├── .gitignore ├── AUTHORS.txt ├── LICENSE.txt ├── MANIFEST.in ├── README.rst ├── flatblocks ├── __init__.py ├── admin.py ├── forms.py ├── locale │ ├── de │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── django.pot │ ├── hu │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── no │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ └── ru │ │ └── LC_MESSAGES │ │ ├── django.mo │ │ └── django.po ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ ├── createflatblock.py │ │ └── deleteflatblock.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py ├── settings.py ├── templates │ └── flatblocks │ │ ├── edit.html │ │ └── flatblock.html ├── templatetags │ ├── __init__.py │ └── flatblock_tags.py ├── tests.py ├── urls.py └── views.py ├── setup.py ├── test_project ├── __init__.py ├── manage.py ├── runtests.py ├── settings.py ├── templates │ └── index.html ├── urls.py └── views.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.bak 3 | *.db 4 | .*.swp 5 | ._* 6 | .idea 7 | README.html 8 | local_settings.py 9 | django_flatblocks.egg-info 10 | dist 11 | build 12 | -------------------------------------------------------------------------------- /AUTHORS.txt: -------------------------------------------------------------------------------- 1 | Original work by Clint Ecker: http://code.google.com/p/django-chunks/ 2 | 3 | Extended by 4 | 5 | * Alexander Artemenko 6 | * Peter Baumgardner 7 | * Kevin Fricovsky 8 | * Horst Gutmann 9 | 10 | Contributions by 11 | 12 | * Michael Greene 13 | * Stephan Jaekel 14 | * James O'Donnell 15 | * Mikhail Korobov 16 | * Henrik Heimbuerger 17 | * Alexander Ovchinnikov 18 | 19 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008, Clint Ecker and Kevin Fricovsky and Peter Baumgartner and Alexander Artemenko 2 | Copyright (c) 2009, Horst Gutmann 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the project nor the names of its contributors may 15 | be used to endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | recursive-include flatblocks/locale * 3 | recursive-include flatblocks/templates/flatblocks *.html 4 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | django-flatblocks 2 | ================= 3 | 4 | .. attention:: 5 | 6 | This project is no longer maintained. Please take a look at 7 | `jazzband/django-flatblocks 8 | `_ for a more 9 | up-to-date version! 10 | 11 | django-flatblocks is a simple application for handling small text-blocks on 12 | websites. Think about it like ``django.contrib.flatpages`` just not for a 13 | whole page but for only parts of it, like an information text describing what 14 | you can do on a site. 15 | 16 | Installation 17 | ------------ 18 | 19 | Probably the easiest way to install this application is to first run `pip 20 | install django-flatblocks`. Once this step is complete add "flatblocks" to 21 | your INSTALLED_APPS setting in your settings.py file and run `python manage.py 22 | syncdb` to update your database. 23 | 24 | 25 | Upgrading 26 | --------- 27 | 28 | django-flatblocks uses `South`_ for handling data and schema migrations 29 | starting with version 0.6.0, so the South-typical update path applies here. 30 | 31 | If you're upgrading from a 0.5.x version or earlier you will have to migrate 32 | in 3 steps: 33 | 34 | 1. Install south. 35 | 36 | 2. Migrate your database to the first version of flatblocks using South:: 37 | 38 | ./manage.py migrate flatblocks 0001 --fake 39 | 40 | 3. Then migrate your dataabase to the latest version of flatblocks' database 41 | and data structure:: 42 | 43 | ./manage.py migrate flatblocks 44 | 45 | Usage 46 | ------------ 47 | 48 | Once you've created some instances of the ``flatblocks.models.FlatBlock`` 49 | model, you can load it it using the ``flatblock_tags`` templatetag-library:: 50 | 51 | {% load flatblock_tags %} 52 | 53 | 54 | 55 | 56 | 57 | 58 |
59 |
60 | 61 |
62 | 65 |
66 | 67 | 68 | 69 | This way you can display a text block with the name 'page.info'. If you 70 | have the name of a block in a template variable, leave out the quotes. 71 | 72 | This tag also accepts an optional argument where you can specify the number 73 | of seconds, the that block should be cached:: 74 | 75 | {% flatblock "page.info" 3600 %} 76 | 77 | Additionally you can also specify which template should be used to render the 78 | flatblock:: 79 | 80 | {% flatblock "page.info" using "my_template.html" %} 81 | # ... 82 | {% flatblock "page.about" 3600 using "my_template.html" %} 83 | 84 | As with the slug of the flatblock also with the template name you have the 85 | choice of using the literal name of the template or pass it to the templatetag 86 | as a variable. 87 | 88 | The content of a flatblock (as well as its header) can also be evaluated as a 89 | full-fledged Django template:: 90 | 91 | {% flatblock "page.info" evaluated %} 92 | 93 | This also works with the other parameters like the cache timeout and the 94 | custom template:: 95 | 96 | 97 | {% flatblock "page.info" evaluated using "my_template.html" %} 98 | {% flatblock "page.about" 3600 evaluated using "my_template.html" %} 99 | 100 | 101 | edit-view 102 | --------- 103 | 104 | With ``flatblocks.views.edit`` django-flatblocks offers a simple view to edit 105 | your flatblocks from your frontend. To use it simply include it in your 106 | URLconf and create a ``flatblocks/edit.html`` template. 107 | 108 | By default the view doesn't do any permission checking, so you should decorate 109 | it accordingly in your URLconf:: 110 | 111 | from flatblocks.views import edit 112 | from django.contrib.auth.decorators import login_required 113 | 114 | # ... 115 | 116 | urlpatterns = pattern('', 117 | url(r'^flatblocks/(?P\d+)/edit/$', login_required(edit), 118 | name='flatblocks-edit'), 119 | # ... 120 | ) 121 | 122 | The template can operate on following variables: 123 | 124 | * ``form`` 125 | * ``flatblock`` 126 | * ``origin`` (the URL of the previous page) 127 | 128 | Additionally the view offers some basic customization hooks via these keyword 129 | arguments: 130 | 131 | ``template_name`` 132 | Name of the template to be used for rendering this view. By default 133 | ``flatblocks/edit.html`` is used. 134 | 135 | ``success_url`` 136 | After successfully editing a flatblock the view will redirect the user to 137 | the URL specified here. By default the view will try to determine the last 138 | visited page before entering the edit-view (which is normally a page where 139 | the flatblock is used) and redirect the user back there. 140 | 141 | ``modelform_class`` 142 | If you want to use a customized ModelForm class for flatblocks you can 143 | specify it here. 144 | 145 | ``permission_check`` 146 | This argument lets you specify a callback function to do some 147 | flatblock-specific permission checking. Such a function could look like 148 | this:: 149 | 150 | def my_permcheck(request, flatblock): 151 | if request.user.is_staff or flatblock.slug == 'free_for_all': 152 | return True 153 | return HttpResponseRedirect('/') 154 | 155 | With this permission callback set, a user that is not a staff-user is not 156 | allowed to edit this view unless it's the "free_for_all" block. If these 157 | criteria are not met, the user is redirected to the root URL of the page. 158 | 159 | The contract here is pretty simple. The permission callback should return 160 | ``False``, if the user should receive a 403 message when trying to edit 161 | this link. If the function returns an instance of ``HttpResponse`` the 162 | view will proceed from the assumption that your view already did 163 | everything there is to do and return that response-object. Any other 164 | return value tells the view that the permissions are OK for the current 165 | user and that it should proceed. 166 | 167 | 168 | History 169 | ------------ 170 | 171 | Since this application targets use-cases that are basically applicable to 172 | most web-projects out there, there are tons of solutions similar to this one. 173 | In fact, this app is a fork originally from `django-chunks`_ developed by 174 | Clint Ecker. 175 | 176 | In November 2008 Kevin Fricovsky created the `original fork`_ in order to add 177 | an additional "active"-flag to each chunk. That project was later on `forked 178 | by Peter Baumgardner`_ who removed that flag again and added a "header"-field 179 | in order to directly associate and optional title with each text block. 180 | 181 | This fork aims now to add more features like variable chunks and also 182 | integrate some of the features developed by H. Waara and S. Cranford in 183 | the `django-better-chunks`_ fork (``django.contrib.site``- and i18n-support). 184 | 185 | Releases 186 | -------- 187 | 188 | 0.8: 189 | * Python 3 & Django 1.6 support 190 | 191 | 0.7: 192 | * Support for evaluated blocks offering access to context variables 193 | 194 | 0.6: 195 | * South support 196 | * Installation and upgrade instructions 197 | 198 | Note: This is primarily a transitional release to get South in here and 199 | open this project up for some database changes in the future. 200 | 201 | 0.5.1 202 | * Removed rendering of the content attribute from the admin list by Michael Fladischer 203 | * PyBabel compatibility by Michael Fladischer 204 | * Fixed caching issue with memcache backend 205 | 206 | 0.5 207 | * Hungarian translation by Török Gábor 208 | * Method added to demo edit form (#5) by Bill Evans 209 | 210 | 0.4 211 | * FlatBlock autocreation by Mikhail Korobov (can be enabled/disabled 212 | with FLATBLOCKS\_AUTOCREATE\_STATIC\_BLOCKS setting) 213 | * Various fixes by Mikhail Korobov 214 | * Fix by Henrik Heimbuerger for the manifest 215 | 216 | 0.3.5 217 | * Russian translation by Mikhail Korobov 218 | 219 | 0.3.4 220 | * Norwegian translation by Eivind Uggedal 221 | 222 | 0.3.3 223 | * FlatBlock.save should also accept optional kwargs. 224 | 225 | 0.3.2 226 | * All settings are now in the flatblocks.settings module 227 | 228 | 0.3.1 229 | * Fixes a bug with FlatBlock.save() failing to reset the cache 230 | * Buildout integration for easier testing 231 | * Example urls.py and flatblocks/edit.html-template 232 | 233 | 0.3 234 | * createflatblock and deleteflatblock commands 235 | * On saving a flatblock its cache will be cleared 236 | * As last argument of the template tag you can now also specify a template 237 | name. 238 | 0.2 239 | * Translatable 240 | * ``flatblocks.views.edit`` view for editing flatblocks 241 | 0.1 242 | Initial release 243 | 244 | .. _`original fork`: http://github.com/howiworkdaily/django-flatblock/ 245 | .. _`django-chunks`: http://code.google.com/p/django-chunks/ 246 | .. _`django-better-chunks`: http://bitbucket.org/hakanw/django-better-chunks/ 247 | .. _`forked by Peter Baumgardner`: http://github.com/lincolnloop/django-flatblock/ 248 | .. _`south`: http://south.aeracode.org/ 249 | -------------------------------------------------------------------------------- /flatblocks/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zerok/django-flatblocks/a5b93d3b61d525b02099bd4fce6a3c38ec41c02e/flatblocks/__init__.py -------------------------------------------------------------------------------- /flatblocks/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from flatblocks.models import FlatBlock 3 | 4 | 5 | class FlatBlockAdmin(admin.ModelAdmin): 6 | ordering = ['slug', ] 7 | list_display = ('slug', 'header') 8 | search_fields = ('slug', 'header', 'content') 9 | 10 | admin.site.register(FlatBlock, FlatBlockAdmin) 11 | -------------------------------------------------------------------------------- /flatblocks/forms.py: -------------------------------------------------------------------------------- 1 | from django.forms import ModelForm 2 | 3 | from flatblocks.models import FlatBlock 4 | 5 | 6 | class FlatBlockForm(ModelForm): 7 | class Meta: 8 | model = FlatBlock 9 | exclude = ('slug', ) 10 | -------------------------------------------------------------------------------- /flatblocks/locale/de/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zerok/django-flatblocks/a5b93d3b61d525b02099bd4fce6a3c38ec41c02e/flatblocks/locale/de/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /flatblocks/locale/de/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Report-Msgid-Bugs-To: \n" 4 | "POT-Creation-Date: 2013-09-26 08:58+0700\n" 5 | "Last-Translator: Horst Gutmann \n" 6 | "Language-Team: de \n" 7 | "Language: \n" 8 | "MIME-Version: 1.0\n" 9 | "Content-Type: text/plain; charset=UTF-8\n" 10 | "Content-Transfer-Encoding: 8bit\n" 11 | "Plural-Forms: nplurals=2; plural=(n != 1)\n" 12 | 13 | #: models.py:15 14 | msgid "Slug" 15 | msgstr "Slug" 16 | 17 | #: models.py:16 18 | msgid "A unique name used for reference in the templates" 19 | msgstr "" 20 | "Ein eindeutiger Name, über den dieser Block in Templates referenziert werden " 21 | "kann" 22 | 23 | #: models.py:19 24 | msgid "Header" 25 | msgstr "Überschrift" 26 | 27 | #: models.py:20 28 | msgid "An optional header for this content" 29 | msgstr "Eine optionale Überschrift für diesen Block" 30 | 31 | #: models.py:22 32 | msgid "Content" 33 | msgstr "Inhalt" 34 | 35 | #: models.py:44 36 | msgid "Flat block" 37 | msgstr "Flatblock" 38 | 39 | #: models.py:45 40 | msgid "Flat blocks" 41 | msgstr "Flatblocks" 42 | 43 | #: views.py:46 44 | msgid "You are not allowed to edit this flatblock" 45 | msgstr "Du darfst diesen Flatblock nicht editieren" 46 | -------------------------------------------------------------------------------- /flatblocks/locale/django.pot: -------------------------------------------------------------------------------- 1 | # Translations template for PROJECT. 2 | # Copyright (C) 2010 ORGANIZATION 3 | # This file is distributed under the same license as the PROJECT project. 4 | # FIRST AUTHOR , 2010. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PROJECT VERSION\n" 10 | "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" 11 | "POT-Creation-Date: 2010-11-26 10:27+0100\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=utf-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Generated-By: Babel 0.9.5\n" 19 | 20 | #: models.py:15 21 | msgid "Slug" 22 | msgstr "" 23 | 24 | #: models.py:16 25 | msgid "A unique name used for reference in the templates" 26 | msgstr "" 27 | 28 | #: models.py:18 29 | msgid "Header" 30 | msgstr "" 31 | 32 | #: models.py:19 33 | msgid "An optional header for this content" 34 | msgstr "" 35 | 36 | #: models.py:20 37 | msgid "Content" 38 | msgstr "" 39 | 40 | #: models.py:31 41 | msgid "Flat block" 42 | msgstr "" 43 | 44 | #: models.py:32 45 | msgid "Flat blocks" 46 | msgstr "" 47 | 48 | #: views.py:44 49 | msgid "You are not allowed to edit this flatblock" 50 | msgstr "" 51 | 52 | -------------------------------------------------------------------------------- /flatblocks/locale/hu/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zerok/django-flatblocks/a5b93d3b61d525b02099bd4fce6a3c38ec41c02e/flatblocks/locale/hu/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /flatblocks/locale/hu/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # Hungarian translations for django-flatblocks. 2 | # This file is distributed under the same license as the django-flatblocks project. 3 | # 4 | #, fuzzy 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: django-flatblocks\n" 8 | "Report-Msgid-Bugs-To: \n" 9 | "POT-Creation-Date: 2013-09-26 08:58+0700\n" 10 | "PO-Revision-Date: 2010-11-26 10:49+0100\n" 11 | "Last-Translator: Török Gábor \n" 12 | "Language-Team: Hungarian \n" 13 | "Language: hu\n" 14 | "MIME-Version: 1.0\n" 15 | "Content-Type: text/plain; charset=UTF-8\n" 16 | "Content-Transfer-Encoding: 8bit\n" 17 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 18 | "Generated-By: Babel 0.9.5\n" 19 | 20 | #: models.py:15 21 | msgid "Slug" 22 | msgstr "Név" 23 | 24 | #: models.py:16 25 | msgid "A unique name used for reference in the templates" 26 | msgstr "Egyedi név, amivel a sablonokban tud majd a dobozra hivatkozni" 27 | 28 | #: models.py:19 29 | msgid "Header" 30 | msgstr "Cím" 31 | 32 | #: models.py:20 33 | msgid "An optional header for this content" 34 | msgstr "Opcionális cím a tartalomhoz" 35 | 36 | #: models.py:22 37 | msgid "Content" 38 | msgstr "Tartalom" 39 | 40 | #: models.py:44 41 | msgid "Flat block" 42 | msgstr "Egyszerű doboz" 43 | 44 | #: models.py:45 45 | msgid "Flat blocks" 46 | msgstr "Egyszerű dobozok" 47 | 48 | #: views.py:46 49 | msgid "You are not allowed to edit this flatblock" 50 | msgstr "Nincs joga a doboz módosításához" 51 | -------------------------------------------------------------------------------- /flatblocks/locale/no/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zerok/django-flatblocks/a5b93d3b61d525b02099bd4fce6a3c38ec41c02e/flatblocks/locale/no/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /flatblocks/locale/no/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Report-Msgid-Bugs-To: \n" 4 | "POT-Creation-Date: 2013-09-26 08:58+0700\n" 5 | "Last-Translator: Eivind Uggedal \n" 6 | "Language-Team: Norsk \n" 7 | "Language: \n" 8 | "MIME-Version: 1.0\n" 9 | "Content-Type: text/plain; charset=UTF-8\n" 10 | "Content-Transfer-Encoding: 8bit\n" 11 | "Plural-Forms: nplurals=2; plural=(n != 1)\n" 12 | 13 | #: models.py:15 14 | msgid "Slug" 15 | msgstr "Slug" 16 | 17 | #: models.py:16 18 | msgid "A unique name used for reference in the templates" 19 | msgstr "Et unikt navn som kan refereres til fra en mal" 20 | 21 | #: models.py:19 22 | msgid "Header" 23 | msgstr "Overskrift" 24 | 25 | #: models.py:20 26 | msgid "An optional header for this content" 27 | msgstr "En overskrift for dette innholdet (ikke påkrevd)" 28 | 29 | #: models.py:22 30 | msgid "Content" 31 | msgstr "Innhold" 32 | 33 | #: models.py:44 34 | msgid "Flat block" 35 | msgstr "Flatblokk" 36 | 37 | #: models.py:45 38 | msgid "Flat blocks" 39 | msgstr "Flatblokker" 40 | 41 | #: views.py:46 42 | msgid "You are not allowed to edit this flatblock" 43 | msgstr "Du har ikke lov til endre denne flatblokken" 44 | -------------------------------------------------------------------------------- /flatblocks/locale/ru/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zerok/django-flatblocks/a5b93d3b61d525b02099bd4fce6a3c38ec41c02e/flatblocks/locale/ru/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /flatblocks/locale/ru/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Report-Msgid-Bugs-To: \n" 4 | "POT-Creation-Date: 2013-09-26 08:58+0700\n" 5 | "Last-Translator: Mikhail Korobov \n" 6 | "Language-Team: Russian\n" 7 | "Language: \n" 8 | "MIME-Version: 1.0\n" 9 | "Content-Type: text/plain; charset=UTF-8\n" 10 | "Content-Transfer-Encoding: 8bit\n" 11 | "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" 12 | "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" 13 | 14 | #: models.py:15 15 | msgid "Slug" 16 | msgstr "Название" 17 | 18 | #: models.py:16 19 | msgid "A unique name used for reference in the templates" 20 | msgstr "Уникальное имя, которое используется в шаблонах" 21 | 22 | #: models.py:19 23 | msgid "Header" 24 | msgstr "Заголовок" 25 | 26 | #: models.py:20 27 | msgid "An optional header for this content" 28 | msgstr "Заголовок (не обязательно)" 29 | 30 | #: models.py:22 31 | msgid "Content" 32 | msgstr "Содержание" 33 | 34 | #: models.py:44 35 | msgid "Flat block" 36 | msgstr "Блок текста" 37 | 38 | #: models.py:45 39 | msgid "Flat blocks" 40 | msgstr "Блоки текста" 41 | 42 | #: views.py:46 43 | msgid "You are not allowed to edit this flatblock" 44 | msgstr "Недостаточно прав для редактирования этого блока текста" 45 | -------------------------------------------------------------------------------- /flatblocks/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zerok/django-flatblocks/a5b93d3b61d525b02099bd4fce6a3c38ec41c02e/flatblocks/management/__init__.py -------------------------------------------------------------------------------- /flatblocks/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zerok/django-flatblocks/a5b93d3b61d525b02099bd4fce6a3c38ec41c02e/flatblocks/management/commands/__init__.py -------------------------------------------------------------------------------- /flatblocks/management/commands/createflatblock.py: -------------------------------------------------------------------------------- 1 | from django.core.management import BaseCommand, CommandError 2 | from django.db import IntegrityError 3 | 4 | from flatblocks.models import FlatBlock 5 | 6 | 7 | class Command(BaseCommand): 8 | help = "Create a new flatblock with the given slug" 9 | 10 | def handle(self, *args, **options): 11 | if len(args) != 1: 12 | raise CommandError("This command requires the slug of the new " 13 | "flatblock as its first argument") 14 | slug = args[0] 15 | block = FlatBlock(header="[{0}]".format(slug), 16 | content="Empty flatblock", 17 | slug=slug) 18 | try: 19 | block.save() 20 | except IntegrityError: 21 | raise CommandError("A flatblock with this name already exists") 22 | -------------------------------------------------------------------------------- /flatblocks/management/commands/deleteflatblock.py: -------------------------------------------------------------------------------- 1 | from django.core.management import BaseCommand, CommandError 2 | 3 | from flatblocks.models import FlatBlock 4 | 5 | 6 | class Command(BaseCommand): 7 | help = "Delete a flatblock with the given slug" 8 | 9 | def handle(self, *args, **options): 10 | if len(args) != 1: 11 | raise CommandError("This command requires the slug of the " 12 | "flatblock as its first argument") 13 | slug = args[0] 14 | try: 15 | FlatBlock.objects.get(slug=slug).delete() 16 | except FlatBlock.DoesNotExist: 17 | raise CommandError("The requested flatblock doesn't exist") 18 | -------------------------------------------------------------------------------- /flatblocks/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | class Migration(SchemaMigration): 8 | 9 | def forwards(self, orm): 10 | 11 | # Adding model 'FlatBlock' 12 | db.create_table('flatblocks_flatblock', ( 13 | ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 14 | ('slug', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255)), 15 | ('header', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)), 16 | ('content', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), 17 | )) 18 | db.send_create_signal('flatblocks', ['FlatBlock']) 19 | 20 | 21 | def backwards(self, orm): 22 | 23 | # Deleting model 'FlatBlock' 24 | db.delete_table('flatblocks_flatblock') 25 | 26 | 27 | models = { 28 | 'flatblocks.flatblock': { 29 | 'Meta': {'object_name': 'FlatBlock'}, 30 | 'content': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 31 | 'header': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), 32 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 33 | 'slug': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}) 34 | } 35 | } 36 | 37 | complete_apps = ['flatblocks'] 38 | -------------------------------------------------------------------------------- /flatblocks/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zerok/django-flatblocks/a5b93d3b61d525b02099bd4fce6a3c38ec41c02e/flatblocks/migrations/__init__.py -------------------------------------------------------------------------------- /flatblocks/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.utils.encoding import python_2_unicode_compatible 3 | from django.utils.translation import ugettext_lazy as _ 4 | from django.core.cache import cache 5 | 6 | from flatblocks.settings import CACHE_PREFIX 7 | 8 | 9 | @python_2_unicode_compatible 10 | class FlatBlock(models.Model): 11 | """ 12 | Think of a flatblock as a flatpage but for just part of a site. It's 13 | basically a piece of content with a given name (slug) and an optional 14 | title (header) which you can, for example, use in a sidebar of a website. 15 | """ 16 | slug = models.CharField(max_length=255, unique=True, 17 | verbose_name=_('Slug'), 18 | help_text=_("A unique name used for reference in " 19 | "the templates")) 20 | header = models.CharField(blank=True, null=True, max_length=255, 21 | verbose_name=_('Header'), 22 | help_text=_("An optional header for this " 23 | "content")) 24 | content = models.TextField(verbose_name=_('Content'), blank=True, 25 | null=True) 26 | 27 | # Helper attributes used if content should be evaluated in order to 28 | # represent the original content. 29 | raw_content = None 30 | raw_header = None 31 | 32 | def __str__(self): 33 | return self.slug 34 | 35 | def save(self, *args, **kwargs): 36 | super(FlatBlock, self).save(*args, **kwargs) 37 | # Now also invalidate the cache used in the templatetag 38 | cache.delete('%s%s' % (CACHE_PREFIX, self.slug, )) 39 | 40 | def delete(self, *args, **kwargs): 41 | cache_key = '%s%s' % (CACHE_PREFIX, self.slug,) 42 | super(FlatBlock, self).delete(*args, **kwargs) 43 | cache.delete(cache_key) 44 | 45 | class Meta: 46 | verbose_name = _('Flat block') 47 | verbose_name_plural = _('Flat blocks') 48 | -------------------------------------------------------------------------------- /flatblocks/settings.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | CACHE_PREFIX = getattr(settings, 'FLATBLOCKS_CACHE_PREFIX', 'flatblocks_') 4 | AUTOCREATE_STATIC_BLOCKS = getattr(settings, 5 | 'FLATBLOCKS_AUTOCREATE_STATIC_BLOCKS', 6 | False) 7 | -------------------------------------------------------------------------------- /flatblocks/templates/flatblocks/edit.html: -------------------------------------------------------------------------------- 1 |

