diff --git a/cms/__init__.py b/cms/__init__.py index d1c3568..6172cbf 100644 --- a/cms/__init__.py +++ b/cms/__init__.py @@ -1,2 +1,2 @@ -__version__ = "1.0.5" +__version__ = "1.0.6" default_app_config = "cms.apps.CmsConfig" diff --git a/cms/__main__.py b/cms/__main__.py index 3052380..15af440 100644 --- a/cms/__main__.py +++ b/cms/__main__.py @@ -1,58 +1,85 @@ -import os, re, argparse, shutil, example, cms +import argparse +import os +import re +import shutil + from pip._internal.operations import freeze as pip +import cms +import example + def main(): - parser = argparse.ArgumentParser(description='SimpleCMS') - parser.add_argument('project_name', nargs='?', default='.') + parser = argparse.ArgumentParser(description="SimpleCMS") + parser.add_argument("project_name", nargs="?", default=".") project_name = parser.parse_args().project_name - if project_name == '.': + if project_name == ".": project_dir = os.getcwd() project_name = os.path.basename(project_dir) else: project_dir = os.path.join(os.getcwd(), project_name) - if re.match(r'^\w+$', project_name): - if input(f'Do you want to create a new project in `{project_dir}` ?\033[1D') in 'Yy': + if re.match(r"^\w+$", project_name): + if ( + input(f"Do you want to create a new project in `{project_dir}` ?\033[1D") + in "Yy" + ): create_project(project_name, project_dir) else: - print(f'Invalid project name: {project_name}') + print(f"Invalid project name: {project_name}") def create_project(project_name, project_dir): os.makedirs(project_dir, exist_ok=True) - with open(os.path.join(project_dir, 'requirements.txt'), 'w') as f: + with open(os.path.join(project_dir, "requirements.txt"), "w") as f: for line in pip.freeze(): - if 'django_simplecms' in line: - line = f'django-simplecms=={cms.__version__}' + if "django_simplecms" in line: + line = f"django-simplecms=={cms.__version__}" print(line, file=f) - shutil.copytree(os.path.dirname(example.__file__),os.path.join(project_dir, project_name), dirs_exist_ok=True) - with open(os.open(os.path.join(project_dir, 'manage.py'), os.O_CREAT|os.O_WRONLY, 0o755), 'w') as f: - print('#!/usr/bin/env python', - 'import os, sys', - f"os.environ.setdefault('DJANGO_SETTINGS_MODULE', '{project_name}.settings')", - 'from django.core.management import execute_from_command_line', - 'execute_from_command_line(sys.argv)', - sep='\n', file=f) - with open(os.path.join(project_dir, project_name, 'wsgi.py'), 'w') as f: - print('import os', - 'from django.core.wsgi import get_wsgi_application', - f"os.environ.setdefault('DJANGO_SETTINGS_MODULE', '{project_name}.settings')", - 'application = get_wsgi_application()', - sep='\n', file=f) - with open(os.path.join(project_dir, '.gitignore'), 'w') as f: - print('*.pyc\n__pycache__/', file=f) + shutil.copytree( + os.path.dirname(example.__file__), + os.path.join(project_dir, project_name), + dirs_exist_ok=True, + ) + with open( + os.open( + os.path.join(project_dir, "manage.py"), os.O_CREAT | os.O_WRONLY, 0o755 + ), + "w", + ) as f: + print( + "#!/usr/bin/env python", + "import os, sys", + f"os.environ.setdefault('DJANGO_SETTINGS_MODULE', '{project_name}.settings')", + "from django.core.management import execute_from_command_line", + "execute_from_command_line(sys.argv)", + sep="\n", + file=f, + ) + with open(os.path.join(project_dir, project_name, "wsgi.py"), "w") as f: + print( + "import os", + "from django.core.wsgi import get_wsgi_application", + f"os.environ.setdefault('DJANGO_SETTINGS_MODULE', '{project_name}.settings')", + "application = get_wsgi_application()", + sep="\n", + file=f, + ) + with open(os.path.join(project_dir, ".gitignore"), "w") as f: + print("*.pyc\n__pycache__/", file=f) - print(f'Successfully created project "{project_name}"', - '', - 'Things to do next:', - '- create a database', - '- ./manage.py makemigrations', - '- ./manage.py migrate', - '- ./manage.py createsuperuser', - '- ./manage.py runserver', - sep='\n') + print( + f'Successfully created project "{project_name}"', + "", + "Things to do next:", + "- create a database", + "- ./manage.py makemigrations", + "- ./manage.py migrate", + "- ./manage.py createsuperuser", + "- ./manage.py runserver", + sep="\n", + ) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/cms/apps.py b/cms/apps.py index a7614d9..9a96878 100644 --- a/cms/apps.py +++ b/cms/apps.py @@ -1,10 +1,11 @@ from django.apps import AppConfig -from django.utils.translation import gettext_lazy as _ from django.utils.module_loading import autodiscover_modules +from django.utils.translation import gettext_lazy as _ + class CmsConfig(AppConfig): - name = 'cms' - verbose_name = _('Content Management System') + name = "cms" + verbose_name = _("Content Management System") def ready(self): - autodiscover_modules('views') + autodiscover_modules("views") diff --git a/cms/decorators.py b/cms/decorators.py index 590e4ca..d787f2c 100644 --- a/cms/decorators.py +++ b/cms/decorators.py @@ -1,17 +1,20 @@ from cms import registry + def page_model(cls): - '''Decorator to register the Page model''' + """Decorator to register the Page model""" registry.page_class = cls return cls + def section_model(cls): - '''Decorator to register the Section model''' + """Decorator to register the Section model""" registry.section_class = cls return cls + def section_view(cls): - '''Decorator to register a view for a specific section''' + """Decorator to register a view for a specific section""" registry.view_per_type[cls.__name__.lower()] = cls registry.section_types.append((cls.__name__.lower(), cls.verbose_name)) return cls diff --git a/cms/forms.py b/cms/forms.py index 8d40608..00a7503 100644 --- a/cms/forms.py +++ b/cms/forms.py @@ -1,27 +1,28 @@ from django import forms from django.conf import settings -from django.db.models import Prefetch -from django.contrib.contenttypes.models import ContentType from django.core.mail import EmailMessage from django.utils.translation import gettext_lazy as _ from . import registry + class PageForm(forms.ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.label_suffix = '' + self.label_suffix = "" - self.formsets = [forms.inlineformset_factory( - parent_model=registry.page_class, - model=registry.section_class, - form=SectionForm, - extra=1, - )( - data=self.data if self.is_bound else None, - files=self.files if self.is_bound else None, - instance=self.instance, - )] + self.formsets = [ + forms.inlineformset_factory( + parent_model=registry.page_class, + model=registry.section_class, + form=SectionForm, + extra=1, + )( + data=self.data if self.is_bound else None, + files=self.files if self.is_bound else None, + instance=self.instance, + ) + ] self.formsets[0][0].empty_permitted = True def is_valid(self): @@ -44,17 +45,18 @@ class PageForm(forms.ModelForm): class Meta: model = registry.page_class - fields = '__all__' + fields = "__all__" + class SectionForm(forms.ModelForm): type = forms.ChoiceField() def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.label_suffix = '' - self.fields['DELETE'] = forms.BooleanField(label=_('Delete'), required=False) - self.fields['type'].choices = registry.get_types() - self.fields['type'].initial = registry.get_types()[0][0] + self.label_suffix = "" + self.fields["DELETE"] = forms.BooleanField(label=_("Delete"), required=False) + self.fields["type"].choices = registry.get_types() + self.fields["type"].initial = registry.get_types()[0][0] # Populate the 'formsets' attribute if the Section was # extendend with one_to_many fields @@ -64,14 +66,14 @@ class SectionForm(forms.ModelForm): formset = forms.inlineformset_factory( parent_model=registry.section_class, model=field.related_model, - fields='__all__', + fields="__all__", extra=1, )( instance=self.instance, data=self.data if self.is_bound else None, files=self.files if self.is_bound else None, - prefix=f'{self.prefix}-{field.name}', - form_kwargs={'label_suffix': self.label_suffix}, + prefix=f"{self.prefix}-{field.name}", + form_kwargs={"label_suffix": self.label_suffix}, ) formset.name = field.name self.formsets.append(formset) @@ -79,19 +81,19 @@ class SectionForm(forms.ModelForm): def is_valid(self): result = super().is_valid() for formset in self.formsets: - result = result and formset.is_valid() # AND + result = result and formset.is_valid() # AND return result def has_changed(self): result = super().has_changed() for formset in self.formsets: - result = result or formset.has_changed() # OR + result = result or formset.has_changed() # OR return result def save(self, commit=True): section = super().save(commit=commit) - if self.cleaned_data['DELETE']: + if self.cleaned_data["DELETE"]: section.delete() if section.page.slug and not section.page.sections.exists(): section.page.delete() @@ -106,25 +108,28 @@ class SectionForm(forms.ModelForm): class Meta: model = registry.section_class - exclude = ['page'] + exclude = ["page"] + class ContactForm(forms.Form): - sender = forms.EmailField(label=_('Your email address')) - spam_protection = forms.CharField(label=_('Your message'), widget=forms.Textarea()) - message = forms.CharField(label=_('Your message'), widget=forms.Textarea(), initial='Hi there!') + sender = forms.EmailField(label=_("Your email address")) + spam_protection = forms.CharField(label=_("Your message"), widget=forms.Textarea()) + message = forms.CharField( + label=_("Your message"), widget=forms.Textarea(), initial="Hi there!" + ) def save(self): - body = self.cleaned_data.get('spam_protection') + body = self.cleaned_data.get("spam_protection") if len(body.split()) < 7: return - spamcheck = self.cleaned_data.get('message') - if spamcheck != 'Hi there!': + spamcheck = self.cleaned_data.get("message") + if spamcheck != "Hi there!": return email = EmailMessage( - to = settings.DEFAULT_TO_EMAIL, - body = body, - subject = _('Contact form'), - headers = {'Reply-To': self.cleaned_data.get('sender')}, + to=settings.DEFAULT_TO_EMAIL, + body=body, + subject=_("Contact form"), + headers={"Reply-To": self.cleaned_data.get("sender")}, ) email.send() diff --git a/cms/middleware.py b/cms/middleware.py index 912a7e2..e294962 100644 --- a/cms/middleware.py +++ b/cms/middleware.py @@ -1,7 +1,9 @@ import os -from sass import compile + from django.conf import settings from django.middleware import cache +from sass import compile + def locate(filename): for path, dirs, files in os.walk(os.getcwd(), followlinks=True): @@ -9,38 +11,44 @@ def locate(filename): if f == filename: yield os.path.join(path, filename) + class FetchFromCacheMiddleware(cache.FetchFromCacheMiddleware): - '''Minor change to the original middleware that prevents caching of + """Minor change to the original middleware that prevents caching of requests that have a `sessionid` cookie. This should be the Django default, IMHO. - ''' + """ + def process_request(self, request): - if 'sessionid' not in request.COOKIES: + if "sessionid" not in request.COOKIES: return super().process_request(request) + class SassMiddleware: - '''Simple SASS middleware that intercepts requests for .css files and + """Simple SASS middleware that intercepts requests for .css files and tries to compile the corresponding SCSS file. - ''' + """ + def __init__(self, get_response): self.get_response = get_response def __call__(self, request): - if settings.DEBUG and request.path.endswith('.css'): - css_file = request.path.rsplit('/',1)[1] + if settings.DEBUG and request.path.endswith(".css"): + css_file = request.path.rsplit("/", 1)[1] sass_file = css_file[:-4] sass_paths = locate(sass_file) for sass_path in list(sass_paths): if os.path.exists(sass_path): - css_path = sass_path + '.css' - map_path = css_path + '.map' - css = compile(filename=sass_path, output_style='nested') - css, mapping = compile(filename=sass_path, source_map_filename=map_path) - with open(css_path, 'w') as f: + css_path = sass_path + ".css" + map_path = css_path + ".map" + css = compile(filename=sass_path, output_style="nested") + css, mapping = compile( + filename=sass_path, source_map_filename=map_path + ) + with open(css_path, "w") as f: f.write(css) - with open(map_path, 'w') as f: + with open(map_path, "w") as f: f.write(mapping) response = self.get_response(request) diff --git a/cms/models.py b/cms/models.py index f645d09..d63c0b3 100644 --- a/cms/models.py +++ b/cms/models.py @@ -10,6 +10,8 @@ from . import fields, mixins class Model(models.Model): """Felt cute, might delete later.""" + id = models.BigAutoField(primary_key=True) + class Meta: abstract = True diff --git a/cms/registry.py b/cms/registry.py index a6826d6..b0814fd 100644 --- a/cms/registry.py +++ b/cms/registry.py @@ -3,15 +3,17 @@ section_class = None section_types = [] view_per_type = {} + def get_types(): return section_types + def get_view(section, request): return view_per_type[section.type](request) + def get_fields_per_type(): fields_per_type = {} for name, view in view_per_type.items(): - fields_per_type[name] = ['title', 'type', 'number'] + view.fields + fields_per_type[name] = ["title", "type", "number"] + view.fields return fields_per_type - diff --git a/cms/templatetags/cms.py b/cms/templatetags/cms.py index 9212337..708f7ea 100644 --- a/cms/templatetags/cms.py +++ b/cms/templatetags/cms.py @@ -1,18 +1,17 @@ -from markdown import markdown as md - from django import template from django.shortcuts import reverse from django.utils.safestring import mark_safe -from django.utils.translation import gettext_lazy as _ +from markdown import markdown as md from cms import registry -MARKDOWN_EXTENSIONS = ['extra', 'smarty'] +MARKDOWN_EXTENSIONS = ["extra", "smarty"] register = template.Library() + @register.simple_tag(takes_context=True) def eval(context, expr): - '''USE WITH CAUTION!!! + """USE WITH CAUTION!!! This template tag runs its argument through Django's templating system using the current context, placing all power into the @@ -20,49 +19,61 @@ def eval(context, expr): Also, it applies Markdown. - ''' + """ result = template.Template(expr).render(context) return mark_safe(md(result, extensions=MARKDOWN_EXTENSIONS)) + @register.simple_tag(takes_context=True) def editsection(context, inner): - '''Renders a simple link to edit the current section''' - section = context['section'] - user = context['request'].user + """Renders a simple link to edit the current section""" + section = context["section"] + user = context["request"].user app_label = section._meta.app_label model_name = section._meta.model_name - if user.has_perm(f'{app_label}.change_{model_name}'): + if user.has_perm(f"{app_label}.change_{model_name}"): slug = section.page.slug number = section.number - url = reverse('cms:updatesection', args=[slug, number]) if slug else reverse('cms:updatesection', args=[number]) + url = ( + reverse("cms:updatesection", args=[slug, number]) + if slug + else reverse("cms:updatesection", args=[number]) + ) return mark_safe(f'{inner}') - return '' + return "" + @register.simple_tag(takes_context=True) def editpage(context, inner): - '''Renders a simple link to edit the current page''' - page = context['page'] - user = context['request'].user + """Renders a simple link to edit the current page""" + page = context["page"] + user = context["request"].user app_label = page._meta.app_label model_name = page._meta.model_name - if user.has_perm(f'{app_label}.change_{model_name}'): + if user.has_perm(f"{app_label}.change_{model_name}"): slug = page.slug - url = reverse('cms:updatepage', args=[slug]) if slug else reverse('cms:updatepage') + url = ( + reverse("cms:updatepage", args=[slug]) + if slug + else reverse("cms:updatepage") + ) return mark_safe(f'{inner}') - return '' + return "" -@register.tag('include_section') + +@register.tag("include_section") def do_include(parser, token): - '''Renders the section with its own context''' + """Renders the section with its own context""" _, section = token.split_contents() return IncludeSectionNode(section) + class IncludeSectionNode(template.Node): def __init__(self, section): self.section = template.Variable(section) - self.csrf_token = template.Variable('csrf_token') - self.request = template.Variable('request') - self.perms = template.Variable('perms') + self.csrf_token = template.Variable("csrf_token") + self.request = template.Variable("request") + self.perms = template.Variable("perms") super().__init__() def render(self, context): @@ -73,13 +84,13 @@ class IncludeSectionNode(template.Node): view = registry.get_view(section, request) initial_context = { - 'csrf_token': csrf_token, - 'section': section, - 'request': request, - 'perms': perms, + "csrf_token": csrf_token, + "section": section, + "request": request, + "perms": perms, } - if hasattr(section, 'invalid_form'): - initial_context['form'] = section.invalid_form + if hasattr(section, "invalid_form"): + initial_context["form"] = section.invalid_form section_context = view.get_context_data(**initial_context) t = context.template.engine.get_template(view.template_name) diff --git a/cms/urls.py b/cms/urls.py index 4113786..c2a0930 100644 --- a/cms/urls.py +++ b/cms/urls.py @@ -1,15 +1,23 @@ from django.urls import path -from .views import PageView, CreatePage, UpdatePage, CreateSection, UpdateSection -app_name = 'cms' +from .views import CreatePage, CreateSection, PageView, UpdatePage, UpdateSection + +app_name = "cms" urlpatterns = [ - path('new/', CreatePage.as_view(), name='createpage'), - path('edit/', UpdatePage.as_view(), kwargs={'slug': ''}, name='updatepage'), - path('edit//', UpdateSection.as_view(), kwargs={'slug': ''}, name='updatesection'), - path('/edit/', UpdatePage.as_view(), name='updatepage'), - path('/edit/new/', CreateSection.as_view(), name='createsection'), - path('/edit//', UpdateSection.as_view(), name='updatesection'), - path('', PageView.as_view(), name='page'), - path('/', PageView.as_view(), name='page'), + path("new/", CreatePage.as_view(), name="createpage"), + path("edit/", UpdatePage.as_view(), kwargs={"slug": ""}, name="updatepage"), + path( + "edit//", + UpdateSection.as_view(), + kwargs={"slug": ""}, + name="updatesection", + ), + path("/edit/", UpdatePage.as_view(), name="updatepage"), + path("/edit/new/", CreateSection.as_view(), name="createsection"), + path( + "/edit//", UpdateSection.as_view(), name="updatesection" + ), + path("", PageView.as_view(), name="page"), + path("/", PageView.as_view(), name="page"), ] diff --git a/cms/views.py b/cms/views.py index b2068c8..b60b58e 100644 --- a/cms/views.py +++ b/cms/views.py @@ -1,18 +1,20 @@ import json +from django.contrib.auth.mixins import UserPassesTestMixin +from django.http import Http404, HttpResponseBadRequest, HttpResponseRedirect from django.shortcuts import redirect -from django.views.generic import base, detail, edit from django.utils.decorators import method_decorator from django.views.decorators.cache import never_cache -from django.contrib.auth.mixins import UserPassesTestMixin -from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseBadRequest +from django.views.generic import base, detail, edit from . import registry from .forms import PageForm, SectionForm + class SectionView: - '''Generic section view''' - template_name = 'cms/sections/section.html' + """Generic section view""" + + template_name = "cms/sections/section.html" def __init__(self, request): self.request = request @@ -20,14 +22,16 @@ class SectionView: def get_context_data(self, **kwargs): return kwargs + class SectionFormView(SectionView): - '''Generic section with associated form''' + """Generic section with associated form""" + form_class = None success_url = None def get_context_data(self, **kwargs): - if 'form' not in kwargs: - kwargs['form'] = self.get_form() + if "form" not in kwargs: + kwargs["form"] = self.get_form() return kwargs def form_valid(self, form): @@ -37,56 +41,62 @@ class SectionFormView(SectionView): def get_form_kwargs(self): return {} - def get_form(self, method='get'): + def get_form(self, method="get"): form_class = self.form_class kwargs = self.get_form_kwargs() - if method == 'post': - kwargs.update({ - 'data': self.request.POST, - 'files': self.request.FILES, - }) + if method == "post": + kwargs.update( + { + "data": self.request.POST, + "files": self.request.FILES, + } + ) return form_class(**kwargs) -class PageView(detail.DetailView): - '''View of a page with heterogeneous sections''' - model = registry.page_class - template_name = 'cms/page.html' - def setup(self, *args, slug='', **kwargs): - '''Supply a default argument for slug''' +class PageView(detail.DetailView): + """View of a page with heterogeneous sections""" + + model = registry.page_class + template_name = "cms/page.html" + + def setup(self, *args, slug="", **kwargs): + """Supply a default argument for slug""" super().setup(*args, slug=slug, **kwargs) def get(self, request, *args, **kwargs): - '''Instantiate section views and render final response''' + """Instantiate section views and render final response""" try: page = self.object = self.get_object() except Http404: app_label = registry.page_class._meta.app_label model_name = registry.page_class._meta.model_name - if self.kwargs['slug'] == '': - page = registry.page_class(title='Homepage', slug='') + if self.kwargs["slug"] == "": + page = registry.page_class(title="Homepage", slug="") page.save() self.object = page # Special case: Don't serve 404 pages to authorized users, # but redirect to the edit page form with the slug pre-filled - elif (self.request.user.has_perm(f'{app_label}.change_{model_name}')): - return redirect('cms:updatepage', slug=self.kwargs['slug']) + elif self.request.user.has_perm(f"{app_label}.change_{model_name}"): + return redirect("cms:updatepage", slug=self.kwargs["slug"]) else: raise context = self.get_context_data(**kwargs) sections = page.sections.all() - context.update({ - 'page': page, - 'sections': sections, - }) + context.update( + { + "page": page, + "sections": sections, + } + ) return self.render_to_response(context) def post(self, request, **kwargs): - '''Call the post() method of the correct section view''' + """Call the post() method of the correct section view""" try: - pk = int(self.request.POST.get('section')) - except: + pk = int(self.request.POST.get("section")) + except Exception: return HttpResponseBadRequest() page = self.object = self.get_object() @@ -95,65 +105,73 @@ class PageView(detail.DetailView): for section in sections: if section.pk == pk: view = registry.get_view(section, request) - form = view.get_form(method='post') + form = view.get_form(method="post") if form.is_valid(): return view.form_valid(form) section.invalid_form = form - context.update({ - 'page': page, - 'sections': sections, - }) + context.update( + { + "page": page, + "sections": sections, + } + ) return self.render_to_response(context) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) pages = registry.page_class.objects.filter(menu=True) - context.update({ - 'pages': pages, - }) + context.update( + { + "pages": pages, + } + ) return context -@method_decorator(never_cache, name='dispatch') -class EditPage(UserPassesTestMixin, edit.ModelFormMixin, base.TemplateResponseMixin, base.View): - '''Base view with nested forms for editing the page and all its sections''' + +@method_decorator(never_cache, name="dispatch") +class EditPage( + UserPassesTestMixin, edit.ModelFormMixin, base.TemplateResponseMixin, base.View +): + """Base view with nested forms for editing the page and all its sections""" + model = registry.page_class form_class = PageForm - template_name = 'cms/edit.html' + template_name = "cms/edit.html" def test_func(self): - '''Only allow users with the correct permissions''' + """Only allow users with the correct permissions""" app_label = registry.page_class._meta.app_label model_name = registry.page_class._meta.model_name - return self.request.user.has_perm(f'{app_label}.change_{model_name}') + return self.request.user.has_perm(f"{app_label}.change_{model_name}") def get_form_kwargs(self): - '''Set the default slug to the current URL for new pages''' + """Set the default slug to the current URL for new pages""" kwargs = super().get_form_kwargs() - if 'slug' in self.kwargs: - kwargs.update({'initial': {'slug': self.kwargs['slug']}}) + if "slug" in self.kwargs: + kwargs.update({"initial": {"slug": self.kwargs["slug"]}}) return kwargs def get_context_data(self, **kwargs): - '''Populate the fields_per_type dict for use in javascript''' + """Populate the fields_per_type dict for use in javascript""" context = super().get_context_data(**kwargs) - context['fields_per_type'] = json.dumps(registry.get_fields_per_type()) + context["fields_per_type"] = json.dumps(registry.get_fields_per_type()) return context def get_object(self): - '''Prevent 404 by serving the new object form''' + """Prevent 404 by serving the new object form""" try: return super().get_object() except Http404: return None def get(self, *args, **kwargs): - '''Handle GET requests''' + """Handle GET requests""" self.object = self.get_object() return self.render_to_response(self.get_context_data(**kwargs)) def post(self, *args, **kwargs): - '''Handle POST requests''' + """Handle POST requests""" self.object = self.get_object() form = self.get_form() @@ -161,51 +179,59 @@ class EditPage(UserPassesTestMixin, edit.ModelFormMixin, base.TemplateResponseMi page = form.save() if page: return HttpResponseRedirect(page.get_absolute_url()) - return HttpResponseRedirect('/') + return HttpResponseRedirect("/") return self.render_to_response(self.get_context_data(form=form, **kwargs)) + class CreatePage(EditPage): - '''View for creating new pages''' + """View for creating new pages""" + def get_object(self): return registry.page_class() -class UpdatePage(EditPage): - '''View for editing existing pages''' -@method_decorator(never_cache, name='dispatch') -class EditSection(UserPassesTestMixin, edit.ModelFormMixin, base.TemplateResponseMixin, base.View): +class UpdatePage(EditPage): + """View for editing existing pages""" + + +@method_decorator(never_cache, name="dispatch") +class EditSection( + UserPassesTestMixin, edit.ModelFormMixin, base.TemplateResponseMixin, base.View +): model = registry.section_class form_class = SectionForm - template_name = 'cms/edit.html' + template_name = "cms/edit.html" def test_func(self): - '''Only allow users with the correct permissions''' + """Only allow users with the correct permissions""" app_label = registry.section_class._meta.app_label model_name = registry.section_class._meta.model_name - return self.request.user.has_perm(f'{app_label}.change_{model_name}') + return self.request.user.has_perm(f"{app_label}.change_{model_name}") def get_form_kwargs(self): kwargs = super().get_form_kwargs() - kwargs.update({ - 'prefix': 'section', - }) + kwargs.update( + { + "prefix": "section", + } + ) return kwargs def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['fields_per_type'] = json.dumps(registry.get_fields_per_type()) + context["fields_per_type"] = json.dumps(registry.get_fields_per_type()) return context def get_object(self, queryset=None): try: - self.page = registry.page_class.objects.get(slug=self.kwargs['slug']) + self.page = registry.page_class.objects.get(slug=self.kwargs["slug"]) except registry.page_class.DoesNotExist: raise Http404() return self.get_section() def get_section(self): try: - section = self.page.sections.get(number=self.kwargs['number']) + section = self.page.sections.get(number=self.kwargs["number"]) except self.page.sections.DoesNotExist: raise Http404() return section @@ -225,12 +251,14 @@ class EditSection(UserPassesTestMixin, edit.ModelFormMixin, base.TemplateRespons elif self.page.sections.exists(): return HttpResponseRedirect(self.page.get_absolute_url()) else: - return HttpResponseRedirect('/') + return HttpResponseRedirect("/") return self.render_to_response(self.get_context_data(form=form, **kwargs)) + class CreateSection(EditSection): def get_section(self): return registry.section_class(page=self.page) + class UpdateSection(EditSection): pass diff --git a/example/models.py b/example/models.py index 50848c0..0707768 100644 --- a/example/models.py +++ b/example/models.py @@ -1,26 +1,33 @@ from django.db import models from django.utils.translation import gettext_lazy as _ -from cms.models import BasePage, BaseSection + from cms.decorators import page_model, section_model +from cms.models import BasePage, BaseSection + @page_model class Page(BasePage): - '''Add custom fields here. Already existing fields: title, slug, + """Add custom fields here. Already existing fields: title, slug, number, menu - ''' + """ + @section_model class Section(BaseSection): - '''Add custom fields here. Already existing fields: title, type, + """Add custom fields here. Already existing fields: title, type, number, content, image, video, href - ''' - page = models.ForeignKey(Page, related_name='sections', on_delete=models.PROTECT) + """ + + page = models.ForeignKey(Page, related_name="sections", on_delete=models.PROTECT) + class SectionImage(models.Model): - section = models.ForeignKey(Section, related_name='images', on_delete=models.CASCADE) - image = models.ImageField(_('Image')) + section = models.ForeignKey( + Section, related_name="images", on_delete=models.CASCADE + ) + image = models.ImageField(_("Image")) class Meta: - ordering = ['pk'] + ordering = ["pk"] diff --git a/example/settings.py b/example/settings.py index a4a8e63..d987395 100644 --- a/example/settings.py +++ b/example/settings.py @@ -5,95 +5,95 @@ import sys PROJECT_NAME = os.path.basename(os.path.dirname(os.path.abspath(__file__))) -DEBUG = 'runserver' in sys.argv -KEYFILE = f'/tmp/{PROJECT_NAME}.secret' -ADMINS = [('JJ Vens', 'jj@rtts.eu')] -DEFAULT_FROM_EMAIL = 'noreply@rtts.eu' -DEFAULT_TO_EMAIL = ['jj@rtts.eu'] -ALLOWED_HOSTS = ['*'] -ROOT_URLCONF = PROJECT_NAME + '.urls' -WSGI_APPLICATION = PROJECT_NAME + '.wsgi.application' -LANGUAGE_CODE = 'nl' -TIME_ZONE = 'Europe/Amsterdam' +DEBUG = "runserver" in sys.argv +KEYFILE = f"/tmp/{PROJECT_NAME}.secret" +ADMINS = [("JJ Vens", "jj@rtts.eu")] +DEFAULT_FROM_EMAIL = "noreply@rtts.eu" +DEFAULT_TO_EMAIL = ["jj@rtts.eu"] +ALLOWED_HOSTS = ["*"] +ROOT_URLCONF = PROJECT_NAME + ".urls" +WSGI_APPLICATION = PROJECT_NAME + ".wsgi.application" +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" +LANGUAGE_CODE = "nl" +TIME_ZONE = "Europe/Amsterdam" USE_I18N = True USE_L10N = True USE_TZ = True -STATIC_URL = '/static/' -STATIC_ROOT = '/srv/' + PROJECT_NAME + '/static' -MEDIA_URL = '/media/' -MEDIA_ROOT = '/srv/' + PROJECT_NAME + '/media' -LOGIN_REDIRECT_URL = '/' -LOGOUT_REDIRECT_URL = '/' -DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' +STATIC_URL = "/static/" +STATIC_ROOT = "/srv/" + PROJECT_NAME + "/static" +MEDIA_URL = "/media/" +MEDIA_ROOT = "/srv/" + PROJECT_NAME + "/media" +LOGIN_REDIRECT_URL = "/" +LOGOUT_REDIRECT_URL = "/" if DEBUG: - EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' + EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" CACHE_MIDDLEWARE_SECONDS = 0 try: with open(KEYFILE) as f: SECRET_KEY = f.read() except IOError: - SECRET_KEY = ''.join(random.choice(string.printable) for x in range(50)) - with open(KEYFILE, 'w') as f: + SECRET_KEY = "".join(random.choice(string.printable) for x in range(50)) + with open(KEYFILE, "w") as f: f.write(SECRET_KEY) INSTALLED_APPS = [ PROJECT_NAME, - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'cms', - 'embed_video', - 'easy_thumbnails', - 'django_extensions', + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "cms", + "embed_video", + "easy_thumbnails", + "django_extensions", ] if not DEBUG: - INSTALLED_APPS += ['django.contrib.staticfiles'] + INSTALLED_APPS += ["django.contrib.staticfiles"] MIDDLEWARE = [ - 'django.middleware.cache.UpdateCacheMiddleware', - 'cms.middleware.SassMiddleware', - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', - 'tidy.middleware.TidyMiddleware', - 'cms.middleware.FetchFromCacheMiddleware', + "django.middleware.cache.UpdateCacheMiddleware", + "cms.middleware.SassMiddleware", + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", + "tidy.middleware.TidyMiddleware", + "cms.middleware.FetchFromCacheMiddleware", ] TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", ], }, }, ] DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql', - 'USER': PROJECT_NAME, - 'NAME': PROJECT_NAME, + "default": { + "ENGINE": "django.db.backends.postgresql", + "USER": PROJECT_NAME, + "NAME": PROJECT_NAME, } } CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', - 'LOCATION': '127.0.0.1:11211', - 'KEY_PREFIX': PROJECT_NAME, + "default": { + "BACKEND": "django.core.cache.backends.memcached.PyLibMCCache", + "LOCATION": "127.0.0.1:11211", + "KEY_PREFIX": PROJECT_NAME, } } diff --git a/example/urls.py b/example/urls.py index be77a41..49d9e51 100644 --- a/example/urls.py +++ b/example/urls.py @@ -1,16 +1,20 @@ from django.conf import settings -from django.contrib import admin -from django.urls import path, include from django.conf.urls.static import static -from django.views.generic import RedirectView +from django.contrib import admin from django.contrib.staticfiles.urls import staticfiles_urlpatterns +from django.urls import include, path +from django.views.generic import RedirectView -admin.site.site_header = admin.site.site_title = settings.PROJECT_NAME.replace('_', ' ').title() -urlpatterns = staticfiles_urlpatterns() + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) +admin.site.site_header = admin.site.site_title = settings.PROJECT_NAME.replace( + "_", " " +).title() +urlpatterns = staticfiles_urlpatterns() + static( + settings.MEDIA_URL, document_root=settings.MEDIA_ROOT +) urlpatterns += [ - path('admin/', admin.site.urls), - path('accounts/', include('django.contrib.auth.urls')), - path('login/', RedirectView.as_view(url='/accounts/login/')), - path('logout/', RedirectView.as_view(url='/accounts/logout/')), - path('', include('cms.urls', namespace='cms')), + path("admin/", admin.site.urls), + path("accounts/", include("django.contrib.auth.urls")), + path("login/", RedirectView.as_view(url="/accounts/login/")), + path("logout/", RedirectView.as_view(url="/accounts/logout/")), + path("", include("cms.urls", namespace="cms")), ] diff --git a/example/views.py b/example/views.py index e891d71..995e394 100644 --- a/example/views.py +++ b/example/views.py @@ -1,30 +1,35 @@ from django.utils.translation import gettext_lazy as _ -from cms.views import SectionView, SectionFormView + from cms.decorators import section_view from cms.forms import ContactForm +from cms.views import SectionFormView, SectionView + @section_view class Text(SectionView): - verbose_name = _('Text') - fields = ['content'] - template_name = 'text.html' + verbose_name = _("Text") + fields = ["content"] + template_name = "text.html" + @section_view class Images(SectionView): - verbose_name = _('Image(s)') - fields = ['images'] - template_name = 'images.html' + verbose_name = _("Image(s)") + fields = ["images"] + template_name = "images.html" + @section_view class Video(SectionView): - verbose_name = _('Video') - fields = ['video'] - template_name = 'video.html' + verbose_name = _("Video") + fields = ["video"] + template_name = "video.html" + @section_view class Contact(SectionFormView): - verbose_name = _('Contact') + verbose_name = _("Contact") fields = [] form_class = ContactForm - success_url = '/thanks/' - template_name = 'contact.html' + success_url = "/thanks/" + template_name = "contact.html" diff --git a/example/wsgi.py b/example/wsgi.py index 805686a..5fb93d6 100644 --- a/example/wsgi.py +++ b/example/wsgi.py @@ -11,6 +11,6 @@ import os from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'example.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings") application = get_wsgi_application() diff --git a/manage.py b/manage.py index f0f029c..6bfa2c4 100755 --- a/manage.py +++ b/manage.py @@ -2,8 +2,8 @@ import os import sys -if __name__ == '__main__': - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'example.settings') +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings") try: from django.core.management import execute_from_command_line except ImportError as exc: diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..1a17557 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,2 @@ +[tool.isort] +line_length = 88 diff --git a/setup.py b/setup.py index f1f1168..a17767d 100755 --- a/setup.py +++ b/setup.py @@ -1,39 +1,40 @@ #!/usr/bin/env python -import cms -from setuptools import setup, find_packages +from setuptools import find_packages, setup -with open('README.md', 'r') as fh: +import cms + +with open("README.md", "r") as fh: long_description = fh.read() setup( - name = 'django-simplecms', - description = 'Simple Django CMS', - version = cms.__version__, - author = 'Jaap Joris Vens', - author_email = 'jj+cms@rtts.eu', - url = 'https://github.com/rtts/django-simplecms', - long_description = long_description, - long_description_content_type = 'text/markdown', - packages = find_packages(), - entry_points = { - 'console_scripts': ['simplecms=cms.__main__:main'], + name="django-simplecms", + description="Simple Django CMS", + version=cms.__version__, + author="Jaap Joris Vens", + author_email="jj+cms@rtts.eu", + url="https://github.com/rtts/django-simplecms", + long_description=long_description, + long_description_content_type="text/markdown", + packages=find_packages(), + entry_points={ + "console_scripts": ["simplecms=cms.__main__:main"], }, - include_package_data = True, - classifiers = [ - 'Programming Language :: Python :: 3', - 'License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)', - 'Operating System :: OS Independent', + include_package_data=True, + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)", + "Operating System :: OS Independent", ], - python_requires = '>=3.8', - install_requires = [ - 'django', - 'django-extensions', - 'django-embed-video', - 'django-tidy', - 'easy-thumbnails', - 'libsass', - 'markdown', - 'psycopg2', - 'pylibmc', + python_requires=">=3.8", + install_requires=[ + "django", + "django-extensions", + "django-embed-video", + "django-tidy", + "easy-thumbnails", + "libsass", + "markdown", + "psycopg2", + "pylibmc", ], )