diff --git a/.gitignore b/.gitignore index f7ebcbb..e574f58 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ *.swp .svn +*.pyc +.idea/* \ No newline at end of file diff --git a/DESCRIPTION b/DESCRIPTION new file mode 100644 index 0000000..3ff253d --- /dev/null +++ b/DESCRIPTION @@ -0,0 +1,2 @@ +Parts of reusable content across your site. Usable for validation meta codes, +analytics codes, phone numbers, addresses and so on. \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..8a3837a --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +recursive-include chunks * +global-exclude *.orig *.pyc diff --git a/README.md b/README.md index 6f4937d..673f06b 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,75 @@ -Think of it as flatpages for small bits of reusable content you might want to insert into your templates and manage from the admin interface. +# Django chunks documentation + +## Preface +Think of it as flatpages for small bits of reusable content you might want to insert into your templates and manage from the admin interface. This is really nothing more than a model and a template tag. By adding `chunks` to your installed apps list in your Django project and performing a `./manage.py syncdb`, you'll be able to add as many "keyed" bits of content chunks to your site. The idea here is that you can create a chunk of content, name it with a unique key (for example: `home_page_left_bottom`) and then you can call this content from a normal template. -### Why would anyone want this? ### +### Why would anyone want this? Well it essentially allows someone to define "chunks" (I had wanted to call it blocks, but that would be very confusing for obvious reasons) of content in your template that can be directly edited from the awesome Django admin interface. Throwing a rich text editor control on top of it make it even easier. -### Usage: ### +## Installation and basic usage + +1. Install package + + `` pip install git+git://github.com/shoker174/django-chunks.git`` + +2. Configure your settings file: + + ``` + INSTALLED_APPS += ['chunks'] + ``` +3. Call chunks in the html template - {% load chunks %} + Usage example №1: + ``` html + {% load chunks_tags %} - - Test - - -

Blah blah blah

- -
- {% chunk "home_page_left" %} -
- - + ... + {% chunk "phone" %} + ... + ``` + Usage example №2: + ``` html + {% load chunks_tags %} + + ... + {% get_chunk "phone" as phone %} + {% if phone.content %} + {{ phone.content }} + {% endif %} + ... + +4. Apply migrations and run local server + + ```python + python manage.py migrate chunks + python manage.py runserver + ``` +5. Create chunks in admin interface + +## Advansed usage +In many cases you may need in many chunks. Basic usage examples generate one request to database for each chunk - +this is a bad idea for large projects. For large progects your must use cached chunks. -This is really helpful in those cases where you want to use `django.contrib.flatpages` but you need multiple content areas. I hope this is helpful to people and I'll be making minor edits as I see them necessary. +1. Add context processor to settings.py + + ```python + TEMPLATES[0]['OPTIONS']['context_processors'] += ['chunks.context_processors.chunks_processor'] + ``` +2. Call chunks in the html template + ```html + + ... + {% if chunks.phone %} + {{ chunks.phone.content }} + {% endif %} + ... + + ``` diff --git a/USAGE b/USAGE deleted file mode 100644 index c7dbb42..0000000 --- a/USAGE +++ /dev/null @@ -1,58 +0,0 @@ -Think of it as flatpages for small bits of reusable content you might want to -insert into your templates and manage from the admin interface. - -This is really nothing more than a model and a template tag. - -By adding `chunks` to your installed apps list in your Django project and -performing a `./manage.py syncdb`, you'll be able to add as many "keyed" bits -of content chunks to your site. - -The idea here is that you can create a chunk of content, name it with a unique -key (for example: `home_page_left_bottom`) and then you can call this content -from a normal template. - -Why would anyone want this? - -It essentially allows someone to define "chunks" (I had wanted to call it - blocks, but that would be very confusing for obvious reasons) of content in -your template that can be directly edited from the awesome Django admin -interface. Throwing a rich text editor control on top of it make it even -easier. - -Template tag usage: - -{% load chunks %} - - - Test - - -

Blah blah blah

- -
- {% chunk "home_page_left" %} -
- - - - -This is really helpful in those cases where you want to use -`django.contrib.flatpages` but you need multiple content areas. I hope this -is helpful to people and I'll be making minor edits as I see them necessary. - -Caching - -If you want to cache the content of your chunks you can pass along a caching -time argument in your chunk tag. Example: - -{% chunk "home_page_left" 3600 %} - -The caching time is specified in seconds. For caching to work properly you -must configure a cache backend in your settings.py. See the Django -documentation for more information: - -http://www.djangoproject.com/documentation/cache/ diff --git a/chunks/__init__.py b/chunks/__init__.py index e69de29..739fd2b 100644 --- a/chunks/__init__.py +++ b/chunks/__init__.py @@ -0,0 +1,2 @@ +__version__ = '2.2' +default_app_config = 'chunks.apps.ChunksAppConfig' diff --git a/chunks/admin.py b/chunks/admin.py index 06dd917..c22fec1 100644 --- a/chunks/admin.py +++ b/chunks/admin.py @@ -1,8 +1,16 @@ +from django.utils.translation import ugettext_lazy as _ from django.contrib import admin -from models import Chunk +from .models import Chunk + class ChunkAdmin(admin.ModelAdmin): - list_display = ('key',) - search_fields = ('key', 'content') -admin.site.register(Chunk, ChunkAdmin) \ No newline at end of file + def get_content(self, obj): + return obj.content[0:50] + + get_content.short_description = _('content') + list_display = ('key', 'description', 'get_content') + search_fields = ('key', 'content') + + +admin.site.register(Chunk, ChunkAdmin) diff --git a/chunks/apps.py b/chunks/apps.py new file mode 100644 index 0000000..bcd2302 --- /dev/null +++ b/chunks/apps.py @@ -0,0 +1,13 @@ +from django.apps import AppConfig +from django.utils.translation import ugettext_lazy as _ + + +class ChunksAppConfig(AppConfig): + name = 'chunks' + verbose_name = _('Chunk') + + class Meta: + app_label = 'chunks' + + def ready(self): + from . import signals \ No newline at end of file diff --git a/chunks/context_processors.py b/chunks/context_processors.py new file mode 100644 index 0000000..f08cb28 --- /dev/null +++ b/chunks/context_processors.py @@ -0,0 +1,33 @@ +import json +from django.core.cache import cache +from django.utils.html import mark_safe +from .models import Chunk + +CACHE_KEY = 'chunks' + + +def get_chunks_data(): + result = {} + for chunk in Chunk.objects.all(): + result[chunk.key] = { + 'content': chunk.content, + 'file': chunk.file.url if chunk.file else None + } + return result + + +def chunks_processor(request): + chunks_json = cache.get(CACHE_KEY) + if chunks_json: + raw_chunks = json.loads(chunks_json) + else: + raw_chunks = get_chunks_data() + cache.set(CACHE_KEY, json.dumps(raw_chunks, ensure_ascii=False)) + + chunks = {} + for key, val in raw_chunks.items(): + chunks[key] = { + 'content': mark_safe(val['content']), + 'file': val['file'] + } + return {'chunks': chunks} diff --git a/chunks/locale/ru/LC_MESSAGES/django.mo b/chunks/locale/ru/LC_MESSAGES/django.mo new file mode 100644 index 0000000..8e0d989 Binary files /dev/null and b/chunks/locale/ru/LC_MESSAGES/django.mo differ diff --git a/chunks/locale/ru/LC_MESSAGES/django.po b/chunks/locale/ru/LC_MESSAGES/django.po new file mode 100644 index 0000000..87d32dd --- /dev/null +++ b/chunks/locale/ru/LC_MESSAGES/django.po @@ -0,0 +1,49 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: chunks\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-04-16 14:37+0500\n" +"PO-Revision-Date: 2015-04-16 18:28+0500\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: ru\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"X-Generator: Poedit 1.5.4\n" +"X-Poedit-SourceCharset: UTF-8\n" + +#: models.py:11 +msgid "Key" +msgstr "Название" + +#: models.py:15 +msgid "File or image" +msgstr "Файл или изображение" + +#: models.py:11 +msgid "A unique name for this chunk of content" +msgstr "Название должно быть уникальным" + +#: models.py:12 +msgid "Content" +msgstr "Содержание" + +#: models.py:13 +msgid "Description" +msgstr "Описание" + +#: models.py:19 +msgid "Chunk" +msgstr "Фрагмент" + +#: models.py:20 +msgid "Chunks" +msgstr "Фрагменты" diff --git a/chunks/migrations/0001_initial.py b/chunks/migrations/0001_initial.py new file mode 100644 index 0000000..82e5e12 --- /dev/null +++ b/chunks/migrations/0001_initial.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.20 on 2019-05-29 17:34 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Chunk', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('key', models.CharField(help_text='A unique name for this chunk of content', max_length=255, unique=True, verbose_name='Key')), + ('content', models.TextField(blank=True, verbose_name='Content')), + ], + options={ + 'verbose_name': 'Chunk', + 'verbose_name_plural': 'Chunks', + }, + ), + ] diff --git a/chunks/migrations/0002_auto__add_field_chunk_description.py b/chunks/migrations/0002_auto__add_field_chunk_description.py new file mode 100644 index 0000000..d2b69e9 --- /dev/null +++ b/chunks/migrations/0002_auto__add_field_chunk_description.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.20 on 2019-05-29 17:34 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('chunks', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='chunk', + name='description', + field=models.TextField(blank=True, verbose_name='Description'), + ), + ] diff --git a/chunks/migrations/0003_auto__chg_field_chunk_description.py b/chunks/migrations/0003_auto__chg_field_chunk_description.py new file mode 100644 index 0000000..bad560e --- /dev/null +++ b/chunks/migrations/0003_auto__chg_field_chunk_description.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Changing field 'Chunk.description' + db.alter_column('chunks_chunk', 'description', self.gf('django.db.models.fields.TextField')()) + + def backwards(self, orm): + + # Changing field 'Chunk.description' + db.alter_column('chunks_chunk', 'description', self.gf('django.db.models.fields.CharField')(max_length=255)) + + models = { + 'chunks.chunk': { + 'Meta': {'object_name': 'Chunk'}, + 'content': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}) + } + } + + complete_apps = ['chunks'] \ No newline at end of file diff --git a/chunks/migrations/0003_chunk_file.py b/chunks/migrations/0003_chunk_file.py new file mode 100644 index 0000000..a3f0de9 --- /dev/null +++ b/chunks/migrations/0003_chunk_file.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2019-06-21 10:56 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('chunks', '0002_auto__add_field_chunk_description'), + ] + + operations = [ + migrations.AddField( + model_name='chunk', + name='file', + field=models.FileField(blank=True, null=True, upload_to=b'uploads', verbose_name='File or image'), + ), + ] diff --git a/chunks/migrations/__init__.py b/chunks/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/chunks/models.py b/chunks/models.py index cdb36be..e94c9cd 100644 --- a/chunks/models.py +++ b/chunks/models.py @@ -1,4 +1,7 @@ +# -*- coding: utf-8 -*- from django.db import models +from django.utils.translation import ugettext_lazy as _ + class Chunk(models.Model): """ @@ -7,8 +10,14 @@ class Chunk(models.Model): any template with the use of a special template tag """ - key = models.CharField(help_text="A unique name for this chunk of content", blank=False, max_length=255, unique=True) - content = models.TextField(blank=True) + key = models.CharField( _('Key'), help_text=_("A unique name for this chunk of content"), blank=False, max_length=255, unique=True) + content = models.TextField( _('Content'), blank=True) + file = models.FileField( _('File or image'), upload_to='uploads', null=True, blank=True) + description = models.TextField( _('Description'),blank=True, unique=False) - def __unicode__(self): + def __str__(self): return u"%s" % (self.key,) + + class Meta: + verbose_name = _('Chunk') + verbose_name_plural = _('Chunks') diff --git a/chunks/redsolution_setup/__init__.py b/chunks/redsolution_setup/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/chunks/redsolution_setup/__init__.pyc b/chunks/redsolution_setup/__init__.pyc new file mode 100644 index 0000000..170d7d9 Binary files /dev/null and b/chunks/redsolution_setup/__init__.pyc differ diff --git a/chunks/redsolution_setup/__init__.pyo b/chunks/redsolution_setup/__init__.pyo new file mode 100644 index 0000000..170d7d9 Binary files /dev/null and b/chunks/redsolution_setup/__init__.pyo differ diff --git a/chunks/redsolution_setup/make.py b/chunks/redsolution_setup/make.py new file mode 100755 index 0000000..4def2e2 --- /dev/null +++ b/chunks/redsolution_setup/make.py @@ -0,0 +1,19 @@ +from redsolutioncms.make import BaseMake +from redsolutioncms.models import CMSSettings + +class Make(BaseMake): + def make(self): + super(Make, self).make() + cms_settings = CMSSettings.objects.get_settings() + cms_settings.render_to('settings.py', 'chunks/redsolutioncms/settings.pyt') + cms_settings.render_to(['..', 'templates', 'base_chunks.html'], + 'chunks/redsolutioncms/base_chunks.html', { + }, 'w') + cms_settings.render_to('urls.py', 'chunks/redsolutioncms/urls.pyt') + cms_settings.render_to(['..', 'templates', 'robots.txt'], + 'chunks/redsolutioncms/robots.txt', {}, 'w') + cms_settings.base_template = 'base_chunks.html' + cms_settings.save() + +make = Make() + diff --git a/chunks/redsolution_setup/make.pyc b/chunks/redsolution_setup/make.pyc new file mode 100644 index 0000000..a0dffe6 Binary files /dev/null and b/chunks/redsolution_setup/make.pyc differ diff --git a/chunks/redsolution_setup/make.pyo b/chunks/redsolution_setup/make.pyo new file mode 100644 index 0000000..a0dffe6 Binary files /dev/null and b/chunks/redsolution_setup/make.pyo differ diff --git a/chunks/redsolution_setup/templates/chunks/redsolutioncms/base_chunks.html b/chunks/redsolution_setup/templates/chunks/redsolutioncms/base_chunks.html new file mode 100644 index 0000000..4e679da --- /dev/null +++ b/chunks/redsolution_setup/templates/chunks/redsolutioncms/base_chunks.html @@ -0,0 +1,17 @@ +{% load redsolutioncms_tags %}{% start_block %} extends '{{ cms_settings.base_template }}' {% end_block %} +{% raw %} +{% block head %} + {% load chunks %} + {{ block.super }} + {% chunk "end-of-head" %} +{% endblock head %} +{% block body %} + {{ block.super }} + {% chunk "end-of-body" %} +{% endblock body %} +{% block footer %} + {{ block.super }} +
{% chunk "contacts" %}
+ +{% endblock footer %} +{% endraw %} \ No newline at end of file diff --git a/chunks/redsolution_setup/templates/chunks/redsolutioncms/robots.txt b/chunks/redsolution_setup/templates/chunks/redsolutioncms/robots.txt new file mode 100644 index 0000000..03fbd7f --- /dev/null +++ b/chunks/redsolution_setup/templates/chunks/redsolutioncms/robots.txt @@ -0,0 +1,3 @@ +{% load redsolutioncms_tags %}{% raw %} +{% load chunks %}{% chunk "robots.txt" %} +{% endraw %} \ No newline at end of file diff --git a/chunks/redsolution_setup/templates/chunks/redsolutioncms/settings.pyt b/chunks/redsolution_setup/templates/chunks/redsolutioncms/settings.pyt new file mode 100644 index 0000000..368a412 --- /dev/null +++ b/chunks/redsolution_setup/templates/chunks/redsolutioncms/settings.pyt @@ -0,0 +1,3 @@ +# ---- django-chunks ---- + +INSTALLED_APPS += ['chunks'] diff --git a/chunks/redsolution_setup/templates/chunks/redsolutioncms/urls.pyt b/chunks/redsolution_setup/templates/chunks/redsolutioncms/urls.pyt new file mode 100644 index 0000000..1d32a02 --- /dev/null +++ b/chunks/redsolution_setup/templates/chunks/redsolutioncms/urls.pyt @@ -0,0 +1,5 @@ +# ---- django-chunks ---- + +urlpatterns += patterns('', + (r'^robots.txt$', 'django.views.generic.simple.direct_to_template', {'template': 'robots.txt', 'mimetype': 'text/plain'}), +) diff --git a/chunks/signals.py b/chunks/signals.py new file mode 100644 index 0000000..9cdad69 --- /dev/null +++ b/chunks/signals.py @@ -0,0 +1,12 @@ +from django.db.models.signals import post_save, post_delete +from django.core.cache import cache +from .models import Chunk + + +def chunks_change_handler(sender, instance, **kwargs): + cache.delete('chunks') + + +post_save.connect(chunks_change_handler, Chunk) +post_delete.connect(chunks_change_handler, Chunk) + diff --git a/chunks/templatetags/chunks.py b/chunks/templatetags/chunks.py deleted file mode 100644 index 2b06cad..0000000 --- a/chunks/templatetags/chunks.py +++ /dev/null @@ -1,43 +0,0 @@ -from django import template -from django.db import models -from django.core.cache import cache - -register = template.Library() - -Chunk = models.get_model('chunks', 'chunk') -CACHE_PREFIX = "chunk_" - -def do_get_chunk(parser, token): - # split_contents() knows not to split quoted strings. - tokens = token.split_contents() - if len(tokens) < 2 or len(tokens) > 3: - raise template.TemplateSyntaxError, "%r tag should have either 2 or 3 arguments" % (tokens[0],) - if len(tokens) == 2: - tag_name, key = tokens - cache_time = 0 - if len(tokens) == 3: - tag_name, key, cache_time = tokens - # Check to see if the key is properly double/single quoted - if not (key[0] == key[-1] and key[0] in ('"', "'")): - raise template.TemplateSyntaxError, "%r tag's argument should be in quotes" % tag_name - # Send key without quotes and caching time - return ChunkNode(key[1:-1], cache_time) - -class ChunkNode(template.Node): - def __init__(self, key, cache_time=0): - self.key = key - self.cache_time = cache_time - - def render(self, context): - try: - cache_key = CACHE_PREFIX + self.key - c = cache.get(cache_key) - if c is None: - c = Chunk.objects.get(key=self.key) - cache.set(cache_key, c, int(self.cache_time)) - content = c.content - except Chunk.DoesNotExist: - content = '' - return content - -register.tag('chunk', do_get_chunk) diff --git a/chunks/templatetags/chunks_tags.py b/chunks/templatetags/chunks_tags.py new file mode 100644 index 0000000..2e9ee6b --- /dev/null +++ b/chunks/templatetags/chunks_tags.py @@ -0,0 +1,21 @@ +from django import template +from chunks.models import Chunk +from django.utils.safestring import mark_safe + +register = template.Library() + + +@register.simple_tag +def chunk(key): + try: + return mark_safe(Chunk.objects.get(key=key).content) + except: + return '' + + +@register.simple_tag +def get_chunk(key): + try: + return Chunk.objects.get(key=key) + except: + return None diff --git a/setup.py b/setup.py index 8b6bbb6..a18799a 100644 --- a/setup.py +++ b/setup.py @@ -1,12 +1,28 @@ -from distutils.core import setup +# -*- coding: utf-8 -*- +import os +from setuptools import setup, find_packages + +# Utility function to read the README file. +# Used for the long_description. It's nice, because now 1) we have a top level +# README file and 2) it's easier to type in the README file than to put a raw +# string in below ... +def read(fname): + try: + return open(os.path.join(os.path.dirname(__file__), fname)).read() + except IOError: + return '' + +setup(name='redsolutioncms.django-chunks', + version=__import__('chunks').__version__, + description=read('DESCRIPTION'), -setup(name='chunks', - version='0.1', - description='Keyed blocks of content for use in your Django templates', author='Clint Ecker', author_email='me@clintecker.com', + url='http://code.google.com/p/django-chunks/', - packages=['chunks', 'chunks.templatetags'], + + packages=find_packages(), + classifiers=['Development Status :: 4 - Beta', 'Environment :: Web Environment', 'Intended Audience :: Developers', @@ -14,4 +30,12 @@ 'Operating System :: OS Independent', 'Programming Language :: Python', 'Topic :: Utilities'], - ) \ No newline at end of file + + include_package_data=True, + zip_safe=False, + long_description=read('README'), + + entry_points={ + 'redsolutioncms': ['chunks = chunks.redsolution_setup', ], + } + )