Edit "{{ flatblock.slug }}"

2 | 3 |
4 | {{ form.as_p }} 5 | 6 |
7 | -------------------------------------------------------------------------------- /flatblocks/templates/flatblocks/flatblock.html: -------------------------------------------------------------------------------- 1 |
2 | {% if flatblock.header %} 3 |

{{ flatblock.header }}

4 | {% endif %} 5 |
{{ flatblock.content|safe }}
6 |
7 | -------------------------------------------------------------------------------- /flatblocks/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zerok/django-flatblocks/a5b93d3b61d525b02099bd4fce6a3c38ec41c02e/flatblocks/templatetags/__init__.py -------------------------------------------------------------------------------- /flatblocks/templatetags/flatblock_tags.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module offers one templatetag called "flatblock" which allows you to 3 | easily embed small text-snippets (like for example the help section of a page) 4 | into a template. 5 | 6 | It accepts 2 parameter: 7 | 8 | slug 9 | The slug/key of the text (for example 'contact_help'). There are two 10 | ways you can pass the slug to the templatetag: (1) by its name or 11 | (2) as a variable. 12 | 13 | If you want to pass it by name, you have to use quotes on it. 14 | Otherwise just use the variable name. 15 | 16 | cache_time 17 | The number of seconds that text should get cached after it has been 18 | fetched from the database. 19 | 20 | This field is optional and defaults to no caching (0). 21 | 22 | To use Django's default caching length use None. 23 | 24 | Example:: 25 | 26 | {% load flatblock_tags %} 27 | 28 | ... 29 | 30 | {% flatblock 'contact_help' %} 31 | {% flatblock name_in_variable %} 32 | 33 | The 'flatblock' template tag acts like an inclusiontag and operates on the 34 | ``flatblock/flatblock.html`` template file, which gets (besides the global 35 | context) also the ``flatblock`` variable passed. 36 | 37 | Compared to the original implementation this includes not only the block's 38 | content but the whole object inclusing title, slug and id. This way you 39 | can easily for example offer administrative operations (like editing) 40 | within that template. 41 | 42 | """ 43 | 44 | from django import template 45 | from django.template import loader 46 | from django.db import models 47 | from django.core.cache import cache 48 | 49 | from flatblocks import settings 50 | 51 | import logging 52 | 53 | 54 | register = template.Library() 55 | logger = logging.getLogger(__name__) 56 | 57 | FlatBlock = models.get_model('flatblocks', 'flatblock') 58 | 59 | 60 | class BasicFlatBlockWrapper(object): 61 | def prepare(self, parser, token): 62 | """ 63 | The parser checks for following tag-configurations:: 64 | 65 | {% flatblock {block} %} 66 | {% flatblock {block} {timeout} %} 67 | {% flatblock {block} using {tpl_name} %} 68 | {% flatblock {block} {timeout} using {tpl_name} %} 69 | {% flatblock {block} evaluated %} 70 | {% flatblock {block} evaluated using {tpl_name} %} 71 | {% flatblock {block} {timeout} evaluated %} 72 | {% flatblock {block} {timeout} evaluated using {tpl_name} %} 73 | """ 74 | tokens = token.split_contents() 75 | self.is_variable = False 76 | self.tpl_is_variable = False 77 | self.slug = None 78 | self.cache_time = 0 79 | self.tpl_name = None 80 | self.evaluated = False 81 | tag_name, self.slug, args = tokens[0], tokens[1], tokens[2:] 82 | num_args = len(args) 83 | if num_args == 0: 84 | # Only the block name was specified 85 | pass 86 | elif num_args == 1: 87 | # block and timeout 88 | if args[0] == 'evaluated': 89 | self.evaluated = True 90 | else: 91 | self.cache_time = args[0] 92 | elif num_args == 2: 93 | if args[0] != 'using': 94 | # block, timeout, "evaluated" 95 | if args[1] != 'evaluated': 96 | raise template.TemplateSyntaxError("{0!r} tag with two " 97 | "arguments has to " 98 | "include the cache " 99 | "timeout and the " 100 | "evaluated flag".format( 101 | tag_name)) 102 | self.cache_time = args[0] 103 | self.evaluated = True 104 | else: 105 | # block, "using", tpl_name 106 | self.tpl_name = args[1] 107 | elif num_args == 3: 108 | # block, timeout|"evaluated", "using", tpl_name 109 | if args[0] == 'evaluated': 110 | self.evaluated = True 111 | else: 112 | self.cache_time = args[0] 113 | self.tpl_name = args[2] 114 | elif num_args == 4: 115 | self.cache_time = args[0] 116 | self.evaluated = True 117 | self.tpl_name = args[3] 118 | else: 119 | raise template.TemplateSyntaxError("{0!r} tag should have between " 120 | "1 and 5 arguments".format( 121 | tokens[0])) 122 | # Check to see if the slug is properly double/single quoted 123 | if not (self.slug[0] == self.slug[-1] and self.slug[0] in ('"', "'")): 124 | self.is_variable = True 125 | else: 126 | self.slug = self.slug[1:-1] 127 | # Clean up the template name 128 | if self.tpl_name is not None: 129 | if not(self.tpl_name[0] == self.tpl_name[-1] and 130 | self.tpl_name[0] in ('"', "'")): 131 | self.tpl_is_variable = True 132 | else: 133 | self.tpl_name = self.tpl_name[1:-1] 134 | if self.cache_time is not None and self.cache_time != 'None': 135 | self.cache_time = int(self.cache_time) 136 | 137 | def __call__(self, parser, token): 138 | self.prepare(parser, token) 139 | return FlatBlockNode(self.slug, self.is_variable, self.cache_time, 140 | template_name=self.tpl_name, 141 | tpl_is_variable=self.tpl_is_variable, 142 | evaluated=self.evaluated) 143 | 144 | 145 | class PlainFlatBlockWrapper(BasicFlatBlockWrapper): 146 | def __call__(self, parser, token): 147 | self.prepare(parser, token) 148 | return FlatBlockNode(self.slug, self.is_variable, self.cache_time, 149 | False, evaluated=self.evaluated) 150 | 151 | 152 | do_get_flatblock = BasicFlatBlockWrapper() 153 | do_plain_flatblock = PlainFlatBlockWrapper() 154 | 155 | 156 | class FlatBlockNode(template.Node): 157 | def __init__(self, slug, is_variable, cache_time=0, with_template=True, 158 | template_name=None, tpl_is_variable=False, evaluated=False): 159 | if template_name is None: 160 | self.template_name = 'flatblocks/flatblock.html' 161 | else: 162 | if tpl_is_variable: 163 | self.template_name = template.Variable(template_name) 164 | else: 165 | self.template_name = template_name 166 | self.slug = slug 167 | self.is_variable = is_variable 168 | self.cache_time = cache_time 169 | self.with_template = with_template 170 | self.evaluated = evaluated 171 | 172 | def render(self, context): 173 | if self.is_variable: 174 | real_slug = template.Variable(self.slug).resolve(context) 175 | else: 176 | real_slug = self.slug 177 | if isinstance(self.template_name, template.Variable): 178 | real_template = self.template_name.resolve(context) 179 | else: 180 | real_template = self.template_name 181 | # Eventually we want to pass the whole context to the template so that 182 | # users have the maximum of flexibility of what to do in there. 183 | if self.with_template: 184 | new_ctx = template.Context({}) 185 | new_ctx.update(context) 186 | try: 187 | flatblock = None 188 | if self.cache_time != 0: 189 | cache_key = settings.CACHE_PREFIX + real_slug 190 | flatblock = cache.get(cache_key) 191 | if flatblock is None: 192 | 193 | # if flatblock's slug is hard-coded in template then it is 194 | # safe and convenient to auto-create block if it doesn't exist. 195 | # This behaviour can be configured using the 196 | # FLATBLOCKS_AUTOCREATE_STATIC_BLOCKS setting 197 | if self.is_variable or not settings.AUTOCREATE_STATIC_BLOCKS: 198 | flatblock = FlatBlock.objects.get(slug=real_slug) 199 | else: 200 | flatblock, _ = FlatBlock.objects.get_or_create( 201 | slug=real_slug, 202 | defaults={'content': real_slug} 203 | ) 204 | if self.cache_time != 0: 205 | if self.cache_time is None or self.cache_time == 'None': 206 | logger.debug("Caching {0} for the cache's default " 207 | "timeout".format(real_slug)) 208 | cache.set(cache_key, flatblock) 209 | else: 210 | logger.debug("Caching {0} for {1} seconds".format( 211 | real_slug, str(self.cache_time))) 212 | cache.set(cache_key, flatblock, int(self.cache_time)) 213 | else: 214 | logger.debug("Don't cache %s" % (real_slug,)) 215 | 216 | if self.evaluated: 217 | flatblock.raw_content = flatblock.content 218 | flatblock.raw_header = flatblock.header 219 | flatblock.content = self._evaluate(flatblock.content, context) 220 | flatblock.header = self._evaluate(flatblock.header, context) 221 | 222 | if self.with_template: 223 | tmpl = loader.get_template(real_template) 224 | new_ctx.update({'flatblock': flatblock}) 225 | return tmpl.render(new_ctx) 226 | else: 227 | return flatblock.content 228 | except FlatBlock.DoesNotExist: 229 | return '' 230 | 231 | def _evaluate(self, content, context): 232 | return template.Template(content).render(context) 233 | 234 | register.tag('flatblock', do_get_flatblock) 235 | register.tag('plain_flatblock', do_plain_flatblock) 236 | -------------------------------------------------------------------------------- /flatblocks/tests.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from django.test import TestCase 3 | from django.core.cache import cache 4 | from django.contrib.auth.models import User 5 | from django import db 6 | 7 | from flatblocks.models import FlatBlock 8 | from flatblocks.templatetags.flatblock_tags import do_get_flatblock 9 | from flatblocks import settings 10 | 11 | 12 | class BasicTests(TestCase): 13 | urls = 'flatblocks.urls' 14 | 15 | def setUp(self): 16 | self.testblock = FlatBlock.objects.create(slug='block', 17 | header='HEADER', 18 | content='CONTENT' 19 | ) 20 | self.admin = User.objects.create_superuser('admin', 'admin@localhost', 21 | 'adminpwd') 22 | 23 | def testURLConf(self): 24 | # We have to support two different APIs here (1.1 and 1.2) 25 | def get_tmpl(resp): 26 | if hasattr(resp, 'templates'): 27 | return resp.templates[0] 28 | else: 29 | if isinstance(resp.template, list): 30 | return resp.template[0] 31 | return resp.template 32 | self.assertEqual(get_tmpl(self.client.get('/edit/1/')).name, 33 | 'admin/login.html') 34 | self.client.login(username='admin', password='adminpwd') 35 | self.assertEqual(get_tmpl(self.client.get('/edit/1/')).name, 36 | 'flatblocks/edit.html') 37 | 38 | def testCacheReset(self): 39 | """ 40 | Tests if FlatBlock.save() resets the cache. 41 | """ 42 | tpl = template.Template( 43 | '{% load flatblock_tags %}{% flatblock "block" 60 %}') 44 | tpl.render(template.Context({})) 45 | name = '%sblock' % settings.CACHE_PREFIX 46 | self.assertNotEqual(None, cache.get(name)) 47 | block = FlatBlock.objects.get(slug='block') 48 | block.header = 'UPDATED' 49 | block.save() 50 | self.assertEqual(None, cache.get(name)) 51 | 52 | def testSaveForceUpdate(self): 53 | block = FlatBlock(slug='missing') 54 | with self.assertRaises(ValueError): 55 | block.save(force_update=True) 56 | 57 | def testSaveForceInsert(self): 58 | block = FlatBlock.objects.get(slug='block') 59 | with self.assertRaises(db.IntegrityError): 60 | block.save(force_insert=True) 61 | 62 | def testCacheRemoval(self): 63 | """ 64 | If a block is deleted it should also be removed from the cache. 65 | """ 66 | block = FlatBlock(slug="test", content="CONTENT") 67 | block.save() 68 | tpl = template.Template( 69 | '{% load flatblock_tags %}{% flatblock "test" 100 %}') 70 | # We fill the cache by rendering the block 71 | tpl.render(template.Context({})) 72 | cache_key = "%stest" % settings.CACHE_PREFIX 73 | self.assertNotEqual(None, cache.get(cache_key)) 74 | block.delete() 75 | self.assertEqual(None, cache.get(cache_key)) 76 | 77 | 78 | class TagTests(TestCase): 79 | def setUp(self): 80 | self.testblock = FlatBlock.objects.create(slug='block', 81 | header='HEADER', 82 | content='CONTENT' 83 | ) 84 | 85 | def testLoadingTaglib(self): 86 | """Tests if the taglib defined in this app can be loaded""" 87 | tpl = template.Template('{% load flatblock_tags %}') 88 | tpl.render(template.Context({})) 89 | 90 | def testExistingPlain(self): 91 | tpl = template.Template( 92 | '{% load flatblock_tags %}{% plain_flatblock "block" %}') 93 | self.assertEqual('CONTENT', tpl.render(template.Context({})).strip()) 94 | 95 | def testExistingTemplate(self): 96 | expected = """
97 | 98 |

