', six.text_type(resp.content))
151 | key = ids[0]
152 |
153 | resp = self.client.post(reverse('front-placeholder-save'), {'key': key, 'val': '
'})
154 | resp = self.client.post(reverse('front-placeholder-save'), {'key': key, 'val': '
'})
155 |
156 | self.assertEqual(2, PlaceholderHistory.objects.filter(placeholder__key=key).count())
157 |
158 | resp = self.client.post(reverse('front-placeholder-save'), {'key': key, 'val': '
'})
159 | self.assertEqual(2, PlaceholderHistory.objects.filter(placeholder__key=key).count())
160 |
161 | resp = self.client.post(reverse('front-placeholder-save'), {'key': key, 'val': '
'})
162 | self.assertEqual(3, PlaceholderHistory.objects.filter(placeholder__key=key).count())
163 |
164 | resp = self.client.post(reverse('front-placeholder-save'), {'key': key, 'val': '
'})
165 | self.assertEqual(3, PlaceholderHistory.objects.filter(placeholder__key=key).count())
166 |
167 | def test_11_invalid_tag(self):
168 | self.client.login(username='admin_user', password='admin_user')
169 | resp = self.client.get(reverse('front-test_invalid_template_tag'))
170 | self.assertTrue(six.text_type(u"plugin: 'default'") in six.text_type(resp.content.decode('utf8')))
171 |
172 | def test_12_calculate_keys(self):
173 | self.client.login(username='admin_user', password='admin_user')
174 | resp = self.client.get(reverse('front-test'))
175 | self.assertFalse('empty-editable' in six.text_type(resp.content))
176 | ids = re.findall(r'
', six.text_type(resp.content))
177 | self.assertEqual(six.text_type(ids[0]), six.text_type('d577f15230caa8d39fb651d5b1ea34743f56edff'))
178 |
179 | def test_13_copy_content(self):
180 | self.assertEqual(0, Placeholder.objects.count())
181 | self.client.login(username='admin_user', password='admin_user')
182 |
183 | resp = self.client.get(reverse('front-test'))
184 | key = Placeholder.key_for('global-ph')
185 | self.assertTrue(key in six.text_type(resp.content))
186 | key = Placeholder.key_for('some-other-ph', 'hello')
187 | self.assertTrue(key in six.text_type(resp.content))
188 |
189 | resp = self.client.post(reverse('front-placeholder-save'), {'key': key, 'val': '
booh
'})
190 | self.assertEqual(1, Placeholder.objects.count())
191 |
192 | Placeholder.copy_content('some-other-ph', ['hello'], ['jello'])
193 | self.assertEqual(2, Placeholder.objects.count())
194 |
195 | jello_key = Placeholder.key_for('some-other-ph', 'jello')
196 | hello_key = Placeholder.key_for('some-other-ph', 'hello')
197 | self.assertTrue(Placeholder.objects.filter(key=jello_key).exists())
198 | self.assertTrue(Placeholder.objects.filter(key=hello_key).exists())
199 | self.assertEqual(
200 | Placeholder.objects.get(key=jello_key).value,
201 | Placeholder.objects.get(key=jello_key).value
202 | )
203 |
204 | def test_14_extra_container_classes(self):
205 | _setting = getattr(django_front_settings, 'DJANGO_FRONT_EXTRA_CONTAINER_CLASSES', '')
206 | django_front_settings.DJANGO_FRONT_EXTRA_CONTAINER_CLASSES = 'some extra classes'
207 |
208 | self.client.login(username='admin_user', password='admin_user')
209 | resp = self.client.get(reverse('front-test'))
210 | self.assertTrue('some extra classes' in six.text_type(resp.content))
211 |
212 | django_front_settings.DJANGO_FRONT_EXTRA_CONTAINER_CLASSES = _setting
213 |
--------------------------------------------------------------------------------
/front/tests/urls.py:
--------------------------------------------------------------------------------
1 | import django
2 |
3 | from .views import test, test_invalid_template_tag
4 |
5 |
6 | if django.VERSION >= (3, 1, 0):
7 | from django.urls import re_path as url, include
8 | else:
9 | from django.conf.urls import url, include
10 |
11 | urlpatterns = [
12 | url(r'^test/$', test, name='front-test'),
13 | url(
14 | r'^test-invalid/$',
15 | test_invalid_template_tag,
16 | name='front-test_invalid_template_tag',
17 | ),
18 | url(r'^i18n/', include('django.conf.urls.i18n')),
19 | url(r'^front-edit/', include('front.urls')),
20 | ]
21 |
--------------------------------------------------------------------------------
/front/tests/urls_no_save.py:
--------------------------------------------------------------------------------
1 | import django
2 |
3 | from .views import test
4 |
5 |
6 | if django.VERSION >= (3, 1, 0):
7 | from django.urls import re_path as url, include
8 | else:
9 | from django.conf.urls import url, include
10 |
11 | urlpatterns = [
12 | url(r'^test/$', test, name='front-test'),
13 | url(r'^i18n/', include('django.conf.urls.i18n')),
14 | ]
15 |
--------------------------------------------------------------------------------
/front/tests/views.py:
--------------------------------------------------------------------------------
1 | from django.http import HttpResponse
2 | try:
3 | from django.template import engines
4 | __is_18 = True
5 | except ImportError:
6 | from django.template import loader, RequestContext
7 | __is_18 = False
8 |
9 |
10 | TEST_TEMPLATE = r'''
11 | {% load front_tags %}
12 |
13 |
14 |
15 |
16 |
front test
17 |
18 |
19 |
Hello, {% if request.user.is_authenticated %}{{request.user}}{% else %}Anon{% endif %}!
20 |
{% front_edit "global-ph" %}global base content{% end_front_edit %}
21 |
{% front_edit "locale-ph" request.LANGUAGE_CODE %}locale base content{% end_front_edit %}
22 |
{% front_edit "some-other-ph" arg1 %}argument based content{% end_front_edit %}
23 | {% front_edit_scripts editor="ace" %}
24 |
25 |
26 | '''
27 |
28 |
29 | TEST_TEMPLATE_INVALID_EDITOR = r'''
30 | {% load front_tags %}
31 | {% front_edit_scripts editor="dummy" %}
32 | '''
33 |
34 |
35 | def _render(template_string, context, request):
36 | if __is_18:
37 | return engines['django'].from_string(template_string).render(context=context, request=request)
38 | else:
39 | return loader.get_template_from_string(template_string).render(RequestContext(request, context))
40 |
41 |
42 | def test(request):
43 | return HttpResponse(_render(TEST_TEMPLATE, context=dict(arg1='hello'), request=request))
44 |
45 |
46 | def test_invalid_template_tag(request):
47 | return HttpResponse(_render(TEST_TEMPLATE_INVALID_EDITOR, context={}, request=request))
48 |
--------------------------------------------------------------------------------
/front/urls.py:
--------------------------------------------------------------------------------
1 | import django
2 |
3 | from .views import do_save, get_history
4 |
5 |
6 | if django.VERSION >= (3, 1, 0):
7 | from django.urls import re_path as url
8 | else:
9 | from django.conf.urls import url
10 |
11 |
12 | urlpatterns = [
13 | url(r'^save/$', do_save, name='front-placeholder-save'),
14 | url(
15 | r'^hist/(?P
[0-9a-f]{1,40})/$', get_history, name='front-placeholder-history'
16 | ),
17 | ]
18 |
--------------------------------------------------------------------------------
/front/views.py:
--------------------------------------------------------------------------------
1 | from .conf import settings as django_front_settings
2 | from .models import Placeholder, PlaceholderHistory
3 | from django.core.serializers.json import DjangoJSONEncoder
4 | from django.http import HttpResponse
5 | from django.views.decorators.http import require_POST, require_GET
6 | import json
7 |
8 |
9 | class JsonHttpResponse(HttpResponse):
10 | def __init__(self, **kwargs):
11 | super(JsonHttpResponse, self).__init__(json.dumps(kwargs, cls=DjangoJSONEncoder, ensure_ascii=False), content_type='application/json')
12 |
13 |
14 | @require_POST
15 | def do_save(request):
16 | if request.POST and django_front_settings.DJANGO_FRONT_PERMISSION(request.user):
17 | key, val = request.POST.get('key'), request.POST.get('val')
18 | placeholder, created = Placeholder.objects.get_or_create(key=key, defaults=dict(value=val))
19 | if not created:
20 | placeholder.value = val
21 | placeholder.save()
22 |
23 | return HttpResponse('1')
24 | return HttpResponse('0')
25 |
26 |
27 | @require_GET
28 | def get_history(request, key):
29 | return JsonHttpResponse(history=[ph._as_json for ph in PlaceholderHistory.objects.filter(placeholder__key=key)])
30 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | from setuptools import find_packages, setup
4 | from setuptools.command.test import test as test_command
5 |
6 |
7 | class Tox(test_command):
8 | user_options = [("tox-args=", "a", "Arguments to pass to tox")]
9 |
10 | def initialize_options(self):
11 | test_command.initialize_options(self)
12 | self.tox_args = None
13 |
14 | def finalize_options(self):
15 | test_command.finalize_options(self)
16 | self.test_args = []
17 | self.test_suite = True
18 |
19 | def run_tests(self):
20 | # import here, cause outside the eggs aren't loaded
21 | import shlex
22 |
23 | import tox
24 |
25 | args = self.tox_args
26 | if args:
27 | args = shlex.split(self.tox_args)
28 | errno = tox.cmdline(args=args)
29 | sys.exit(errno)
30 |
31 |
32 | with open("README.rst") as readme:
33 | long_description = readme.read()
34 |
35 | setup(
36 | name="django-front",
37 | version=__import__("front").get_version(limit=3),
38 | description="A Django application to allow of front-end editing",
39 | long_description=long_description,
40 | author="Marco Bonetti",
41 | author_email="mbonetti@gmail.com",
42 | url="https://github.com/mbi/django-front",
43 | license="MIT",
44 | packages=find_packages(exclude=["test_project", "test_project.*"]),
45 | classifiers=[
46 | "Development Status :: 4 - Beta",
47 | "Environment :: Web Environment",
48 | "Intended Audience :: Developers",
49 | "License :: OSI Approved :: MIT License",
50 | "Operating System :: OS Independent",
51 | "Programming Language :: Python",
52 | "Framework :: Django",
53 | "Framework :: Django :: 4.2",
54 | "Framework :: Django :: 5.0",
55 | "Framework :: Django :: 5.2",
56 | "Programming Language :: Python :: 3.9",
57 | "Programming Language :: Python :: 3.10",
58 | "Programming Language :: Python :: 3.11",
59 | "Programming Language :: Python :: 3.12",
60 | ],
61 | include_package_data=True,
62 | zip_safe=False,
63 | install_requires=["django-classy-tags >= 1.0", "Django >= 4.2", "six"],
64 | tests_require=["tox~=4.11.4"],
65 | cmdclass={"test": Tox},
66 | )
67 |
--------------------------------------------------------------------------------
/test_project/.coveragerc:
--------------------------------------------------------------------------------
1 | [run]
2 | branch = True
3 | source =
4 | front
5 | omit =
6 | ../*migrations*
7 | ../*tests*
8 | [report]
9 | precision = 2
10 |
--------------------------------------------------------------------------------
/test_project/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 |
5 | sys.path.append('..')
6 |
7 | if __name__ == "__main__":
8 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_project.settings")
9 |
10 | from django.core.management import execute_from_command_line
11 |
12 | execute_from_command_line(sys.argv)
13 |
--------------------------------------------------------------------------------
/test_project/static/.gitignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mbi/django-front/0ed620dcd7f0e4ce11f62b58149b878f3dfb3eb6/test_project/static/.gitignore
--------------------------------------------------------------------------------
/test_project/templates/base.html:
--------------------------------------------------------------------------------
1 | {% load i18n front_tags %}
2 |
3 |
4 |
5 | {% front_edit "global" LANGUAGE_CODE %}aaa{% end_front_edit %}
6 | {% front_edit_scripts editor="ace" %}
7 |
8 |
9 |
--------------------------------------------------------------------------------
/test_project/test_project/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mbi/django-front/0ed620dcd7f0e4ce11f62b58149b878f3dfb3eb6/test_project/test_project/__init__.py
--------------------------------------------------------------------------------
/test_project/test_project/settings.py:
--------------------------------------------------------------------------------
1 | # Django settings for test_project project.
2 | import os
3 |
4 | DEBUG = True
5 | DATABASES = {
6 | "default": {
7 | "ENGINE": "django.db.backends.sqlite3",
8 | "NAME": os.path.join(os.path.dirname(__file__), "test_project.db"),
9 | }
10 | }
11 |
12 | USE_I18N = True
13 | SECRET_KEY = "lol I dont even care"
14 | USE_TZ = True
15 |
16 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
17 |
18 | MIDDLEWARE = (
19 | "django.middleware.common.CommonMiddleware",
20 | "django.contrib.sessions.middleware.SessionMiddleware",
21 | "django.middleware.csrf.CsrfViewMiddleware",
22 | "django.contrib.auth.middleware.AuthenticationMiddleware",
23 | "django.contrib.messages.middleware.MessageMiddleware",
24 | "django.middleware.locale.LocaleMiddleware",
25 | )
26 |
27 | TEMPLATES = [
28 | {
29 | "BACKEND": "django.template.backends.django.DjangoTemplates",
30 | "APP_DIRS": True,
31 | "OPTIONS": {
32 | "context_processors": (
33 | "django.contrib.auth.context_processors.auth",
34 | "django.template.context_processors.debug",
35 | "django.template.context_processors.i18n",
36 | "django.template.context_processors.media",
37 | "django.template.context_processors.static",
38 | "django.template.context_processors.tz",
39 | "django.contrib.messages.context_processors.messages",
40 | "django.template.context_processors.request",
41 | ),
42 | "debug": False,
43 | },
44 | }
45 | ]
46 | STATIC_URL = "/static/"
47 |
48 | ROOT_URLCONF = "test_project.urls"
49 |
50 | # Python dotted path to the WSGI application used by Django's runserver.
51 | WSGI_APPLICATION = "test_project.wsgi.application"
52 |
53 | INSTALLED_APPS = (
54 | "django.contrib.auth",
55 | "django.contrib.contenttypes",
56 | "django.contrib.sessions",
57 | "django.contrib.sites",
58 | "django.contrib.staticfiles",
59 | "front",
60 | )
61 |
62 | SITE_ID = 1
63 |
--------------------------------------------------------------------------------
/test_project/test_project/urls.py:
--------------------------------------------------------------------------------
1 | # from django.conf.urls import include, url
2 | from django.contrib.staticfiles.urls import staticfiles_urlpatterns
3 |
4 | urlpatterns = []
5 |
6 | urlpatterns += staticfiles_urlpatterns()
7 |
--------------------------------------------------------------------------------
/test_project/test_project/views.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth.decorators import login_required
2 | from django.http import HttpResponse
3 | from django.shortcuts import render_to_response
4 | from django.http import HttpResponse, HttpResponseRedirect
5 | from django.template import RequestContext
6 |
7 |
8 | def home(request):
9 | return render_to_response('base.html', dict(
10 | ), context_instance=RequestContext(request))
11 |
--------------------------------------------------------------------------------
/test_project/test_project/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for test_project project.
3 |
4 | This module contains the WSGI application used by Django's development server
5 | and any production WSGI deployments. It should expose a module-level variable
6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
7 | this application via the ``WSGI_APPLICATION`` setting.
8 |
9 | Usually you will have the standard Django WSGI application here, but it also
10 | might make sense to replace the whole Django WSGI application with a custom one
11 | that later delegates to the Django one. For example, you could introduce WSGI
12 | middleware here, or combine a Django application with an application of another
13 | framework.
14 |
15 | """
16 | import os
17 |
18 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_project.settings")
19 |
20 | # This application object is used by any WSGI server configured to use this
21 | # file. This includes Django's development server, if the WSGI_APPLICATION
22 | # setting points here.
23 | from django.core.wsgi import get_wsgi_application
24 | application = get_wsgi_application()
25 |
26 | # Apply WSGI middleware here.
27 | # from helloworld.wsgi import HelloWorldApplication
28 | # application = HelloWorldApplication(application)
29 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist =
3 | py{39,310}-django42,
4 | py{310,311,312}-django50,
5 | py{310,311,312}-django52,
6 | flake8,
7 | docs
8 | skipsdist = True
9 |
10 |
11 | [gh-actions]
12 | python =
13 | 3.9: py39-django42, flake8, docs
14 | 3.10: py310-django42, py310-django50, py310-django52,
15 | 3.11: py311-django42, py311-django50, py311-django52,
16 | 3.12: py312-django50, py312-django52
17 |
18 |
19 | [testenv]
20 | changedir = test_project
21 | commands =
22 | python -Wd manage.py test front
23 |
24 | setenv =
25 | PYTHONDONTWRITEBYTECODE=1
26 |
27 | deps =
28 | django42: Django>=4.2a,<4.3
29 | django50: Django>=5.0,<5.1
30 | django52: Django>=5.2,<5.3
31 |
32 | pymemcache
33 | coverage
34 | django-classy-tags
35 | south
36 | django-wymeditor
37 | six
38 |
39 | [testenv:flake8]
40 | basepython = python3
41 | deps = flake8
42 | commands=
43 | flake8 {toxinidir}/front
44 |
45 | [testenv:docs]
46 | deps =
47 | sphinx
48 | sphinx-book-theme
49 |
50 | changedir = docs
51 | commands=
52 | sphinx-build -W -b html . _build/html
53 |
54 |
55 | [flake8]
56 | ignore = E501,W503
57 |
--------------------------------------------------------------------------------