django-siteblocks 1.2.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,87 @@
1
+ Metadata-Version: 2.5
2
+ Name: django-siteblocks
3
+ Version: 1.2.2
4
+ Summary: Reusable application for Django introducing URL-dependent static and dynamic data blocks
5
+ Project-URL: Homepage, https://github.com/idlesign/django-siteblocks
6
+ Project-URL: Documentation, https://django-siteblocks.readthedocs.io/
7
+ Author-email: Igor Starikov <idlesign@yandex.ru>
8
+ License-Expression: BSD-3-Clause
9
+ License-File: LICENSE
10
+ Keywords: Django,blocks
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Environment :: Web Environment
13
+ Classifier: Framework :: Django
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: BSD License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
24
+ Requires-Python: >=3.10
25
+ Description-Content-Type: text/markdown
26
+
27
+ # django-siteblocks
28
+
29
+ <https://github.com/idlesign/django-siteblocks>
30
+
31
+ [![release](https://img.shields.io/pypi/v/django-siteblocks.svg)](https://pypi.python.org/pypi/django-siteblocks)
32
+ [![lic](https://img.shields.io/pypi/l/django-siteblocks.svg)](https://pypi.python.org/pypi/django-siteblocks)
33
+ [![coverage](https://img.shields.io/coveralls/idlesign/django-siteblocks/master.svg)](https://coveralls.io/r/idlesign/django-siteblocks)
34
+
35
+ ## What's that
36
+
37
+ *django-siteblocks is a reusable application for Django to build blocks of static or dynamic data that could be used in templates.*
38
+
39
+ It allows you to describe data that doesn't clearly belong to any application in your project in terms of static or dynamic blocks,
40
+ that could be rendered in certain places on site pages. These blocks are addressed in templates by their aliases.
41
+
42
+ The following siteblock types are supported:
43
+
44
+ ### Static
45
+
46
+ Those are defined using Django Admin contrib and are linked to certain URLs.
47
+
48
+ This allows different siteblock contents on different URLs. Just go to admin and add some blocks.
49
+
50
+ ### Dynamic
51
+
52
+ Those are ordinary Python functions registered as siteblocks returning contents.
53
+
54
+ This allows complex logic to build siteblock contents. Let's make a block with a random quote from Pratchett:
55
+
56
+ ```python
57
+ from random import choice
58
+
59
+ from siteblocks.siteblocksapp import register_dynamic_block
60
+
61
+
62
+ def get_quote(**kwargs):
63
+ quotes = [
64
+ 'Early to rise, early to bed, makes a man healthy, wealthy and dead.',
65
+ 'The duke had a mind that ticked like a clock and, like a clock, it regularly went cuckoo.',
66
+ 'Speak softly and employ a huge man with a crowbar.',
67
+ ]
68
+ return choice(quotes)
69
+
70
+ register_dynamic_block('quote', get_quote)
71
+ ```
72
+
73
+ ## Block rendering
74
+
75
+ Use `siteblocks` tag to render block contents where you need it.
76
+
77
+ ```html
78
+ {% load siteblocks %}
79
+
80
+ <div class="quote">
81
+ {% siteblock "quote" %}
82
+ </div>
83
+ ```
84
+
85
+ ## Documentation
86
+
87
+ <https://django-siteblocks.readthedocs.io/>
@@ -0,0 +1,21 @@
1
+ siteblocks/__init__.py,sha256=QFqUvdnuC8ebmxSfLeY1iXn6kxj_ekIGviPOPfQQFGg,76
2
+ siteblocks/admin.py,sha256=aaE1CHDf_hTXM6wq4qM-NIIy4tRGPG9HRXexf9iblxw,744
3
+ siteblocks/apps.py,sha256=7s4IncuqsRvJsjPfVWP20radBQJ1qOoMxyEZMOriiH4,277
4
+ siteblocks/models.py,sha256=PgSJWPE6Ngix2_VDImPsftwtB6aKwALE-_E5Aobdunc,1589
5
+ siteblocks/settings.py,sha256=JBMnc2kqgmMchrp-TOhQqhTsehbRsxKMdRl6nW8I2rE,152
6
+ siteblocks/siteblocksapp.py,sha256=b_JpSdDETSUDKKpewBv099f97qOOGAiV1X1vnj9-Lao,7597
7
+ siteblocks/locale/en/LC_MESSAGES/django.po,sha256=21aqqDyGDRnJq5EBzerd5g0ouVuMG55lCgNNoDBPjEs,1927
8
+ siteblocks/locale/fr/LC_MESSAGES/django.mo,sha256=orsVIt75ULgsFsw6xZMKUUGQ5cYQMpIZGk0fNV8DjHQ,1771
9
+ siteblocks/locale/fr/LC_MESSAGES/django.po,sha256=FN5t8AjXS3Yb0uxQCHPzXR_S-faVEavREyMvymG6v90,2645
10
+ siteblocks/locale/ru/LC_MESSAGES/django.mo,sha256=gv6Eg-xMHCFU2RAXgSautwYkX7dKIpQfb9aoPNCJIng,3015
11
+ siteblocks/locale/ru/LC_MESSAGES/django.po,sha256=PB0CciHQjiAhAThpdtfxTjHdnP4VpM_NhsWNVqW6G1c,3533
12
+ siteblocks/migrations/0001_initial.py,sha256=ZP1YSNFu0RCatiNEobUO2Fw93SJHWAsHpW7P8Mt7krU,1522
13
+ siteblocks/migrations/0002_auto_20140921_1038.py,sha256=H9mN6sdeiYSHdHyyJMcOzIxbDlzvcZBrDcUoBGCWbJI,868
14
+ siteblocks/migrations/0003_alter_block_access_guest_alter_block_access_loggedin.py,sha256=1QdRvNrx4Na-8g7CaV9whhcncWbQPBQV9ofwPTI3sjU,799
15
+ siteblocks/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ siteblocks/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ siteblocks/templatetags/siteblocks.py,sha256=F-RBs3J-hzMDYxH8vr9fsTAEHI-LC06hCgkG66XgYKM,1691
18
+ django_siteblocks-1.2.2.dist-info/METADATA,sha256=g7lTTyzKtzAJl3O2lrQJyr7HadzEy9XYZjB9jT-uRkQ,3080
19
+ django_siteblocks-1.2.2.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
20
+ django_siteblocks-1.2.2.dist-info/licenses/LICENSE,sha256=TgZMEIuVqPzFCn8S6twEoSSGSgFF2-2oaKWrgULpEOA,1555
21
+ django_siteblocks-1.2.2.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.33.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,27 @@
1
+ Copyright (c) 2010-2026, Igor 'idle sign' Starikov
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification,
5
+ are permitted provided that the following conditions are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright notice,
8
+ this list of conditions and the following disclaimer.
9
+
10
+ 2. Redistributions in binary form must reproduce the above copyright
11
+ notice, this list of conditions and the following disclaimer in the
12
+ documentation and/or other materials provided with the distribution.
13
+
14
+ 3. Neither the name of the django-siteblocks nor the names of its
15
+ contributors may be used to endorse or promote products derived from
16
+ this software without specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
siteblocks/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ VERSION = '1.2.2'
2
+
3
+
4
+ default_app_config = 'siteblocks.apps.SiteblocksConfig'
siteblocks/admin.py ADDED
@@ -0,0 +1,31 @@
1
+ from django.contrib import admin
2
+ from django.utils.translation import gettext_lazy as _
3
+
4
+ from .models import Block
5
+
6
+
7
+ class BlockAdmin(admin.ModelAdmin):
8
+
9
+ list_display = ('alias', 'description', 'url', 'hidden', 'access_loggedin', 'access_guest')
10
+
11
+ search_fields = ['alias', 'url']
12
+
13
+ list_filter = ['hidden']
14
+
15
+ ordering = ['alias']
16
+
17
+ fieldsets = (
18
+ (_('Basic settings'), {
19
+ 'fields': ('alias', 'url', 'contents',)
20
+ }),
21
+ (_('Access settings'), {
22
+ 'classes': ('collapse',),
23
+ 'fields': ('access_loggedin', 'access_guest')
24
+ }),
25
+ (_('Additional settings'), {
26
+ 'fields': ('description', 'hidden')
27
+ }),
28
+ )
29
+
30
+
31
+ admin.site.register(Block, BlockAdmin)
siteblocks/apps.py ADDED
@@ -0,0 +1,10 @@
1
+ from django.apps import AppConfig
2
+ from django.utils.translation import gettext_lazy as _
3
+
4
+
5
+ class SiteblocksConfig(AppConfig):
6
+ """Siteblocks configuration."""
7
+
8
+ name = 'siteblocks'
9
+ verbose_name = _('Site Blocks')
10
+ default_auto_field = 'django.db.models.AutoField'
@@ -0,0 +1,98 @@
1
+ # SOME DESCRIPTIVE TITLE.
2
+ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3
+ # This file is distributed under the same license as the PACKAGE package.
4
+ # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5
+ #
6
+ #, fuzzy
7
+ msgid ""
8
+ msgstr ""
9
+ "Project-Id-Version: PACKAGE VERSION\n"
10
+ "Report-Msgid-Bugs-To: \n"
11
+ "POT-Creation-Date: 2014-09-21 12:39+0700\n"
12
+ "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
+ "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
+ "Language-Team: LANGUAGE <LL@li.org>\n"
15
+ "Language: \n"
16
+ "MIME-Version: 1.0\n"
17
+ "Content-Type: text/plain; charset=UTF-8\n"
18
+ "Content-Transfer-Encoding: 8bit\n"
19
+
20
+ #: admin.py:14
21
+ msgid "Basic settings"
22
+ msgstr ""
23
+
24
+ #: admin.py:17
25
+ msgid "Access settings"
26
+ msgstr ""
27
+
28
+ #: admin.py:21
29
+ msgid "Additional settings"
30
+ msgstr ""
31
+
32
+ #: config.py:9 models.py:19
33
+ msgid "Site Blocks"
34
+ msgstr ""
35
+
36
+ #: models.py:9
37
+ msgid "Alias"
38
+ msgstr ""
39
+
40
+ #: models.py:9
41
+ msgid "Short name to address this block from a template."
42
+ msgstr ""
43
+
44
+ #: models.py:10
45
+ msgid "Description"
46
+ msgstr ""
47
+
48
+ #: models.py:10
49
+ msgid "Short memo for this block."
50
+ msgstr ""
51
+
52
+ #: models.py:11
53
+ msgid "URL"
54
+ msgstr ""
55
+
56
+ #: models.py:11
57
+ msgid ""
58
+ "Page URL this block is related to. Regular expressions supported (e.g.: \"/"
59
+ "news.*\" &#8212; everything under \"/news\").<br />View names are supported: "
60
+ "prepend <b>:</b> to view name <br /><b>Reserved URL alias:</b> \"*\" &#8212; "
61
+ "every page."
62
+ msgstr ""
63
+
64
+ #: models.py:12
65
+ msgid "Contents"
66
+ msgstr ""
67
+
68
+ #: models.py:12
69
+ msgid "Block contents to render in a template."
70
+ msgstr ""
71
+
72
+ #: models.py:13
73
+ msgid "Logged in only"
74
+ msgstr ""
75
+
76
+ #: models.py:13
77
+ msgid "Check it to grant access to this block to authenticated users only."
78
+ msgstr ""
79
+
80
+ #: models.py:14
81
+ msgid "Guests only"
82
+ msgstr ""
83
+
84
+ #: models.py:14
85
+ msgid "Check it to grant access to this block to guests only."
86
+ msgstr ""
87
+
88
+ #: models.py:15
89
+ msgid "Hidden"
90
+ msgstr ""
91
+
92
+ #: models.py:15
93
+ msgid "Whether to show this block when requested."
94
+ msgstr ""
95
+
96
+ #: models.py:18
97
+ msgid "Site Block"
98
+ msgstr ""
@@ -0,0 +1,105 @@
1
+ # SOME DESCRIPTIVE TITLE.
2
+ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3
+ # This file is distributed under the same license as the PACKAGE package.
4
+ #
5
+ # Translators:
6
+ # PatrickHetu <patrick.hetu@gmail.com>, 2014
7
+ msgid ""
8
+ msgstr ""
9
+ "Project-Id-Version: django-siteblocks\n"
10
+ "Report-Msgid-Bugs-To: \n"
11
+ "POT-Creation-Date: 2014-09-21 12:39+0700\n"
12
+ "PO-Revision-Date: 2014-09-21 05:35+0000\n"
13
+ "Last-Translator: Igor Starikov <idlesign@yandex.ru>\n"
14
+ "Language-Team: French (http://www.transifex.com/projects/p/django-siteblocks/"
15
+ "language/fr/)\n"
16
+ "Language: fr\n"
17
+ "MIME-Version: 1.0\n"
18
+ "Content-Type: text/plain; charset=UTF-8\n"
19
+ "Content-Transfer-Encoding: 8bit\n"
20
+ "Plural-Forms: nplurals=2; plural=(n > 1);\n"
21
+
22
+ #: admin.py:14
23
+ msgid "Basic settings"
24
+ msgstr ""
25
+
26
+ #: admin.py:17
27
+ msgid "Access settings"
28
+ msgstr ""
29
+
30
+ #: admin.py:21
31
+ msgid "Additional settings"
32
+ msgstr ""
33
+
34
+ #: config.py:9 models.py:19
35
+ msgid "Site Blocks"
36
+ msgstr "Site Blocks"
37
+
38
+ #: models.py:9
39
+ msgid "Alias"
40
+ msgstr "Jeton"
41
+
42
+ #: models.py:9
43
+ msgid "Short name to address this block from a template."
44
+ msgstr "Nom court du jeton à remplacer dans le gabarit."
45
+
46
+ #: models.py:10
47
+ msgid "Description"
48
+ msgstr "Description"
49
+
50
+ #: models.py:10
51
+ msgid "Short memo for this block."
52
+ msgstr "Description courte pour ce jeton."
53
+
54
+ #: models.py:11
55
+ msgid "URL"
56
+ msgstr "URL"
57
+
58
+ #: models.py:11
59
+ msgid ""
60
+ "Page URL this block is related to. Regular expressions supported (e.g.: \"/"
61
+ "news.*\" &#8212; everything under \"/news\").<br />View names are supported: "
62
+ "prepend <b>:</b> to view name <br /><b>Reserved URL alias:</b> \"*\" &#8212; "
63
+ "every page."
64
+ msgstr ""
65
+ "URL, nom d'une vue ou expressions régulières des pages dans lesquelles le "
66
+ "jeton sera remplacé. <br />Par exemple:<br /> \"/nouvelles.*\" &#8212; "
67
+ "remplacera toutes les pages qui commençant par \"/nouvelles\").<br />Le nom "
68
+ "d'une vue est aussi supporté avec le préfixe <b>:</b><br /><b>Utiliser "
69
+ "l'astérisque </b> \"*\" &#8212; pour indiquer toutes les pages."
70
+
71
+ #: models.py:12
72
+ msgid "Contents"
73
+ msgstr "Contenu"
74
+
75
+ #: models.py:12
76
+ msgid "Block contents to render in a template."
77
+ msgstr "Contenu à inclure dans le gabarit."
78
+
79
+ #: models.py:13
80
+ msgid "Logged in only"
81
+ msgstr ""
82
+
83
+ #: models.py:13
84
+ msgid "Check it to grant access to this block to authenticated users only."
85
+ msgstr ""
86
+
87
+ #: models.py:14
88
+ msgid "Guests only"
89
+ msgstr ""
90
+
91
+ #: models.py:14
92
+ msgid "Check it to grant access to this block to guests only."
93
+ msgstr ""
94
+
95
+ #: models.py:15
96
+ msgid "Hidden"
97
+ msgstr "Caché"
98
+
99
+ #: models.py:15
100
+ msgid "Whether to show this block when requested."
101
+ msgstr "Inclure ou non ce contenu dans le gabarit."
102
+
103
+ #: models.py:18
104
+ msgid "Site Block"
105
+ msgstr "Site Block"
@@ -0,0 +1,108 @@
1
+ # SOME DESCRIPTIVE TITLE.
2
+ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3
+ # This file is distributed under the same license as the PACKAGE package.
4
+ #
5
+ # Translators:
6
+ # Igor Starikov <idlesign@yandex.ru>, 2013-2014
7
+ msgid ""
8
+ msgstr ""
9
+ "Project-Id-Version: django-siteblocks\n"
10
+ "Report-Msgid-Bugs-To: \n"
11
+ "POT-Creation-Date: 2014-09-21 12:39+0700\n"
12
+ "PO-Revision-Date: 2014-09-21 05:36+0000\n"
13
+ "Last-Translator: Igor Starikov <idlesign@yandex.ru>\n"
14
+ "Language-Team: Russian (http://www.transifex.com/projects/p/django-"
15
+ "siteblocks/language/ru/)\n"
16
+ "Language: ru\n"
17
+ "MIME-Version: 1.0\n"
18
+ "Content-Type: text/plain; charset=UTF-8\n"
19
+ "Content-Transfer-Encoding: 8bit\n"
20
+ "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
21
+ "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
22
+
23
+ #: admin.py:14
24
+ msgid "Basic settings"
25
+ msgstr "Основные настройки"
26
+
27
+ #: admin.py:17
28
+ msgid "Access settings"
29
+ msgstr "Настройки доступа"
30
+
31
+ #: admin.py:21
32
+ msgid "Additional settings"
33
+ msgstr "Дополнительные настройки"
34
+
35
+ #: config.py:9 models.py:19
36
+ msgid "Site Blocks"
37
+ msgstr "Блоки сайта"
38
+
39
+ #: models.py:9
40
+ msgid "Alias"
41
+ msgstr "Псевдоним"
42
+
43
+ #: models.py:9
44
+ msgid "Short name to address this block from a template."
45
+ msgstr "Краткое имя блока, для обращения к нему из шаблонов."
46
+
47
+ #: models.py:10
48
+ msgid "Description"
49
+ msgstr "Описание"
50
+
51
+ #: models.py:10
52
+ msgid "Short memo for this block."
53
+ msgstr "Произвольная заметка для блока."
54
+
55
+ #: models.py:11
56
+ msgid "URL"
57
+ msgstr "URL"
58
+
59
+ #: models.py:11
60
+ msgid ""
61
+ "Page URL this block is related to. Regular expressions supported (e.g.: \"/"
62
+ "news.*\" &#8212; everything under \"/news\").<br />View names are supported: "
63
+ "prepend <b>:</b> to view name <br /><b>Reserved URL alias:</b> \"*\" &#8212; "
64
+ "every page."
65
+ msgstr ""
66
+ "URL страницы, для которой актуален блок. Поддерживаются регулярные выражения "
67
+ "(например, \"/news.*\" &#8212; любые страницы ниже \"/news\").<br /"
68
+ ">Поддерживаются обращения к представлениям: добавьте <b>:</b> перед именем "
69
+ "представления<br /><b>Зарезервированный псевдоним URL:</b> \"*\" &#8212; "
70
+ "любая страница."
71
+
72
+ #: models.py:12
73
+ msgid "Contents"
74
+ msgstr "Содержимое"
75
+
76
+ #: models.py:12
77
+ msgid "Block contents to render in a template."
78
+ msgstr "Содержимое блока для вывода на страницах."
79
+
80
+ #: models.py:13
81
+ msgid "Logged in only"
82
+ msgstr "Только авторизовавшиеся"
83
+
84
+ #: models.py:13
85
+ msgid "Check it to grant access to this block to authenticated users only."
86
+ msgstr ""
87
+ "Поставьте галочку, чтобы разрешить доступ к этому блоку только "
88
+ "авторизовавшимся."
89
+
90
+ #: models.py:14
91
+ msgid "Guests only"
92
+ msgstr "Только гости"
93
+
94
+ #: models.py:14
95
+ msgid "Check it to grant access to this block to guests only."
96
+ msgstr "Поставьте галочку, чтобы разрешить доступ к этому блоку только гостям."
97
+
98
+ #: models.py:15
99
+ msgid "Hidden"
100
+ msgstr "Скрыт"
101
+
102
+ #: models.py:15
103
+ msgid "Whether to show this block when requested."
104
+ msgstr "Определяет, можно ли выводить этот блок."
105
+
106
+ #: models.py:18
107
+ msgid "Site Block"
108
+ msgstr "Блок сайта"
@@ -0,0 +1,29 @@
1
+ # -*- coding: utf-8 -*-
2
+ from __future__ import unicode_literals
3
+
4
+ from django.db import models, migrations
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ]
11
+
12
+ operations = [
13
+ migrations.CreateModel(
14
+ name='Block',
15
+ fields=[
16
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
17
+ ('alias', models.CharField(help_text='Short name to address this block from a template.', max_length=80, verbose_name='Alias', db_index=True)),
18
+ ('description', models.CharField(help_text='Short memo for this block.', max_length=100, null=True, verbose_name='Description', blank=True)),
19
+ ('url', models.CharField(help_text='Page URL this block is related to. Regular expressions supported (e.g.: "/news.*" &#8212; everything under "/news").<br />View names are supported: prepend <b>:</b> to view name <br /><b>Reserved URL alias:</b> "*" &#8212; every page.', max_length=200, verbose_name='URL')),
20
+ ('contents', models.TextField(help_text='Block contents to render in a template.', verbose_name='Contents')),
21
+ ('hidden', models.BooleanField(default=False, help_text='Whether to show this block when requested.', db_index=True, verbose_name='Hidden')),
22
+ ],
23
+ options={
24
+ 'verbose_name': 'Site Block',
25
+ 'verbose_name_plural': 'Site Blocks',
26
+ },
27
+ bases=(models.Model,),
28
+ ),
29
+ ]
@@ -0,0 +1,26 @@
1
+ # -*- coding: utf-8 -*-
2
+ from __future__ import unicode_literals
3
+
4
+ from django.db import models, migrations
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ('siteblocks', '0001_initial'),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.AddField(
15
+ model_name='block',
16
+ name='access_guest',
17
+ field=models.BooleanField(default=False, help_text='Check it to grant access to this item to guests only.', db_index=True, verbose_name='Guests only'),
18
+ preserve_default=True,
19
+ ),
20
+ migrations.AddField(
21
+ model_name='block',
22
+ name='access_loggedin',
23
+ field=models.BooleanField(default=False, help_text='Check it to grant access to this item to authenticated users only.', db_index=True, verbose_name='Logged in only'),
24
+ preserve_default=True,
25
+ ),
26
+ ]
@@ -0,0 +1,23 @@
1
+ # Generated by Django 6.1.3 on 2026-11-09 14:56
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('siteblocks', '0002_auto_20140921_1038'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterField(
14
+ model_name='block',
15
+ name='access_guest',
16
+ field=models.BooleanField(db_index=True, default=False, help_text='Check it to grant access to this block to guests only.', verbose_name='Guests only'),
17
+ ),
18
+ migrations.AlterField(
19
+ model_name='block',
20
+ name='access_loggedin',
21
+ field=models.BooleanField(db_index=True, default=False, help_text='Check it to grant access to this block to authenticated users only.', verbose_name='Logged in only'),
22
+ ),
23
+ ]
File without changes
siteblocks/models.py ADDED
@@ -0,0 +1,41 @@
1
+ from django.db import models
2
+ from django.utils.translation import gettext_lazy as _
3
+
4
+
5
+ class Block(models.Model):
6
+
7
+ alias = models.CharField(
8
+ _('Alias'), max_length=80, help_text=_('Short name to address this block from a template.'), db_index=True)
9
+
10
+ description = models.CharField(
11
+ _('Description'), max_length=100, help_text=_('Short memo for this block.'), null=True, blank=True)
12
+
13
+ url = models.CharField(
14
+ _('URL'), max_length=200,
15
+ help_text=_(
16
+ 'Page URL this block is related to. Regular expressions supported (e.g.: "/news.*" &#8212; '
17
+ 'everything under "/news").<br />'
18
+ 'View names are supported: prepend <b>:</b> to view name <br />'
19
+ '<b>Reserved URL alias:</b> "*" &#8212; every page.'))
20
+
21
+ contents = models.TextField(
22
+ _('Contents'), help_text=_('Block contents to render in a template.'))
23
+
24
+ access_loggedin = models.BooleanField(
25
+ _('Logged in only'),
26
+ help_text=_('Check it to grant access to this block to authenticated users only.'),
27
+ db_index=True, default=False)
28
+
29
+ access_guest = models.BooleanField(
30
+ _('Guests only'),
31
+ help_text=_('Check it to grant access to this block to guests only.'), db_index=True, default=False)
32
+
33
+ hidden = models.BooleanField(
34
+ _('Hidden'), help_text=_('Whether to show this block when requested.'), db_index=True, default=False)
35
+
36
+ class Meta:
37
+ verbose_name = _('Site Block')
38
+ verbose_name_plural = _('Site Blocks')
39
+
40
+ def __str__(self):
41
+ return self.alias
siteblocks/settings.py ADDED
@@ -0,0 +1,5 @@
1
+ from django.conf import settings
2
+
3
+
4
+ # Support for blocks dependent on get_language().
5
+ I18N_SUPPORT = getattr(settings, 'SITEBLOCKS_I18N_SUPPORT', False)
@@ -0,0 +1,224 @@
1
+ import re
2
+ from collections import defaultdict
3
+ from random import choice
4
+ from typing import Callable, Dict, List
5
+
6
+ from django.core.cache import cache
7
+ from django.db.models import signals
8
+ from django.template import Context
9
+ from django.urls import resolve, Resolver404
10
+ from django.utils.translation import get_language
11
+
12
+ from .models import Block
13
+ from .settings import I18N_SUPPORT
14
+
15
+ if I18N_SUPPORT:
16
+
17
+ def cache_get_key(block_alias: str) -> str:
18
+ return f'{block_alias}:{get_language()}'
19
+
20
+ else:
21
+ def cache_get_key(block_alias: str) -> str:
22
+ return block_alias
23
+
24
+
25
+ # Contains dynamic blocks.
26
+ _DYNAMIC_BLOCKS = defaultdict(list)
27
+
28
+
29
+ def register_dynamic_block(alias: str, callable: Callable):
30
+ """Registers a callable that produces contents for a dynamic block.
31
+
32
+ Callable on call will get the following kwargs:
33
+
34
+ * `block_alias` - block alias,
35
+ * `block_context` - template context for block
36
+
37
+ Example::
38
+
39
+ # Put the following code somewhere where it'd be triggered as expected. E.g. in app view.py.
40
+
41
+ from random import choice
42
+ # Import the register function.
43
+ from siteblocks.siteblocksapp import register_dynamic_block
44
+
45
+
46
+ # The following function will be used as a block contents producer.
47
+ def get_quote(**kwargs):
48
+ quotes = [ # From Terry Pratchett's Discworld novels.
49
+ 'Ripples of paradox spread out across the sea of causality.',
50
+ 'Early to rise, early to bed, makes a man healthy, wealthy and dead.',
51
+ 'Granny had nothing against fortune-telling provided it was done badly by people with no talent for it.'
52
+ 'Take it from me, there\'s nothing more terrible than someone out to do the world a favour.',
53
+ 'The duke had a mind that ticked like a clock and, like a clock, it regularly went cuckoo.',
54
+ 'Most gods find it hard to walk and think at the same time.',
55
+ 'They didn\'t have to be funny - they were father jokes',
56
+ 'Speak softly and employ a huge man with a crowbar.',
57
+ ]
58
+ return choice(quotes)
59
+
60
+ # And we register our siteblock.
61
+ register_dynamic_block('quote', get_quote)
62
+
63
+ """
64
+ global _DYNAMIC_BLOCKS
65
+ _DYNAMIC_BLOCKS[alias].append(callable)
66
+
67
+
68
+ def get_dynamic_blocks() -> Dict[str, List[Callable]]:
69
+ """Returns a dictionary with currently registered dynamic blocks."""
70
+ return _DYNAMIC_BLOCKS
71
+
72
+
73
+ class SiteBlocks:
74
+
75
+ # siteblocks objects are stored in Django cache for a year (60 * 60 * 24 * 365 = 31536000 sec).
76
+ # Cache is only invalidated on block item change.
77
+ CACHE_TIMEOUT: str = 31536000
78
+ CACHE_KEY: str = 'siteblocks'
79
+
80
+ IDX_GUEST: int = 0
81
+ IDX_AUTH: int = 1
82
+
83
+ def __init__(self):
84
+ self._cache = None
85
+ signals.post_save.connect(self._cache_empty, sender=Block)
86
+ signals.post_delete.connect(self._cache_empty, sender=Block)
87
+
88
+ def _cache_init(self):
89
+ """Initializes local cache from Django cache."""
90
+ cache_ = cache.get(self.CACHE_KEY)
91
+ if cache_ is None:
92
+ cache_ = defaultdict(dict)
93
+ self._cache = cache_
94
+
95
+ def _cache_save(self):
96
+ cache.set(self.CACHE_KEY, self._cache, self.CACHE_TIMEOUT)
97
+
98
+ def _cache_get(self, key: str) -> List[Dict[str, List]]:
99
+ """Returns cache entry parameter value by its name."""
100
+ return self._cache.get(key, False)
101
+
102
+ def _cache_set(self, key: str, value: List[Dict[str, List]]):
103
+ """Replaces entire cache entry parameter data by its name with new data."""
104
+ self._cache[key] = value
105
+
106
+ def _cache_empty(self, **kwargs):
107
+ self._cache = None
108
+ cache.delete(self.CACHE_KEY)
109
+
110
+ def get_contents_static(self, block_alias: str, context: Context) -> str:
111
+ """Returns contents of a static block."""
112
+
113
+ if 'request' not in context:
114
+ # No use in further actions as we won't ever know current URL.
115
+ return ''
116
+
117
+ current_url = context['request'].path
118
+
119
+ # Resolve current view name to support view names as block URLs.
120
+ try:
121
+ resolver_match = resolve(current_url)
122
+ namespace = ''
123
+
124
+ if resolver_match.namespaces:
125
+ # More than one namespace, really? Hmm.
126
+ namespace = resolver_match.namespaces[0]
127
+
128
+ resolved_view_name = f':{namespace}:{resolver_match.url_name}'
129
+
130
+ except Resolver404:
131
+ resolved_view_name = None
132
+
133
+ self._cache_init()
134
+
135
+ cache_entry_name = cache_get_key(block_alias)
136
+ idx_guest = self.IDX_GUEST
137
+ idx_auth = self.IDX_AUTH
138
+
139
+ siteblocks_static = self._cache_get(cache_entry_name)
140
+
141
+ if not siteblocks_static:
142
+
143
+ blocks = Block.objects.filter(
144
+ alias=block_alias,
145
+ hidden=False
146
+ ).only('url', 'contents', 'access_guest', 'access_loggedin')
147
+
148
+ siteblocks_static = [defaultdict(list), defaultdict(list)]
149
+
150
+ for block in blocks:
151
+
152
+ if block.url == '*':
153
+ url_re = block.url
154
+
155
+ elif block.url.startswith(':'):
156
+ url_re = block.url
157
+ # Normalize URL name to include namespace.
158
+ if url_re.count(':') == 1:
159
+ url_re = f':{url_re}'
160
+
161
+ else:
162
+ url_re = re.compile(rf'{block.url}')
163
+
164
+ if block.access_guest:
165
+ siteblocks_static[idx_guest][url_re].append(block.contents)
166
+
167
+ elif block.access_loggedin:
168
+ siteblocks_static[idx_auth][url_re].append(block.contents)
169
+
170
+ else:
171
+ siteblocks_static[idx_guest][url_re].append(block.contents)
172
+ siteblocks_static[idx_auth][url_re].append(block.contents)
173
+
174
+ self._cache_set(cache_entry_name, siteblocks_static)
175
+
176
+ self._cache_save()
177
+
178
+ user = getattr(context['request'], 'user', None)
179
+ is_authenticated = getattr(user, 'is_authenticated', False)
180
+
181
+ lookup_area = siteblocks_static[idx_auth if is_authenticated else idx_guest]
182
+
183
+ static_block_contents = ''
184
+
185
+ if '*' in lookup_area:
186
+ static_block_contents = choice(lookup_area['*'])
187
+
188
+ elif resolved_view_name in lookup_area:
189
+ static_block_contents = choice(lookup_area[resolved_view_name])
190
+
191
+ else:
192
+ for url, contents in lookup_area.items():
193
+ if url.match(current_url):
194
+ static_block_contents = choice(contents)
195
+ break
196
+
197
+ return static_block_contents
198
+
199
+ def get_contents_dynamic(self, block_alias: str, context: Context) -> str:
200
+ """Returns contents of a dynamic block."""
201
+ dynamic_block = get_dynamic_blocks().get(block_alias, [])
202
+
203
+ if not dynamic_block:
204
+ return ''
205
+
206
+ dynamic_block = choice(dynamic_block)
207
+ return dynamic_block(block_alias=block_alias, block_context=context)
208
+
209
+ def get(self, block_alias: str, context: Context) -> str:
210
+ """Main method returning block contents (static or dynamic)."""
211
+ contents = []
212
+
213
+ dynamic_block_contents = self.get_contents_dynamic(block_alias, context)
214
+ if dynamic_block_contents:
215
+ contents.append(dynamic_block_contents)
216
+
217
+ static_block_contents = self.get_contents_static(block_alias, context)
218
+ if static_block_contents:
219
+ contents.append(static_block_contents)
220
+
221
+ if not contents:
222
+ return ''
223
+
224
+ return choice(contents)
File without changes
@@ -0,0 +1,60 @@
1
+ from django import template
2
+ from django.template.base import FilterExpression, Parser, Token
3
+
4
+ from ..siteblocksapp import SiteBlocks
5
+
6
+ register = template.Library()
7
+
8
+ # Utility methods are implemented in SiteBlocks class
9
+ siteblocks = SiteBlocks()
10
+
11
+
12
+ @register.tag
13
+ def siteblock(parser: Parser, token: Token):
14
+ """Two notation types are acceptable:
15
+
16
+ 1. Two arguments:
17
+ {% siteblock "myblock" %}
18
+ Used to render "myblock" site block.
19
+
20
+ 2. Four arguments:
21
+ {% siteblock "myblock" as myvar %}
22
+ Used to put "myblock" site block into "myvar" template variable.
23
+
24
+ """
25
+ tokens = token.split_contents()
26
+ tokens_num = len(tokens)
27
+
28
+ if tokens_num not in (2, 4):
29
+ raise template.TemplateSyntaxError(
30
+ f'{tokens[0]} tag requires two or four arguments. '
31
+ 'E.g.: {%% siteblock "myblock" %%} or {%% siteblock "myblock" as myvar %%}.')
32
+
33
+ block_alias = parser.compile_filter(tokens[1])
34
+ as_var = None
35
+ tokens = tokens[2:]
36
+
37
+ if len(tokens) >= 2 and tokens[-2] == 'as':
38
+ as_var = tokens[-1]
39
+
40
+ return siteblockNode(block_alias, as_var)
41
+
42
+
43
+ class siteblockNode(template.Node):
44
+
45
+ def __init__(self, block_alias, as_var=None):
46
+ self.block_alias = block_alias
47
+ self.as_var = as_var
48
+
49
+ def render(self, context):
50
+ block_alias = self.block_alias
51
+ if isinstance(self.block_alias, FilterExpression):
52
+ block_alias = block_alias.resolve(context)
53
+
54
+ contents = siteblocks.get(block_alias, context)
55
+
56
+ if self.as_var is not None:
57
+ context[self.as_var] = contents
58
+ return ''
59
+
60
+ return contents