HEADER

99 | 100 |
CONTENT
101 |
102 | """ 103 | tpl = template.Template( 104 | '{% load flatblock_tags %}{% flatblock "block" %}') 105 | self.assertEqual(expected, tpl.render(template.Context({}))) 106 | 107 | def testUsingMissingTemplate(self): 108 | tpl = template.Template( 109 | '{% load flatblock_tags %}' 110 | '{% flatblock "block" using "missing_template.html" %}') 111 | exception = template.base.TemplateDoesNotExist 112 | self.assertRaises(exception, tpl.render, template.Context({})) 113 | 114 | def testSyntax(self): 115 | tpl = template.Template( 116 | '{% load flatblock_tags %}{% flatblock "block" %}') 117 | tpl.render(template.Context({})) 118 | node = do_get_flatblock(None, 119 | template.Token('TOKEN_TEXT', 120 | 'flatblock "block"')) 121 | self.assertEqual('block', node.slug) 122 | self.assertEqual(False, node.evaluated) 123 | 124 | tpl = template.Template( 125 | '{% load flatblock_tags %}{% flatblock "block" 123 %}') 126 | tpl.render(template.Context({})) 127 | node = do_get_flatblock(None, template.Token('TOKEN_TEXT', 128 | 'flatblock "block" 123')) 129 | self.assertEqual('block', node.slug) 130 | self.assertEqual(False, node.evaluated) 131 | self.assertEqual(123, node.cache_time) 132 | 133 | tpl = template.Template( 134 | '{% load flatblock_tags %}' 135 | '{% flatblock "block" using "flatblocks/flatblock.html" %}') 136 | tpl.render(template.Context({})) 137 | node = do_get_flatblock(None, 138 | template.Token('TOKEN_TEXT', 139 | 'flatblock "block" using ' 140 | '"flatblocks/flatblock.html"')) 141 | self.assertEqual('block', node.slug) 142 | self.assertEqual(False, node.evaluated) 143 | self.assertEqual(0, node.cache_time) 144 | self.assertEqual("flatblocks/flatblock.html", node.template_name) 145 | 146 | tpl = template.Template( 147 | '{% load flatblock_tags %}' 148 | '{% flatblock "block" 123 using "flatblocks/flatblock.html" %}') 149 | tpl.render(template.Context({})) 150 | node = do_get_flatblock(None, 151 | template.Token('TOKEN_TEXT', 152 | 'flatblock "block" 123 using ' 153 | '"flatblocks/flatblock.html"')) 154 | self.assertEqual('block', node.slug) 155 | self.assertEqual(False, node.evaluated) 156 | self.assertEqual(123, node.cache_time) 157 | self.assertEqual("flatblocks/flatblock.html", node.template_name) 158 | 159 | tpl = template.Template( 160 | '{% load flatblock_tags %}{% flatblock "block" evaluated %}') 161 | tpl.render(template.Context({})) 162 | node = do_get_flatblock(None, 163 | template.Token('TOKEN_TEXT', 164 | 'flatblock "block" evaluated')) 165 | self.assertEqual('block', node.slug) 166 | self.assertEqual(True, node.evaluated) 167 | self.assertEqual(0, node.cache_time) 168 | 169 | tpl = template.Template( 170 | '{% load flatblock_tags %}' 171 | '{% flatblock "block" evaluated using ' 172 | '"flatblocks/flatblock.html" %}') 173 | tpl.render(template.Context({})) 174 | node = do_get_flatblock(None, 175 | template.Token('TOKEN_TEXT', 176 | 'flatblock "block" evaluated ' 177 | 'using ' 178 | '"flatblocks/flatblock.html"')) 179 | self.assertEqual('block', node.slug) 180 | self.assertEqual(True, node.evaluated) 181 | self.assertEqual(0, node.cache_time) 182 | self.assertEqual("flatblocks/flatblock.html", node.template_name) 183 | 184 | tpl = template.Template( 185 | '{% load flatblock_tags %}{% flatblock "block" 123 evaluated %}') 186 | tpl.render(template.Context({})) 187 | node = do_get_flatblock(None, 188 | template.Token('TOKEN_TEXT', 189 | 'flatblock "block" 123 ' 190 | 'evaluated')) 191 | self.assertEqual(123, node.cache_time) 192 | self.assertEqual(True, node.evaluated) 193 | 194 | tpl = template.Template( 195 | '{% load flatblock_tags %}{% flatblock "block" 123 evaluated ' 196 | 'using "flatblocks/flatblock.html" %}') 197 | tpl.render(template.Context({})) 198 | node = do_get_flatblock(None, 199 | template.Token('TOKEN_TEXT', 200 | 'flatblock "block" 123 ' 201 | 'evaluated using ' 202 | '"flatblocks/flatblock.html"')) 203 | self.assertEqual('block', node.slug) 204 | self.assertEqual(True, node.evaluated) 205 | self.assertEqual(123, node.cache_time) 206 | self.assertEqual("flatblocks/flatblock.html", node.template_name) 207 | 208 | def testBlockAsVariable(self): 209 | tpl = template.Template( 210 | '{% load flatblock_tags %}{% flatblock blockvar %}') 211 | tpl.render(template.Context({'blockvar': 'block'})) 212 | 213 | def testContentEvaluation(self): 214 | """ 215 | If a block is set in the template to be evaluated the actual content of 216 | the block is treated as a Django template and receives the parent 217 | template's context. 218 | """ 219 | FlatBlock.objects.create(slug='tmpl_block', 220 | header='HEADER', 221 | content='{{ variable }}' 222 | ) 223 | tpl = template.Template( 224 | '{% load flatblock_tags %}' 225 | '{% plain_flatblock "tmpl_block" evaluated %}') 226 | result = tpl.render(template.Context({'variable': 'value'})) 227 | self.assertEqual('value', result) 228 | 229 | def testDisabledEvaluation(self): 230 | """ 231 | If "evaluated" is not passed, no evaluation should take place. 232 | """ 233 | FlatBlock.objects.create(slug='tmpl_block', 234 | header='HEADER', 235 | content='{{ variable }}' 236 | ) 237 | tpl = template.Template( 238 | '{% load flatblock_tags %}{% plain_flatblock "tmpl_block" %}') 239 | result = tpl.render(template.Context({'variable': 'value'})) 240 | self.assertEqual('{{ variable }}', result) 241 | 242 | def testHeaderEvaluation(self): 243 | """ 244 | Also the header should receive the context and get evaluated. 245 | """ 246 | FlatBlock.objects.create(slug='tmpl_block', 247 | header='{{ header_variable }}', 248 | content='{{ variable }}' 249 | ) 250 | tpl = template.Template( 251 | '{% load flatblock_tags %}{% flatblock "tmpl_block" evaluated %}') 252 | result = tpl.render(template.Context({ 253 | 'variable': 'value', 254 | 'header_variable': 'header-value' 255 | })) 256 | self.assertTrue('header-value' in result) 257 | 258 | 259 | class AutoCreationTest(TestCase): 260 | """ Test case for block autcreation """ 261 | 262 | def testMissingStaticBlock(self): 263 | """Tests if a missing block with hardcoded name will be auto-created""" 264 | expected = """
265 | 266 |
foo
267 |
""" 268 | settings.AUTOCREATE_STATIC_BLOCKS = True 269 | tpl = template.Template( 270 | '{% load flatblock_tags %}{% flatblock "foo" %}') 271 | self.assertEqual(expected, tpl.render(template.Context({})).strip()) 272 | self.assertEqual(FlatBlock.objects.count(), 1) 273 | self.assertEqual(expected, tpl.render(template.Context({})).strip()) 274 | self.assertEqual(FlatBlock.objects.count(), 1) 275 | 276 | def testNotAutocreatedMissingStaticBlock(self): 277 | """ 278 | Tests if a missing block with hardcoded name won't be auto-created if 279 | feature is disabled""" 280 | expected = "" 281 | settings.AUTOCREATE_STATIC_BLOCKS = False 282 | tpl = template.Template( 283 | '{% load flatblock_tags %}{% flatblock "block" %}') 284 | self.assertEqual(expected, tpl.render(template.Context({})).strip()) 285 | self.assertEqual(FlatBlock.objects.filter(slug='block').count(), 0) 286 | 287 | def testMissingVariableBlock(self): 288 | """ 289 | Tests if a missing block with variable name will simply return an empty 290 | string 291 | """ 292 | settings.AUTOCREATE_STATIC_BLOCKS = True 293 | tpl = template.Template( 294 | '{% load flatblock_tags %}{% flatblock name %}') 295 | self.assertEqual('', 296 | tpl.render(template.Context({'name': 'foo'})).strip()) 297 | -------------------------------------------------------------------------------- /flatblocks/urls.py: -------------------------------------------------------------------------------- 1 | try: 2 | from django.conf.urls import patterns, url 3 | except ImportError: # Django < 1.4 4 | from django.conf.urls.defaults import patterns, url 5 | from django.contrib.admin.views.decorators import staff_member_required 6 | from flatblocks.views import edit 7 | 8 | urlpatterns = patterns('', 9 | url('^edit/(?P\d+)/$', staff_member_required(edit), 10 | name='flatblocks-edit') 11 | ) 12 | -------------------------------------------------------------------------------- /flatblocks/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render_to_response, get_object_or_404 2 | from django.template import RequestContext 3 | from django.http import HttpResponseRedirect, HttpResponseForbidden 4 | from django.http import HttpResponse 5 | from django.utils.translation import ugettext as _ 6 | 7 | from flatblocks.models import FlatBlock 8 | from flatblocks.forms import FlatBlockForm 9 | 10 | 11 | def edit(request, pk, modelform_class=FlatBlockForm, permission_check=None, 12 | template_name='flatblocks/edit.html', success_url=None): 13 | """ 14 | This view provides a simple editor implementation for flatblocks. 15 | 16 | There are two customization hooks. First of all you can specify your own 17 | ModelForm class by passing it to the view using the ``modelform_class`` 18 | keyword-argument. 19 | 20 | The other entry point helps you check permissions: Pass a simple function 21 | via the ``permission_check`` keyword-argument in order to check 22 | permissions on the flatblock-level:: 23 | 24 | def my_perm_check(request, flatblock): 25 | return request.user.is_staff 26 | 27 | # ... 28 | 29 | urlpatterns('flatblocks.views', 30 | url('flatblocks/(?P\d+)/edit/$', 'edit', 31 | kwargs={'permission_check': my_perm_check}), 32 | ) 33 | 34 | The contract here is pretty simple: If the function returns False, the 35 | view will return HttpResponseForbidden. Otherwise it will pass. So if you 36 | want to do some fancy redirects if the permissions are wrong, return your 37 | own HttpResponse-object/-subclass. 38 | 39 | If everything is alright with the permissions, simply return True. 40 | """ 41 | flatblock = get_object_or_404(FlatBlock, pk=pk) 42 | if permission_check is not None: 43 | permcheck_result = permission_check(request, flatblock) 44 | if permcheck_result is False: 45 | return HttpResponseForbidden( 46 | _('You are not allowed to edit this flatblock')) 47 | if isinstance(permcheck_result, HttpResponse): 48 | return permcheck_result 49 | 50 | session_key = 'flatblock.origin.%d' % (int(pk), ) 51 | if request.method == 'POST': 52 | origin = request.session.get(session_key, 53 | request.META.get('HTTP_REFERER', '/')) 54 | form = modelform_class(request.POST, instance=flatblock) 55 | if form.is_valid(): 56 | instance = form.save(commit=False) 57 | instance.slug = flatblock.slug 58 | instance.save() 59 | del request.session[session_key] 60 | redirect_to = success_url and success_url or origin 61 | return HttpResponseRedirect(redirect_to) 62 | else: 63 | origin = request.META.get('HTTP_REFERER', '/') 64 | # Don't set origin to this view's url no matter what 65 | origin = origin == request.get_full_path() and \ 66 | request.session.get(session_key, '/') or origin 67 | form = modelform_class(instance=flatblock) 68 | request.session[session_key] = origin 69 | return render_to_response(template_name, { 70 | 'form': form, 71 | 'origin': origin, 72 | 'flatblock': flatblock, 73 | }, context_instance=RequestContext(request)) 74 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | try: 2 | from setuptools import setup 3 | kws = {'install_requires': ['Django']} 4 | except: 5 | from distutils.core import setup 6 | kws = {} 7 | 8 | 9 | setup( 10 | name='django-flatblocks', 11 | version='0.8', 12 | description='django-flatblocks acts like django.contrib.flatpages but ' 13 | 'for parts of a page; like an editable help box you want ' 14 | 'show alongside the main content.', 15 | long_description=open('README.rst').read(), 16 | keywords='django apps', 17 | license='New BSD License', 18 | author='Horst Gutmann', 19 | author_email='zerok@zerokspot.com', 20 | url='http://github.com/zerok/django-flatblocks/', 21 | classifiers=[ 22 | 'Development Status :: 3 - Alpha', 23 | 'Environment :: Plugins', 24 | 'Environment :: Web Environment', 25 | 'Framework :: Django', 26 | 'Intended Audience :: Developers', 27 | 'License :: OSI Approved :: BSD License', 28 | 'Operating System :: OS Independent', 29 | 'Programming Language :: Python', 30 | 'Programming Language :: Python :: 2', 31 | 'Programming Language :: Python :: 2.6', 32 | 'Programming Language :: Python :: 2.7', 33 | 'Programming Language :: Python :: 3', 34 | 'Programming Language :: Python :: 3.3', 35 | 'Topic :: Software Development :: Libraries :: Python Modules', 36 | 'Topic :: Internet :: WWW/HTTP', 37 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 38 | ], 39 | packages=['flatblocks', 'flatblocks.management', 40 | 'flatblocks.management.commands', 'flatblocks.migrations', 41 | 'flatblocks.templatetags'], 42 | package_data={ 43 | 'flatblocks': [ 44 | 'templates/flatblocks/*.html', 45 | 'locale/*/*/*.mo', 46 | 'locale/*/*/*.po', 47 | ] 48 | }, 49 | **kws 50 | ) 51 | -------------------------------------------------------------------------------- /test_project/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zerok/django-flatblocks/a5b93d3b61d525b02099bd4fce6a3c38ec41c02e/test_project/__init__.py -------------------------------------------------------------------------------- /test_project/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from django.core.management import execute_manager 3 | try: 4 | from . import settings # Assumed to be in the same directory. 5 | except ImportError: 6 | import sys 7 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) 8 | sys.exit(1) 9 | 10 | if __name__ == "__main__": 11 | execute_manager(settings) 12 | -------------------------------------------------------------------------------- /test_project/runtests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | from django.core.management import execute_manager 4 | try: 5 | from . import settings # Assumed to be in the same directory. 6 | except ImportError: 7 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) 8 | sys.exit(1) 9 | 10 | sys.argv.insert(1, 'test') 11 | 12 | if len(sys.argv) == 2: 13 | sys.argv.append('flatblocks') 14 | 15 | if __name__ == "__main__": 16 | execute_manager(settings) 17 | 18 | -------------------------------------------------------------------------------- /test_project/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | from django.conf.global_settings import * 3 | 4 | PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__)) 5 | #sys.path.insert(0, os.path.dirname(PROJECT_ROOT)) 6 | 7 | DEBUG = True 8 | TEMPLATE_DEBUG = True 9 | DATABASES = { 10 | 'default': { 11 | 'ENGINE': 'django.db.backends.sqlite3', 12 | 'NAME': os.path.join(PROJECT_ROOT, 'test.db') 13 | } 14 | } 15 | INSTALLED_APPS = ( 16 | 'django.contrib.auth', 17 | 'django.contrib.admin', 18 | 'django.contrib.contenttypes', 19 | 'django.contrib.sessions', 20 | 'flatblocks', 21 | ) 22 | TEMPLATE_CONTEXT_PROCESSORS = TEMPLATE_CONTEXT_PROCESSORS + ( 23 | 'django.core.context_processors.request', 24 | 'django.contrib.auth.context_processors.auth', 25 | ) 26 | LANGUAGE_CODE = "en" 27 | TEMPLATE_DIRS = ( 28 | os.path.join(PROJECT_ROOT, 'templates'), 29 | ) 30 | ROOT_URLCONF = 'test_project.urls' 31 | SECRET_KEY = "12345" 32 | -------------------------------------------------------------------------------- /test_project/templates/index.html: -------------------------------------------------------------------------------- 1 | {% load flatblock_tags %} 2 | 3 | 4 | 5 | Test page for flatblock rendering 6 | 7 | 8 | {% flatblock 'test1' 0 %} 9 | {% flatblock 'test2' 10 %} 10 | {% flatblock 'test3' None %} 11 | 12 | 13 | -------------------------------------------------------------------------------- /test_project/urls.py: -------------------------------------------------------------------------------- 1 | try: 2 | from django.conf.urls import patterns, url, include 3 | except ImportError: # Django < 1.4 4 | from django.conf.urls.defaults import patterns, url, include 5 | from django.contrib.admin.views.decorators import staff_member_required 6 | from django.contrib import admin 7 | from . import views 8 | 9 | admin.autodiscover() 10 | 11 | urlpatterns = patterns('', 12 | url('^flatblocks/', include("flatblocks.urls")), 13 | url('^admin/', include(admin.site.urls)), 14 | url('^/?', views.index), 15 | ) 16 | -------------------------------------------------------------------------------- /test_project/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render_to_response 2 | from django.template import RequestContext 3 | 4 | 5 | def index(request): 6 | return render_to_response('index.html', context_instance=RequestContext(request)) 7 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist=py26-dj14,py26-dj15,py27-dj14,py27-dj15,py27-dj16,py32-dj15,py32-dj16,py33-dj15,py33-dj16 3 | 4 | [testenv] 5 | commands=django-admin.py test flatblocks --settings=test_project.settings 6 | setenv = 7 | PYTHONPATH = {toxinidir} 8 | 9 | [testenv:py26-dj14] 10 | basepython=python2.6 11 | deps= 12 | django == 1.4.9 13 | 14 | [testenv:py26-dj15] 15 | basepython=python2.6 16 | deps= 17 | django == 1.5.5 18 | 19 | [testenv:py27-dj14] 20 | basepython=python2.7 21 | deps= 22 | django == 1.4.9 23 | 24 | [testenv:py27-dj15] 25 | basepython=python2.7 26 | deps= 27 | django == 1.5.5 28 | 29 | [testenv:py27-dj16] 30 | basepython=python2.7 31 | deps= 32 | https://www.djangoproject.com/download/1.6c1/tarball/ 33 | 34 | [testenv:py32-dj15] 35 | basepython=python3.2 36 | deps= 37 | django == 1.5.5 38 | 39 | [testenv:py32-dj16] 40 | basepython=python3.2 41 | deps= 42 | https://www.djangoproject.com/download/1.6c1/tarball/ 43 | 44 | [testenv:py33-dj15] 45 | basepython=python3.3 46 | deps= 47 | django == 1.5.5 48 | 49 | [testenv:py33-dj16] 50 | basepython=python3.3 51 | deps= 52 | https://www.djangoproject.com/download/1.6c1/tarball/ --------------------------------------------------------------------------------