├── tests
├── __init__.py
├── models.py
├── urls.py
├── templates
│ └── index.html
├── settings.py
└── tests.py
├── flatblocks
├── __init__.py
├── management
│ ├── __init__.py
│ └── commands
│ │ ├── __init__.py
│ │ ├── deleteflatblock.py
│ │ ├── createflatblock.py
│ │ └── unassignedflatblocks.py
├── migrations
│ ├── __init__.py
│ ├── 0002_alter_flatblock_id.py
│ └── 0001_initial.py
├── templatetags
│ ├── __init__.py
│ └── flatblocks.py
├── south_migrations
│ ├── __init__.py
│ ├── 0001_initial.py
│ └── 0002_auto__chg_field_flatblock_content__chg_field_flatblock_header.py
├── locale
│ ├── de
│ │ └── LC_MESSAGES
│ │ │ ├── django.mo
│ │ │ └── django.po
│ ├── hu
│ │ └── LC_MESSAGES
│ │ │ ├── django.mo
│ │ │ └── django.po
│ ├── no
│ │ └── LC_MESSAGES
│ │ │ ├── django.mo
│ │ │ └── django.po
│ ├── ru
│ │ └── LC_MESSAGES
│ │ │ ├── django.mo
│ │ │ └── django.po
│ └── django.pot
├── templates
│ └── flatblocks
│ │ ├── edit.html
│ │ └── flatblock.html
├── apps.py
├── forms.py
├── settings.py
├── admin.py
├── urls.py
├── models.py
└── views.py
├── .pre-commit-config.yaml
├── MANIFEST.in
├── .coveragerc
├── .gitignore
├── tox.ini
├── AUTHORS.txt
├── .github
└── workflows
│ ├── release.yml
│ └── test.yml
├── LICENSE.txt
├── setup.py
├── CODE_OF_CONDUCT.md
├── CHANGELOG.rst
└── README.rst
/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/models.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/flatblocks/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/flatblocks/management/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/flatblocks/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos: []
2 |
--------------------------------------------------------------------------------
/flatblocks/templatetags/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/flatblocks/management/commands/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/flatblocks/south_migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.rst
2 | recursive-include flatblocks/locale *
3 | recursive-include flatblocks/templates/flatblocks *.html
4 |
--------------------------------------------------------------------------------
/.coveragerc:
--------------------------------------------------------------------------------
1 | [run]
2 | source = flatblocks
3 | branch = 1
4 |
5 | [report]
6 | omit = *tests*,*migrations*,.tox/*,setup.py,*settings.py
7 |
--------------------------------------------------------------------------------
/flatblocks/locale/de/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazzband/django-flatblocks/master/flatblocks/locale/de/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/flatblocks/locale/hu/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazzband/django-flatblocks/master/flatblocks/locale/hu/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/flatblocks/locale/no/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazzband/django-flatblocks/master/flatblocks/locale/no/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/flatblocks/locale/ru/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazzband/django-flatblocks/master/flatblocks/locale/ru/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/flatblocks/templates/flatblocks/edit.html:
--------------------------------------------------------------------------------
1 |
Edit "{{ flatblock.slug }}"
2 |
3 |
7 |
--------------------------------------------------------------------------------
/flatblocks/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class DjangoFlatblocksAppConfig(AppConfig):
5 | default_auto_field = "django.db.models.BigAutoField"
6 | name = "flatblocks"
7 |
--------------------------------------------------------------------------------
/.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 | .tox
13 | coverage.xml
14 | .coverage
15 |
--------------------------------------------------------------------------------
/flatblocks/forms.py:
--------------------------------------------------------------------------------
1 | from django.forms import ModelForm
2 | from flatblocks.models import FlatBlock
3 |
4 |
5 | class FlatBlockForm(ModelForm):
6 | class Meta:
7 | model = FlatBlock
8 | exclude = ('slug', )
9 |
--------------------------------------------------------------------------------
/flatblocks/settings.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 |
3 | AUTOCREATE_STATIC_BLOCKS = getattr(settings,
4 | 'FLATBLOCKS_AUTOCREATE_STATIC_BLOCKS',
5 | False)
6 |
--------------------------------------------------------------------------------
/flatblocks/templates/flatblocks/flatblock.html:
--------------------------------------------------------------------------------
1 |
2 | {% if flatblock.header %}
3 |
{{ flatblock.header }}
4 | {% endif %}
5 |
{{ flatblock.content|safe }}
6 |
7 |
--------------------------------------------------------------------------------
/tests/urls.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from django.shortcuts import render
3 | from django.urls import path, include
4 |
5 | urlpatterns = [
6 | path('flatblocks/', include("flatblocks.urls")),
7 | path('admin/', admin.site.urls),
8 | path('', render, {'template_name': 'index.html'}),
9 | ]
10 |
--------------------------------------------------------------------------------
/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/urls.py:
--------------------------------------------------------------------------------
1 | from django.contrib.admin.views.decorators import staff_member_required
2 | from django.urls import re_path
3 | from flatblocks.views import edit
4 |
5 | urlpatterns = [
6 | re_path(
7 | r"^edit/(?P\d+)/$",
8 | staff_member_required(edit),
9 | name="flatblocks-edit",
10 | ),
11 | ]
12 |
--------------------------------------------------------------------------------
/tests/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 |
--------------------------------------------------------------------------------
/flatblocks/migrations/0002_alter_flatblock_id.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.11 on 2022-01-08 02:21
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('flatblocks', '0001_initial'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='flatblock',
15 | name='id',
16 | field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/flatblocks/management/commands/deleteflatblock.py:
--------------------------------------------------------------------------------
1 | from django.core.management import BaseCommand, CommandError
2 | from flatblocks.models import FlatBlock
3 |
4 |
5 | class Command(BaseCommand):
6 | help = "Delete a flatblock with the given slug"
7 |
8 | def handle(self, *args, **options):
9 | if len(args) != 1:
10 | raise CommandError("This command requires the slug of the "
11 | "flatblock as its first argument")
12 | slug = args[0]
13 | try:
14 | FlatBlock.objects.get(slug=slug).delete()
15 | except FlatBlock.DoesNotExist:
16 | raise CommandError("The requested flatblock doesn't exist")
17 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist =
3 | py{37,38,39,310}-dj{32,40,main}
4 |
5 | [testenv]
6 | deps =
7 | coverage
8 | dj32: Django>=3.2,<3.3
9 | dj40: Django>=4.0,<4.1
10 | djmain: https://github.com/django/django/archive/main.tar.gz
11 | usedevelop = True
12 | ignore_outcome =
13 | djmain: True
14 | commands =
15 | coverage run {envbindir}/django-admin test -v2
16 | coverage report
17 | coverage xml
18 | setenv =
19 | DJANGO_SETTINGS_MODULE=tests.settings
20 |
21 | [gh-actions]
22 | python =
23 | 3.7: py37
24 | 3.8: py38
25 | 3.9: py39
26 | 3.10: py310
27 |
28 | [gh-actions:env]
29 | DJANGO =
30 | 3.2: dj32
31 | 4.0: dj40
32 | main: djmain
33 |
--------------------------------------------------------------------------------
/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 | * Curtis Maloney (FunkyBob)
10 |
11 | Contributions by
12 |
13 | * Michael Greene
14 | * Stephan Jaekel
15 | * James O'Donnell
16 | * Mikhail Korobov
17 | * Henrik Heimbuerger
18 | * Alexander Ovchinnikov
19 | * Craig Anderson
20 |
--------------------------------------------------------------------------------
/flatblocks/management/commands/createflatblock.py:
--------------------------------------------------------------------------------
1 | from django.core.management import BaseCommand, CommandError
2 | from django.db import IntegrityError
3 | from flatblocks.models import FlatBlock
4 |
5 |
6 | class Command(BaseCommand):
7 | help = "Create a new 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 new "
12 | "flatblock as its first argument")
13 | slug = args[0]
14 | block = FlatBlock(header="[{0}]".format(slug),
15 | content="Empty flatblock",
16 | slug=slug)
17 | try:
18 | block.save()
19 | except IntegrityError:
20 | raise CommandError("A flatblock with this name already exists")
21 |
--------------------------------------------------------------------------------
/flatblocks/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import migrations, models
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ]
11 |
12 | operations = [
13 | migrations.CreateModel(
14 | name='FlatBlock',
15 | fields=[
16 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
17 | ('slug', models.CharField(help_text='A unique name used for reference in the templates', unique=True, max_length=255, verbose_name='Slug')),
18 | ('header', models.CharField(help_text='An optional header for this content', max_length=255, verbose_name='Header', blank=True)),
19 | ('content', models.TextField(verbose_name='Content', blank=True)),
20 | ],
21 | options={
22 | 'verbose_name': 'Flat block',
23 | 'verbose_name_plural': 'Flat blocks',
24 | },
25 | bases=(models.Model,),
26 | ),
27 | ]
28 |
--------------------------------------------------------------------------------
/flatblocks/models.py:
--------------------------------------------------------------------------------
1 |
2 | from django.db import models
3 | from django.utils.translation import gettext_lazy as _
4 |
5 |
6 | class FlatBlock(models.Model):
7 | """
8 | Think of a flatblock as a flatpage but for just part of a site. It's
9 | basically a piece of content with a given name (slug) and an optional
10 | title (header) which you can, for example, use in a sidebar of a website.
11 | """
12 | slug = models.CharField(_('Slug'), max_length=255, unique=True,
13 | help_text=_("A unique name used for reference in the templates")
14 | )
15 | header = models.CharField(_('Header'), blank=True, max_length=255,
16 | help_text=_("An optional header for this content")
17 | )
18 | content = models.TextField(verbose_name=_('Content'), blank=True)
19 |
20 | # Helper attributes used if content should be evaluated in order to
21 | # represent the original content.
22 | raw_content = None
23 | raw_header = None
24 |
25 | def __str__(self):
26 | return self.slug
27 |
28 | class Meta:
29 | verbose_name = _('Flat block')
30 | verbose_name_plural = _('Flat blocks')
31 |
--------------------------------------------------------------------------------
/tests/settings.py:
--------------------------------------------------------------------------------
1 | DATABASES = {
2 | "default": {
3 | "ENGINE": "django.db.backends.sqlite3",
4 | }
5 | }
6 |
7 | INSTALLED_APPS = (
8 | "django.contrib.contenttypes",
9 | "django.contrib.sessions",
10 | "django.contrib.auth",
11 | "django.contrib.admin",
12 | "django.contrib.messages",
13 | "flatblocks",
14 | "tests",
15 | )
16 |
17 | ROOT_URLCONF = "tests.urls"
18 |
19 | MIDDLEWARE = [
20 | "django.contrib.sessions.middleware.SessionMiddleware",
21 | "django.contrib.auth.middleware.AuthenticationMiddleware",
22 | "django.contrib.messages.middleware.MessageMiddleware",
23 | ]
24 |
25 | SECRET_KEY = "super-secret"
26 |
27 | TEMPLATES = [
28 | {
29 | "BACKEND": "django.template.backends.django.DjangoTemplates",
30 | "DIRS": [],
31 | "APP_DIRS": True,
32 | "OPTIONS": {
33 | "debug": True,
34 | "context_processors": [
35 | "django.template.context_processors.request",
36 | "django.contrib.auth.context_processors.auth",
37 | "django.contrib.messages.context_processors.messages",
38 | ],
39 | },
40 | },
41 | ]
42 |
--------------------------------------------------------------------------------
/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/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/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/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.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/south_migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | import datetime
3 |
4 | from django.db import models
5 | from south.db import db
6 | from south.v2 import SchemaMigration
7 |
8 |
9 | class Migration(SchemaMigration):
10 |
11 | def forwards(self, orm):
12 |
13 | # Adding model 'FlatBlock'
14 | db.create_table('flatblocks_flatblock', (
15 | ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
16 | ('slug', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255)),
17 | ('header', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)),
18 | ('content', self.gf('django.db.models.fields.TextField')(null=True, blank=True)),
19 | ))
20 | db.send_create_signal('flatblocks', ['FlatBlock'])
21 |
22 | def backwards(self, orm):
23 |
24 | # Deleting model 'FlatBlock'
25 | db.delete_table('flatblocks_flatblock')
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 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*'
7 |
8 | jobs:
9 | build:
10 | if: github.repository == 'jazzband/django-flatblocks'
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - uses: actions/checkout@v2
15 | with:
16 | fetch-depth: 0
17 |
18 | - name: Set up Python
19 | uses: actions/setup-python@v2
20 | with:
21 | python-version: 3.8
22 |
23 | - name: Get pip cache dir
24 | id: pip-cache
25 | run: |
26 | echo "::set-output name=dir::$(pip cache dir)"
27 |
28 | - name: Cache
29 | uses: actions/cache@v2
30 | with:
31 | path: ${{ steps.pip-cache.outputs.dir }}
32 | key: release-${{ hashFiles('**/setup.py') }}-${{ hashFiles('**/tox.ini') }}
33 | restore-keys: |
34 | release-
35 |
36 | - name: Install dependencies
37 | run: |
38 | python -m pip install -U pip
39 | python -m pip install -U setuptools twine wheel
40 |
41 | - name: Build package
42 | run: |
43 | python setup.py --version
44 | python setup.py sdist --format=gztar bdist_wheel
45 | twine check dist/*
46 |
47 | - name: Upload packages to Jazzband
48 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
49 | uses: pypa/gh-action-pypi-publish@master
50 | with:
51 | user: jazzband
52 | password: ${{ secrets.JAZZBAND_RELEASE_KEY }}
53 | repository_url: https://jazzband.co/projects/django-flatblocks/upload
54 |
--------------------------------------------------------------------------------
/flatblocks/south_migrations/0002_auto__chg_field_flatblock_content__chg_field_flatblock_header.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import datetime
3 |
4 | from django.db import models
5 | from south.db import db
6 | from south.v2 import SchemaMigration
7 |
8 |
9 | class Migration(SchemaMigration):
10 |
11 | def forwards(self, orm):
12 |
13 | # Changing field 'FlatBlock.content'
14 | db.alter_column(u'flatblocks_flatblock', 'content', self.gf('django.db.models.fields.TextField')(default=''))
15 |
16 | # Changing field 'FlatBlock.header'
17 | db.alter_column(u'flatblocks_flatblock', 'header', self.gf('django.db.models.fields.CharField')(default='', max_length=255))
18 |
19 | def backwards(self, orm):
20 |
21 | # Changing field 'FlatBlock.content'
22 | db.alter_column(u'flatblocks_flatblock', 'content', self.gf('django.db.models.fields.TextField')(null=True))
23 |
24 | # Changing field 'FlatBlock.header'
25 | db.alter_column(u'flatblocks_flatblock', 'header', self.gf('django.db.models.fields.CharField')(max_length=255, null=True))
26 |
27 | models = {
28 | u'flatblocks.flatblock': {
29 | 'Meta': {'object_name': 'FlatBlock'},
30 | 'content': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
31 | 'header': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
32 | u'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 |
--------------------------------------------------------------------------------
/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.
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | name: build (Python ${{ matrix.python-version }}, Django ${{ matrix.django-version }})
8 | runs-on: ubuntu-latest
9 | strategy:
10 | fail-fast: false
11 | max-parallel: 5
12 | matrix:
13 | python-version: ['3.7', '3.8', '3.9', '3.10']
14 | django-version: ['3.2', '4.0', 'main']
15 | exclude:
16 | - django-version: '4.0'
17 | python-version: '3.7'
18 | - django-version: 'main'
19 | python-version: '3.7'
20 |
21 | steps:
22 | - uses: actions/checkout@v2
23 |
24 | - name: Set up Python ${{ matrix.python-version }}
25 | uses: actions/setup-python@v2
26 | with:
27 | python-version: ${{ matrix.python-version }}
28 |
29 | - name: Get pip cache dir
30 | id: pip-cache
31 | run: |
32 | echo "::set-output name=dir::$(pip cache dir)"
33 |
34 | - name: Cache
35 | uses: actions/cache@v2
36 | with:
37 | path: ${{ steps.pip-cache.outputs.dir }}
38 | key:
39 | ${{ matrix.python-version }}-v1-${{ hashFiles('**/setup.py') }}-${{ hashFiles('**/tox.ini') }}
40 | restore-keys: |
41 | ${{ matrix.python-version }}-v1-
42 |
43 | - name: Install dependencies
44 | run: |
45 | python -m pip install --upgrade pip
46 | python -m pip install --upgrade tox tox-gh-actions
47 |
48 | - name: Tox tests
49 | run: |
50 | tox -v
51 | env:
52 | DJANGO: ${{ matrix.django-version }}
53 |
54 | - name: Upload coverage
55 | uses: codecov/codecov-action@v1
56 | with:
57 | name: Python ${{ matrix.python-version }}
58 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import io
2 |
3 | from setuptools import setup, find_packages
4 |
5 |
6 | setup(
7 | name='django-flatblocks',
8 | version='1.0.0',
9 | description='django-flatblocks acts like django.contrib.flatpages but '
10 | 'for parts of a page; like an editable help box you want '
11 | 'show alongside the main content.',
12 | long_description=io.open('README.rst', encoding='utf-8').read(),
13 | keywords='django apps',
14 | license='New BSD License',
15 | author='Horst Gutmann, Curtis Maloney',
16 | author_email='curtis@tinbrain.net',
17 | url='http://github.com/funkybob/django-flatblocks/',
18 | classifiers=[
19 | 'Development Status :: 5 - Production/Stable',
20 | 'Environment :: Plugins',
21 | 'Environment :: Web Environment',
22 | 'Framework :: Django',
23 | 'Framework :: Django :: 3.2',
24 | 'Framework :: Django :: 4.0',
25 | 'Intended Audience :: Developers',
26 | 'License :: OSI Approved :: BSD License',
27 | 'Operating System :: OS Independent',
28 | 'Programming Language :: Python',
29 | 'Programming Language :: Python :: 3',
30 | 'Programming Language :: Python :: 3.7',
31 | 'Programming Language :: Python :: 3.8',
32 | 'Programming Language :: Python :: 3.9',
33 | 'Programming Language :: Python :: 3.10',
34 | 'Programming Language :: Python :: Implementation :: PyPy',
35 | 'Topic :: Software Development :: Libraries :: Python Modules',
36 | 'Topic :: Internet :: WWW/HTTP',
37 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
38 | ],
39 | packages=find_packages(exclude=['tests']),
40 | package_data={
41 | 'flatblocks': [
42 | 'templates/flatblocks/*.html',
43 | 'locale/*/*/*.mo',
44 | 'locale/*/*/*.po',
45 | ]
46 | },
47 | zip_safe=False,
48 | requires = [
49 | 'Django (>=2.2)',
50 | ],
51 | )
52 |
--------------------------------------------------------------------------------
/flatblocks/management/commands/unassignedflatblocks.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from django.conf import settings
4 | from django.core.management.base import BaseCommand
5 | from django.template.loader import get_template
6 | from flatblocks.models import FlatBlock
7 | from flatblocks.templatetags.flatblock_tags import FlatBlockNode
8 |
9 |
10 | class Command(BaseCommand):
11 | help = "List unassigned flatblocks in the templates"
12 |
13 | def handle(self, *args, **options):
14 | save_nodes = (len(args) and args[0] == 'create')
15 | flatblock_nodes = set()
16 | print_nodes = []
17 |
18 | # get list of templates
19 | for templ_dir in settings.TEMPLATE_DIRS:
20 | for path, dirlist, fnlist in os.walk(templ_dir):
21 | for fn in fnlist:
22 | try:
23 | t = get_template(os.path.join(path, fn))
24 | flatblock_nodes.update(
25 | flatblock_nodes.update(
26 | node.slug
27 | for node in t.nodelist.get_nodes_by_type(FlatBlockNode)
28 | )
29 | )
30 | except:
31 | # Should log at debug level?
32 | pass
33 |
34 | # check if flatblocks have entry in database
35 | for node in flatblock_nodes:
36 | if not FlatBlock.objects.filter(slug=node).exists():
37 | # if create argument was supplied, save empty nodes
38 | if save_nodes:
39 | FlatBlock.objects.create(header="[{0}]".format(node),
40 | content="Generated flatblock",
41 | slug=node)
42 | print_nodes.append(node)
43 |
44 | if print_nodes:
45 | if save_nodes:
46 | self.stdout.write("Following nodes were created:")
47 | self.stdout.write("\n".join(print_nodes))
48 | else:
49 | self.stdout.write("All FlatBlock items are in database")
50 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Code of Conduct
2 |
3 | As contributors and maintainers of the Jazzband projects, and in the interest of
4 | fostering an open and welcoming community, we pledge to respect all people who
5 | contribute through reporting issues, posting feature requests, updating documentation,
6 | submitting pull requests or patches, and other activities.
7 |
8 | We are committed to making participation in the Jazzband a harassment-free experience
9 | for everyone, regardless of the level of experience, gender, gender identity and
10 | expression, sexual orientation, disability, personal appearance, body size, race,
11 | ethnicity, age, religion, or nationality.
12 |
13 | Examples of unacceptable behavior by participants include:
14 |
15 | - The use of sexualized language or imagery
16 | - Personal attacks
17 | - Trolling or insulting/derogatory comments
18 | - Public or private harassment
19 | - Publishing other's private information, such as physical or electronic addresses,
20 | without explicit permission
21 | - Other unethical or unprofessional conduct
22 |
23 | The Jazzband roadies have the right and responsibility to remove, edit, or reject
24 | comments, commits, code, wiki edits, issues, and other contributions that are not
25 | aligned to this Code of Conduct, or to ban temporarily or permanently any contributor
26 | for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
27 |
28 | By adopting this Code of Conduct, the roadies commit themselves to fairly and
29 | consistently applying these principles to every aspect of managing the jazzband
30 | projects. Roadies who do not follow or enforce the Code of Conduct may be permanently
31 | removed from the Jazzband roadies.
32 |
33 | This code of conduct applies both within project spaces and in public spaces when an
34 | individual is representing the project or its community.
35 |
36 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by
37 | contacting the roadies at `roadies@jazzband.co`. All complaints will be reviewed and
38 | investigated and will result in a response that is deemed necessary and appropriate to
39 | the circumstances. Roadies are obligated to maintain confidentiality with regard to the
40 | reporter of an incident.
41 |
42 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version
43 | 1.3.0, available at [https://contributor-covenant.org/version/1/3/0/][version]
44 |
45 | [homepage]: https://contributor-covenant.org
46 | [version]: https://contributor-covenant.org/version/1/3/0/
47 |
--------------------------------------------------------------------------------
/CHANGELOG.rst:
--------------------------------------------------------------------------------
1 | Unreleased:
2 | * Add support for Django 3.2 and 4.0.
3 | * Add support for Python 3.10.
4 | * Remove support for Django 2.0, 2.2, 3.0, and 3.1.
5 | * Remove support for Python 3.6.
6 | * Change default primary key to use BigAutoField.
7 |
8 | 1.0.0:
9 | * Add support for Django 2.2, 3.0, and 3.1.
10 | * Add support for Python 3.6, 3.7, 3.8 and 3.9.
11 | * Remove support for Django 1.7, 1.8, 1.9, 1.10, and 1.11.
12 | * Remove support for Python 3.4 and 3.5.
13 | * Move CI to GitHub Actions: https://github.com/jazzband/django-flatblocks/actions
14 |
15 | 0.9.4:
16 | * Drop Python 3.3 support.
17 | * Add support for Django 1.11.
18 |
19 | 0.9.3:
20 | * Fixed Django 1.10 compatibility
21 |
22 | 0.9.2:
23 | * Fixed reading of README in setup.py
24 | * Dropped Django 1.4 testing
25 | * Tidied code with flake8 and isort
26 | * Fix support for Django 1.7+
27 | * Fix packaging to exclude tests module
28 |
29 | 0.9.1:
30 | * Dropped testing of Django 1.5 and 1.6
31 | * Added migrations [Thanks Sergey Fedoseev]
32 |
33 | 0.9:
34 | NOTE: Major tag syntax changes!
35 |
36 | * Modernised to use simple_tag and standard kwarg syntax.
37 | * Removed caching - use {% cache %} tag instead
38 |
39 | 0.8:
40 | * Python 3 & Django 1.6 support
41 |
42 | 0.7:
43 | * Support for evaluated blocks offering access to context variables
44 |
45 | 0.6:
46 | * South support
47 | * Installation and upgrade instructions
48 |
49 | Note: This is primarily a transitional release to get South in here and
50 | open this project up for some database changes in the future.
51 |
52 | 0.5.1
53 | * Removed rendering of the content attribute from the admin list by Michael Fladischer
54 | * PyBabel compatibility by Michael Fladischer
55 | * Fixed caching issue with memcache backend
56 |
57 | 0.5
58 | * Hungarian translation by Török Gábor
59 | * Method added to demo edit form (#5) by Bill Evans
60 |
61 | 0.4
62 | * FlatBlock autocreation by Mikhail Korobov (can be enabled/disabled
63 | with FLATBLOCKS\_AUTOCREATE\_STATIC\_BLOCKS setting)
64 | * Various fixes by Mikhail Korobov
65 | * Fix by Henrik Heimbuerger for the manifest
66 |
67 | 0.3.5
68 | * Russian translation by Mikhail Korobov
69 |
70 | 0.3.4
71 | * Norwegian translation by Eivind Uggedal
72 |
73 | 0.3.3
74 | * FlatBlock.save should also accept optional kwargs.
75 |
76 | 0.3.2
77 | * All settings are now in the flatblocks.settings module
78 |
79 | 0.3.1
80 | * Fixes a bug with FlatBlock.save() failing to reset the cache
81 | * Buildout integration for easier testing
82 | * Example urls.py and flatblocks/edit.html-template
83 |
84 | 0.3
85 | * createflatblock and deleteflatblock commands
86 | * On saving a flatblock its cache will be cleared
87 | * As last argument of the template tag you can now also specify a template
88 | name.
89 | 0.2
90 | * Translatable
91 | * ``flatblocks.views.edit`` view for editing flatblocks
92 | 0.1
93 | Initial release
94 |
--------------------------------------------------------------------------------
/flatblocks/views.py:
--------------------------------------------------------------------------------
1 | from django.http import HttpResponse, HttpResponseForbidden
2 | from django.shortcuts import get_object_or_404, redirect, render
3 | from django.utils.translation import gettext as _
4 | from flatblocks.forms import FlatBlockForm
5 | from flatblocks.models import FlatBlock
6 |
7 |
8 | def edit(request, pk, modelform_class=FlatBlockForm, permission_check=None,
9 | template_name='flatblocks/edit.html', success_url=None):
10 | """
11 | This view provides a simple editor implementation for flatblocks.
12 |
13 | There are two customization hooks. First of all you can specify your own
14 | ModelForm class by passing it to the view using the ``modelform_class``
15 | keyword-argument.
16 |
17 | The other entry point helps you check permissions: Pass a simple function
18 | via the ``permission_check`` keyword-argument in order to check
19 | permissions on the flatblock-level::
20 |
21 | def my_perm_check(request, flatblock):
22 | return request.user.is_staff
23 |
24 | # ...
25 |
26 | urlpatterns('flatblocks.views',
27 | url('flatblocks/(?P\d+)/edit/$', 'edit',
28 | kwargs={'permission_check': my_perm_check}),
29 | )
30 |
31 | The contract here is pretty simple: If the function returns False, the
32 | view will return HttpResponseForbidden. Otherwise it will pass. So if you
33 | want to do some fancy redirects if the permissions are wrong, return your
34 | own HttpResponse-object/-subclass.
35 |
36 | If everything is alright with the permissions, simply return True.
37 | """
38 | flatblock = get_object_or_404(FlatBlock, pk=pk)
39 | if permission_check:
40 | permcheck_result = permission_check(request, flatblock)
41 | if permcheck_result is False:
42 | return HttpResponseForbidden(
43 | _('You are not allowed to edit this flatblock'))
44 | if isinstance(permcheck_result, HttpResponse):
45 | return permcheck_result
46 |
47 | session_key = 'flatblock.origin.%d' % (int(pk), )
48 | if request.method == 'POST':
49 | origin = request.session.get(session_key,
50 | request.META.get('HTTP_REFERER', '/'))
51 | form = modelform_class(request.POST, instance=flatblock)
52 | if form.is_valid():
53 | instance = form.save(commit=False)
54 | instance.slug = flatblock.slug
55 | instance.save()
56 | del request.session[session_key]
57 | redirect_to = success_url if success_url else origin
58 | return redirect(redirect_to)
59 | else:
60 | origin = request.META.get('HTTP_REFERER', '/')
61 | # Don't set origin to this view's url no matter what
62 | origin = (
63 | request.session.get(session_key, '/')
64 | if origin == request.get_full_path()
65 | else origin
66 | )
67 | form = modelform_class(instance=flatblock)
68 | request.session[session_key] = origin
69 | return render(request, template_name, {
70 | 'form': form,
71 | 'origin': origin,
72 | 'flatblock': flatblock,
73 | })
74 |
--------------------------------------------------------------------------------
/flatblocks/templatetags/flatblocks.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 requires one argument, and accepts several extra keywords:
7 |
8 | {% flatblock {slug} [evaluated={bool}] [using={template}] %}
9 |
10 | slug::
11 | The slug/key of the text (for example 'contact_help').
12 |
13 | evaluated::
14 | If set to True, the content and header of the FlatBlock will be
15 | rendered as a Template before being passed to the template.
16 |
17 | This allows you to embed template tags into your FlatBlocks.
18 |
19 | The original values for the content and header will be saved to
20 | raw_content and raw_header respectively.
21 |
22 | using::
23 | The template to render the FlatBlock with. If not supplied, will
24 | default to "flatblocks/flatblock.html".
25 |
26 | If set to False, will not be used, and the output of the tag will be
27 | the ``content`` property of the FlatBlock.
28 |
29 | Example::
30 |
31 | {% load flatblock_tags %}
32 |
33 | ...
34 |
35 | {% flatblock 'contact_help' %}
36 | {% flatblock name_in_variable %}
37 |
38 | The 'flatblock' template tag acts like an inclusiontag and operates on the
39 | ``flatblock/flatblock.html`` template file, which gets (besides the global
40 | context) also the ``flatblock`` variable passed.
41 |
42 | Compared to the original implementation this includes not only the block's
43 | content but the whole object inclusing title, slug and id. This way you
44 | can easily for example offer administrative operations (like editing)
45 | within that template.
46 |
47 | """
48 | from __future__ import absolute_import
49 |
50 | import logging
51 |
52 | from django import VERSION, template
53 | from django.template.loader import render_to_string
54 | from flatblocks import settings
55 |
56 | if VERSION >= (1, 7):
57 | from django.apps import apps
58 | get_model = apps.get_model
59 | else:
60 | from django.db import models
61 | get_model = models.get_model
62 |
63 |
64 | register = template.Library()
65 | logger = logging.getLogger(__name__)
66 |
67 | FlatBlock = get_model('flatblocks', 'flatblock')
68 |
69 |
70 | @register.simple_tag(takes_context=True)
71 | def flatblock(context, slug, evaluated=False,
72 | using='flatblocks/flatblock.html'):
73 |
74 | if not settings.AUTOCREATE_STATIC_BLOCKS:
75 | try:
76 | flatblock = FlatBlock.objects.get(slug=slug)
77 | except FlatBlock.DoesNotExist:
78 | return ''
79 | else:
80 | flatblock, _ = FlatBlock.objects.get_or_create(slug=slug,
81 | defaults={'content': slug}
82 | )
83 |
84 | if evaluated:
85 | flatblock.raw_content = flatblock.content
86 | flatblock.raw_header = flatblock.header
87 | flatblock.content = template.Template(flatblock.content).render(context)
88 | flatblock.header = template.Template(flatblock.header).render(context)
89 |
90 | if using:
91 | ctx = context.flatten()
92 | ctx['flatblock'] = flatblock
93 | result = render_to_string(using, ctx)
94 | else:
95 | result = flatblock.content
96 |
97 | return result
98 |
99 |
100 | @register.simple_tag(takes_context=True)
101 | def plain_flatblock(context, slug, evaluated=False):
102 | return flatblock(context, slug, evaluated=evaluated, using=None)
103 |
--------------------------------------------------------------------------------
/tests/tests.py:
--------------------------------------------------------------------------------
1 | from django import template
2 | from django.test import TestCase
3 | from django.contrib.auth.models import User
4 | from django import db
5 |
6 | from flatblocks.models import FlatBlock
7 | from flatblocks import settings
8 |
9 |
10 | class BasicTests(TestCase):
11 | def setUp(self):
12 | self.testblock = FlatBlock.objects.create(
13 | slug="block", header="HEADER", content="CONTENT"
14 | )
15 | self.admin = User.objects.create_superuser(
16 | "admin", "admin@localhost", "adminpwd"
17 | )
18 |
19 | def testURLConf(self):
20 | resp = self.client.get("/flatblocks/edit/1/", follow=True)
21 | self.assertTemplateUsed(resp, "admin/login.html")
22 | self.client.login(username="admin", password="adminpwd")
23 | resp = self.client.get("/flatblocks/edit/1/")
24 | self.assertTemplateUsed(resp, "flatblocks/edit.html")
25 |
26 | def testSaveForceUpdate(self):
27 | block = FlatBlock(slug="missing")
28 | with self.assertRaises(ValueError):
29 | block.save(force_update=True)
30 |
31 | def testSaveForceInsert(self):
32 | block = FlatBlock.objects.get(slug="block")
33 | with self.assertRaises(db.IntegrityError):
34 | block.save(force_insert=True)
35 |
36 |
37 | class TagTests(TestCase):
38 | def setUp(self):
39 | self.testblock = FlatBlock.objects.create(
40 | slug="block", header="HEADER", content="CONTENT"
41 | )
42 |
43 | def testLoadingTaglib(self):
44 | """Tests if the taglib defined in this app can be loaded"""
45 | tpl = template.Template("{% load flatblocks %}")
46 | tpl.render(template.Context({}))
47 |
48 | def testExistingPlain(self):
49 | tpl = template.Template('{% load flatblocks %}{% plain_flatblock "block" %}')
50 | self.assertEqual("CONTENT", tpl.render(template.Context({})).strip())
51 |
52 | def testExistingTemplate(self):
53 | expected = """
54 |
55 |
HEADER
56 |
57 |
CONTENT
58 |
59 | """
60 | tpl = template.Template('{% load flatblocks %}{% flatblock "block" %}')
61 | self.assertEqual(expected, tpl.render(template.Context({})))
62 |
63 | def testUsingMissingTemplate(self):
64 | tpl = template.Template(
65 | "{% load flatblocks %}"
66 | '{% flatblock "block" using="missing_template.html" %}'
67 | )
68 | exception = template.TemplateDoesNotExist
69 | self.assertRaises(exception, tpl.render, template.Context({}))
70 |
71 | def testBlockAsVariable(self):
72 | tpl = template.Template("{% load flatblocks %}{% flatblock blockvar %}")
73 | tpl.render(template.Context({"blockvar": "block"}))
74 |
75 | def testContentEvaluation(self):
76 | """
77 | If a block is set in the template to be evaluated the actual content of
78 | the block is treated as a Django template and receives the parent
79 | template's context.
80 | """
81 | FlatBlock.objects.create(
82 | slug="tmpl_block", header="HEADER", content="{{ variable }}"
83 | )
84 | tpl = template.Template(
85 | "{% load flatblocks %}" '{% plain_flatblock "tmpl_block" evaluated=True %}'
86 | )
87 | result = tpl.render(template.Context({"variable": "value"}))
88 | self.assertEqual("value", result)
89 |
90 | def testDisabledEvaluation(self):
91 | """
92 | If "evaluated" is not passed, no evaluation should take place.
93 | """
94 | FlatBlock.objects.create(
95 | slug="tmpl_block", header="HEADER", content="{{ variable }}"
96 | )
97 | tpl = template.Template(
98 | '{% load flatblocks %}{% plain_flatblock "tmpl_block" %}'
99 | )
100 | result = tpl.render(template.Context({"variable": "value"}))
101 | self.assertEqual("{{ variable }}", result)
102 |
103 | def testHeaderEvaluation(self):
104 | """
105 | Also the header should receive the context and get evaluated.
106 | """
107 | FlatBlock.objects.create(
108 | slug="tmpl_block", header="{{ header_variable }}", content="{{ variable }}"
109 | )
110 | tpl = template.Template(
111 | '{% load flatblocks %}{% flatblock "tmpl_block" evaluated=True %}'
112 | )
113 | result = tpl.render(
114 | template.Context({"variable": "value", "header_variable": "header-value"})
115 | )
116 | self.assertTrue("header-value" in result)
117 |
118 |
119 | class AutoCreationTest(TestCase):
120 | """ Test case for block autcreation """
121 |
122 | def testMissingStaticBlock(self):
123 | """Tests if a missing block with hardcoded name will be auto-created"""
124 | expected = """"""
128 | tpl = template.Template('{% load flatblocks %}{% flatblock "foo" %}')
129 | settings.AUTOCREATE_STATIC_BLOCKS = True
130 | self.assertEqual(expected, tpl.render(template.Context({})).strip())
131 | self.assertEqual(FlatBlock.objects.count(), 1)
132 | self.assertEqual(expected, tpl.render(template.Context({})).strip())
133 | self.assertEqual(FlatBlock.objects.count(), 1)
134 |
135 | def testNotAutocreatedMissingStaticBlock(self):
136 | """
137 | Tests if a missing block with hardcoded name won't be auto-created if
138 | feature is disabled"""
139 | expected = ""
140 | settings.AUTOCREATE_STATIC_BLOCKS = False
141 | tpl = template.Template('{% load flatblocks %}{% flatblock "block" %}')
142 | self.assertEqual(expected, tpl.render(template.Context({})).strip())
143 | self.assertEqual(FlatBlock.objects.filter(slug="block").count(), 0)
144 |
145 | def _testMissingVariableBlock(self):
146 | """
147 | Tests if a missing block with variable name will simply return an empty
148 | string
149 | """
150 | settings.AUTOCREATE_STATIC_BLOCKS = True
151 | tpl = template.Template("{% load flatblocks %}{% flatblock name %}")
152 | output = tpl.render(template.Context({"name": "foo"})).strip()
153 | self.assertEqual("", output)
154 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | django-flatblocks
2 | =================
3 |
4 | .. image:: https://jazzband.co/static/img/badge.svg
5 | :target: https://jazzband.co/
6 | :alt: Jazzband
7 |
8 | .. image:: https://github.com/jazzband/django-flatblocks/workflows/Test/badge.svg
9 | :target: https://github.com/jazzband/django-flatblocks/actions
10 | :alt: GitHub Actions
11 |
12 | .. image:: https://codecov.io/gh/jazzband/django-flatblocks/branch/master/graph/badge.svg
13 | :target: https://codecov.io/gh/jazzband/django-flatblocks
14 |
15 | django-flatblocks is a simple application for handling small text-blocks on
16 | websites. Think about it like ``django.contrib.flatpages`` just not for a
17 | whole page but for only parts of it, like an information text describing what
18 | you can do on a site.
19 |
20 | Installation
21 | ------------
22 |
23 | Probably the easiest way to install this application is to first run
24 | ``pip install django-flatblocks``. Once this step is complete add
25 | ``"flatblocks"`` to your ``INSTALLED_APPS`` setting in your ``settings.py``
26 | file and run ``python manage.py syncdb`` to update your database.
27 |
28 |
29 | Upgrading
30 | ---------
31 |
32 | django-flatblocks uses `South`_ for handling data and schema migrations
33 | starting with version 0.6.0, so the South-typical update path applies here.
34 |
35 | If you're upgrading from a 0.5.x version or earlier you will have to migrate
36 | in 3 steps:
37 |
38 | 1. Install south.
39 |
40 | 2. Migrate your database to the first version of flatblocks using South:
41 |
42 | .. code-block:: sh
43 |
44 | ./manage.py migrate flatblocks 0001 --fake
45 |
46 | 3. Then migrate your database to the latest version of flatblocks' database
47 | and data structure:
48 |
49 | .. code-block:: sh
50 |
51 | ./manage.py migrate flatblocks
52 |
53 | Usage
54 | -----
55 |
56 | Once you've created some instances of the ``flatblocks.models.FlatBlock``
57 | model, you can load it it using the ``flatblocks`` templatetag-library:
58 |
59 | .. code-block:: html+django
60 |
61 | {% load flatblocks %}
62 |
63 |
64 |
65 |
66 |
67 |
68 |
76 |
77 |
78 |
79 | This way you can display a text block with the name 'page.info'. If you
80 | have the name of a block in a template variable, leave out the quotes.
81 |
82 | Additionally you can also specify which template should be used to render the
83 | flatblock:
84 |
85 | .. code-block:: django
86 |
87 | {% flatblock "page.info" using="my_template.html" %}
88 |
89 | {% flatblock "page.about" using="my_template.html" %}
90 |
91 | If you want to simply output the value of the ``content`` field of a flatblock
92 | without using any template, you can use either options:
93 |
94 | .. code-block:: django
95 |
96 | {% flatblock "page.info" using=False %}
97 |
98 | or
99 |
100 | .. code-block:: django
101 |
102 | {% plain_flatblock "page.info" %}
103 |
104 | As with the slug of the flatblock also with the template name you have the
105 | choice of using the literal name of the template or pass it to the templatetag
106 | as a variable.
107 |
108 | The content of a flatblock (as well as its header) can also be evaluated as a
109 | full-fledged Django template:
110 |
111 | .. code-block:: django
112 |
113 | {% flatblock "page.info" evaluated=True %}
114 |
115 | This also works with the other parameters like the custom template and with
116 | the ``plain_flatblock`` templatetag:
117 |
118 | .. code-block:: django
119 |
120 | {% flatblock "page.info" evaluated=True using="my_template.html" %}
121 |
122 | {% plain_flatblock "page.about" evaluated=True %}
123 |
124 |
125 | edit-view
126 | ---------
127 |
128 | With ``flatblocks.views.edit`` django-flatblocks offers a simple view to edit
129 | your flatblocks from your frontend. To use it simply include it in your
130 | URLconf and create a ``flatblocks/edit.html`` template.
131 |
132 | By default the view doesn't do any permission checking, so you should decorate
133 | it accordingly in your URLconf:
134 |
135 | .. code-block:: python
136 |
137 | from flatblocks.views import edit
138 | from django.contrib.auth.decorators import login_required
139 |
140 | # ...
141 |
142 | urlpatterns = pattern('',
143 | url(r'^flatblocks/(?P\d+)/edit/$', login_required(edit),
144 | name='flatblocks-edit'),
145 | # ...
146 | )
147 |
148 | The template can operate on following variables:
149 |
150 | * ``form``
151 | * ``flatblock``
152 | * ``origin`` (the URL of the previous page)
153 |
154 | Additionally the view offers some basic customization hooks via these keyword
155 | arguments:
156 |
157 | ``template_name``
158 | Name of the template to be used for rendering this view. By default
159 | ``flatblocks/edit.html`` is used.
160 |
161 | ``success_url``
162 | After successfully editing a flatblock the view will redirect the user to
163 | the URL specified here. By default the view will try to determine the last
164 | visited page before entering the edit-view (which is normally a page where
165 | the flatblock is used) and redirect the user back there.
166 |
167 | ``modelform_class``
168 | If you want to use a customized ModelForm class for flatblocks you can
169 | specify it here.
170 |
171 | ``permission_check``
172 | This argument lets you specify a callback function to do some
173 | flatblock-specific permission checking. Such a function could look like
174 | this:
175 |
176 | .. code-block:: python
177 |
178 | def my_permcheck(request, flatblock):
179 | if request.user.is_staff or flatblock.slug == 'free_for_all':
180 | return True
181 | return HttpResponseRedirect('/')
182 |
183 | With this permission callback set, a user that is not a staff-user is not
184 | allowed to edit this view unless it's the "free_for_all" block. If these
185 | criteria are not met, the user is redirected to the root URL of the page.
186 |
187 | The contract here is pretty simple. The permission callback should return
188 | ``False``, if the user should receive a 403 message when trying to edit
189 | this link. If the function returns an instance of ``HttpResponse`` the
190 | view will proceed from the assumption that your view already did
191 | everything there is to do and return that response-object. Any other
192 | return value tells the view that the permissions are OK for the current
193 | user and that it should proceed.
194 |
195 |
196 | History
197 | -------
198 |
199 | Since this application targets use-cases that are basically applicable to
200 | most web-projects out there, there are tons of solutions similar to this one.
201 | In fact, this app is a fork originally from `django-chunks`_ developed by
202 | Clint Ecker.
203 |
204 | In November 2008 Kevin Fricovsky created the `original fork`_ in order to add
205 | an additional "active"-flag to each chunk. That project was later on `forked
206 | by Peter Baumgardner`_ who removed that flag again and added a "header"-field
207 | in order to directly associate and optional title with each text block.
208 |
209 | This fork aims now to add more features like variable chunks and also
210 | integrate some of the features developed by H. Waara and S. Cranford in
211 | the `django-better-chunks`_ fork (``django.contrib.site``- and i18n-support).
212 |
213 | .. _`original fork`: http://github.com/howiworkdaily/django-flatblock/
214 | .. _`django-chunks`: http://code.google.com/p/django-chunks/
215 | .. _`django-better-chunks`: http://bitbucket.org/hakanw/django-better-chunks/
216 | .. _`forked by Peter Baumgardner`: http://github.com/lincolnloop/django-flatblock/
217 | .. _`south`: http://south.aeracode.org/
218 |
--------------------------------------------------------------------------------