From 0d5a8c65e317d9921f9bccc962ef55c1ea93dacd Mon Sep 17 00:00:00 2001 From: Jens Kleineheismann Date: Thu, 13 Dec 2018 14:47:58 +0100 Subject: [PATCH] Refactor: split code into several django apps (we call them modules). --- dav_auth/__init__.py | 1 + dav_auth/apps.py | 12 ++ .../settings-dav_auth.py | 2 + dav_auth/emails.py | 25 ++++ dav_events/forms/auth.py => dav_auth/forms.py | 0 dav_auth/module.json | 3 + dav_auth/templates/dav_auth/base.html | 1 + .../dav_auth}/emails/password_set.txt | 2 +- .../templates/dav_auth/forms/login.html | 6 +- .../dav_auth/forms/reset_password.html | 4 +- .../dav_auth/forms/set_password.html | 4 +- .../dav_auth}/includes/login_widget.html | 6 +- dav_auth/urls.py | 10 ++ dav_events/views/auth.py => dav_auth/views.py | 40 ++++-- dav_base/__init__.py | 1 + .../__init__.py => dav_base/admin.py | 0 dav_base/apps.py | 13 ++ dav_base/config/__init__.py | 2 + dav_base/config/apps.py | 71 ++++++++++ dav_base/config/modules.py | 132 ++++++++++++++++++ dav_base/console_scripts/__init__.py | 0 .../console_scripts/admin.py | 44 +++--- .../additional_settings.py | 23 ++- .../settings-dav_base.py | 6 + .../django_project_config/urls.py | 12 ++ dav_base/emails.py | 59 ++++++++ dav_base/management/__init__.py | 0 dav_base/management/commands/__init__.py | 0 .../management/commands/disable_module.py | 18 +++ dav_base/management/commands/enable_module.py | 37 +++++ dav_base/management/commands/list_modules.py | 17 +++ dav_base/migrations/__init__.py | 0 dav_base/models/__init__.py | 0 .../static/dav_base}/bootstrap/config.json | 0 .../dav_base}/bootstrap/css/bootstrap.css | 0 .../dav_base}/bootstrap/css/bootstrap.min.css | 0 .../fonts/glyphicons-halflings-regular.eot | Bin .../fonts/glyphicons-halflings-regular.svg | 0 .../fonts/glyphicons-halflings-regular.ttf | Bin .../fonts/glyphicons-halflings-regular.woff | Bin .../fonts/glyphicons-halflings-regular.woff2 | Bin .../dav_base}/bootstrap/js/bootstrap.js | 0 .../dav_base}/bootstrap/js/bootstrap.min.js | 0 .../css/dataTables.bootstrap.min.css | 0 .../static/dav_base}/css/local.css | 0 .../static/dav_base}/img/brand.png | Bin .../static/dav_base}/img/dav-favicon.ico | Bin .../dav_base}/js/jquery.dataTables.min.js | 0 .../static/dav_base}/js/jquery.min.js | 0 {dav_events => dav_base}/templates/400.html | 2 +- {dav_events => dav_base}/templates/403.html | 2 +- {dav_events => dav_base}/templates/404.html | 2 +- {dav_events => dav_base}/templates/500.html | 2 +- dav_base/templates/dav_base/base.html | 72 ++++++++++ .../templates/dav_base}/error_base.html | 2 +- dav_base/templates/dav_base/root.html | 27 ++++ dav_base/templates/project_name.html | 0 dav_base/templatetags/__init__.py | 0 dav_base/templatetags/dav_base.py | 53 +++++++ dav_base/urls.py | 9 ++ dav_base/views.py | 23 +++ dav_events/apps.py | 6 +- dav_events/config.py | 67 --------- .../Resources/django.main.urls.py | 7 - .../settings-dav_events.py} | 3 - dav_events/emails.py | 81 +---------- dav_events/forms/__init__.py | 1 - dav_events/models/event.py | 2 +- dav_events/models/oneclickaction.py | 2 +- dav_events/module.json | 3 + dav_events/templates/dav_events/base.html | 72 +--------- .../templates/dav_events/event_detail.html | 14 +- .../templates/dav_events/event_list.html | 10 +- .../dav_events/event_list_export_form.html | 8 +- .../dav_events/event_update_form.html | 8 +- dav_events/templates/dav_events/home.html | 4 +- dav_events/urls.py | 20 ++- dav_events/views/__init__.py | 1 - dav_events/views/events.py | 9 +- dav_events/workflow.py | 4 +- setup.py | 4 +- 81 files changed, 739 insertions(+), 332 deletions(-) create mode 100644 dav_auth/__init__.py create mode 100644 dav_auth/apps.py create mode 100644 dav_auth/django_project_config/settings-dav_auth.py create mode 100644 dav_auth/emails.py rename dav_events/forms/auth.py => dav_auth/forms.py (100%) create mode 100644 dav_auth/module.json create mode 100644 dav_auth/templates/dav_auth/base.html rename {dav_events/templates/dav_events => dav_auth/templates/dav_auth}/emails/password_set.txt (59%) rename dav_events/templates/dav_events/auth/login_form.html => dav_auth/templates/dav_auth/forms/login.html (79%) rename dav_events/templates/dav_events/auth/reset_password_form.html => dav_auth/templates/dav_auth/forms/reset_password.html (89%) rename dav_events/templates/dav_events/auth/set_password_form.html => dav_auth/templates/dav_auth/forms/set_password.html (89%) rename {dav_events/templates/dav_events => dav_auth/templates/dav_auth}/includes/login_widget.html (67%) create mode 100644 dav_auth/urls.py rename dav_events/views/auth.py => dav_auth/views.py (66%) create mode 100644 dav_base/__init__.py rename dav_events/console_scripts/__init__.py => dav_base/admin.py (100%) create mode 100644 dav_base/apps.py create mode 100644 dav_base/config/__init__.py create mode 100644 dav_base/config/apps.py create mode 100644 dav_base/config/modules.py create mode 100644 dav_base/console_scripts/__init__.py rename {dav_events => dav_base}/console_scripts/admin.py (67%) rename dav_events/console_scripts/Resources/django.main.additional_settings.py => dav_base/console_scripts/django_project_config/additional_settings.py (82%) create mode 100644 dav_base/console_scripts/django_project_config/settings-dav_base.py create mode 100644 dav_base/console_scripts/django_project_config/urls.py create mode 100644 dav_base/emails.py create mode 100644 dav_base/management/__init__.py create mode 100644 dav_base/management/commands/__init__.py create mode 100644 dav_base/management/commands/disable_module.py create mode 100644 dav_base/management/commands/enable_module.py create mode 100644 dav_base/management/commands/list_modules.py create mode 100644 dav_base/migrations/__init__.py create mode 100644 dav_base/models/__init__.py rename {dav_events/static/dav_events => dav_base/static/dav_base}/bootstrap/config.json (100%) rename {dav_events/static/dav_events => dav_base/static/dav_base}/bootstrap/css/bootstrap.css (100%) rename {dav_events/static/dav_events => dav_base/static/dav_base}/bootstrap/css/bootstrap.min.css (100%) rename {dav_events/static/dav_events => dav_base/static/dav_base}/bootstrap/fonts/glyphicons-halflings-regular.eot (100%) rename {dav_events/static/dav_events => dav_base/static/dav_base}/bootstrap/fonts/glyphicons-halflings-regular.svg (100%) rename {dav_events/static/dav_events => dav_base/static/dav_base}/bootstrap/fonts/glyphicons-halflings-regular.ttf (100%) rename {dav_events/static/dav_events => dav_base/static/dav_base}/bootstrap/fonts/glyphicons-halflings-regular.woff (100%) rename {dav_events/static/dav_events => dav_base/static/dav_base}/bootstrap/fonts/glyphicons-halflings-regular.woff2 (100%) rename {dav_events/static/dav_events => dav_base/static/dav_base}/bootstrap/js/bootstrap.js (100%) rename {dav_events/static/dav_events => dav_base/static/dav_base}/bootstrap/js/bootstrap.min.js (100%) rename {dav_events/static/dav_events => dav_base/static/dav_base}/css/dataTables.bootstrap.min.css (100%) rename {dav_events/static/dav_events => dav_base/static/dav_base}/css/local.css (100%) rename {dav_events/static/dav_events => dav_base/static/dav_base}/img/brand.png (100%) rename {dav_events/static/dav_events => dav_base/static/dav_base}/img/dav-favicon.ico (100%) rename {dav_events/static/dav_events => dav_base/static/dav_base}/js/jquery.dataTables.min.js (100%) rename {dav_events/static/dav_events => dav_base/static/dav_base}/js/jquery.min.js (100%) rename {dav_events => dav_base}/templates/400.html (84%) rename {dav_events => dav_base}/templates/403.html (84%) rename {dav_events => dav_base}/templates/404.html (83%) rename {dav_events => dav_base}/templates/500.html (84%) create mode 100644 dav_base/templates/dav_base/base.html rename {dav_events/templates => dav_base/templates/dav_base}/error_base.html (93%) create mode 100644 dav_base/templates/dav_base/root.html create mode 100644 dav_base/templates/project_name.html create mode 100644 dav_base/templatetags/__init__.py create mode 100644 dav_base/templatetags/dav_base.py create mode 100644 dav_base/urls.py create mode 100644 dav_base/views.py delete mode 100644 dav_events/console_scripts/Resources/django.main.urls.py rename dav_events/{console_scripts/Resources/django.main.settings-dav_events.py => django_project_config/settings-dav_events.py} (98%) create mode 100644 dav_events/module.json diff --git a/dav_auth/__init__.py b/dav_auth/__init__.py new file mode 100644 index 0000000..77b977e --- /dev/null +++ b/dav_auth/__init__.py @@ -0,0 +1 @@ +default_app_config = 'dav_auth.apps.AppConfig' diff --git a/dav_auth/apps.py b/dav_auth/apps.py new file mode 100644 index 0000000..5311b2b --- /dev/null +++ b/dav_auth/apps.py @@ -0,0 +1,12 @@ +from dav_base.config.apps import AppConfig as _AppConfig, DefaultSetting + +DEFAULT_SETTINGS = ( + DefaultSetting('login_redirect_url', 'root'), + DefaultSetting('logout_redirect_url', 'root'), +) + + +class AppConfig(_AppConfig): + name = 'dav_auth' + verbose_name = u'DAV Benutzerverwaltung' + default_settings = DEFAULT_SETTINGS diff --git a/dav_auth/django_project_config/settings-dav_auth.py b/dav_auth/django_project_config/settings-dav_auth.py new file mode 100644 index 0000000..99094ad --- /dev/null +++ b/dav_auth/django_project_config/settings-dav_auth.py @@ -0,0 +1,2 @@ +# LOGIN_REDIRECT_URL = 'root' +# LOGOUT_REDIRECT_URL = 'root' diff --git a/dav_auth/emails.py b/dav_auth/emails.py new file mode 100644 index 0000000..8448725 --- /dev/null +++ b/dav_auth/emails.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +from dav_base.emails import AbstractMail + + +class PasswordSetEmail(AbstractMail): + _subject = u'Zugangsdaten' + _template_name = 'dav_auth/emails/password_set.txt' + + def __init__(self, user, password): + self._user = user + self._password = password + + def _get_recipients(self): + r = u'{fullname} <{email}>'.format(fullname=self._user.get_full_name(), + email=self._user.email) + return [r] + + def _get_context_data(self, extra_context=None): + context = super(PasswordSetEmail, self)._get_context_data(extra_context=extra_context) + context.update({ + 'fullname': self._user.get_full_name(), + 'username': self._user.username, + 'password': self._password + }) + return context diff --git a/dav_events/forms/auth.py b/dav_auth/forms.py similarity index 100% rename from dav_events/forms/auth.py rename to dav_auth/forms.py diff --git a/dav_auth/module.json b/dav_auth/module.json new file mode 100644 index 0000000..2f1e776 --- /dev/null +++ b/dav_auth/module.json @@ -0,0 +1,3 @@ +{ + "url_prefix": "auth" +} \ No newline at end of file diff --git a/dav_auth/templates/dav_auth/base.html b/dav_auth/templates/dav_auth/base.html new file mode 100644 index 0000000..3da1e6b --- /dev/null +++ b/dav_auth/templates/dav_auth/base.html @@ -0,0 +1 @@ +{% extends "dav_base/base.html" %} \ No newline at end of file diff --git a/dav_events/templates/dav_events/emails/password_set.txt b/dav_auth/templates/dav_auth/emails/password_set.txt similarity index 59% rename from dav_events/templates/dav_events/emails/password_set.txt rename to dav_auth/templates/dav_auth/emails/password_set.txt index ffbb183..e5b3335 100644 --- a/dav_events/templates/dav_events/emails/password_set.txt +++ b/dav_auth/templates/dav_auth/emails/password_set.txt @@ -3,4 +3,4 @@ Hallo {{ fullname }}, Benutzername: {{ username }} Passwort: {{ password }} -URL: {{ base_url }}{% url 'dav_events:home' %} +URL: {{ base_url }}{% url 'root' %} diff --git a/dav_events/templates/dav_events/auth/login_form.html b/dav_auth/templates/dav_auth/forms/login.html similarity index 79% rename from dav_events/templates/dav_events/auth/login_form.html rename to dav_auth/templates/dav_auth/forms/login.html index 248a462..1214928 100644 --- a/dav_events/templates/dav_events/auth/login_form.html +++ b/dav_auth/templates/dav_auth/forms/login.html @@ -1,4 +1,4 @@ -{% extends "dav_events/base.html" %} +{% extends "dav_auth/base.html" %} {% load bootstrap3 %} {% load i18n %} @@ -15,13 +15,13 @@
{% csrf_token %} {% bootstrap_form form %} - + {% buttons %} - + {% bootstrap_icon 'remove' %}  {% trans 'Abbrechen' %} diff --git a/dav_events/templates/dav_events/auth/reset_password_form.html b/dav_auth/templates/dav_auth/forms/reset_password.html similarity index 89% rename from dav_events/templates/dav_events/auth/reset_password_form.html rename to dav_auth/templates/dav_auth/forms/reset_password.html index fa2c77d..007da89 100644 --- a/dav_events/templates/dav_events/auth/reset_password_form.html +++ b/dav_auth/templates/dav_auth/forms/reset_password.html @@ -1,4 +1,4 @@ -{% extends "dav_events/base.html" %} +{% extends "dav_auth/base.html" %} {% load bootstrap3 %} {% load i18n %} @@ -20,7 +20,7 @@ {% bootstrap_icon 'saved' %}  {% trans 'Neues Passwort per E-Mail zusenden' %} - + {% bootstrap_icon 'remove' %}  {% trans 'Abbrechen' %} diff --git a/dav_events/templates/dav_events/auth/set_password_form.html b/dav_auth/templates/dav_auth/forms/set_password.html similarity index 89% rename from dav_events/templates/dav_events/auth/set_password_form.html rename to dav_auth/templates/dav_auth/forms/set_password.html index a933a6e..d46536c 100644 --- a/dav_events/templates/dav_events/auth/set_password_form.html +++ b/dav_auth/templates/dav_auth/forms/set_password.html @@ -1,4 +1,4 @@ -{% extends "dav_events/base.html" %} +{% extends "dav_auth/base.html" %} {% load bootstrap3 %} {% load i18n %} @@ -20,7 +20,7 @@ {% bootstrap_icon 'saved' %}  {% trans 'Neues Passwort setzen' %} - + {% bootstrap_icon 'remove' %}  {% trans 'Abbrechen' %} diff --git a/dav_events/templates/dav_events/includes/login_widget.html b/dav_auth/templates/dav_auth/includes/login_widget.html similarity index 67% rename from dav_events/templates/dav_events/includes/login_widget.html rename to dav_auth/templates/dav_auth/includes/login_widget.html index f286bba..b19180d 100644 --- a/dav_events/templates/dav_events/includes/login_widget.html +++ b/dav_auth/templates/dav_auth/includes/login_widget.html @@ -7,12 +7,12 @@ {{ user }}  {% else %} - + {% bootstrap_icon 'log-in' %}  {% trans 'Login' %} diff --git a/dav_auth/urls.py b/dav_auth/urls.py new file mode 100644 index 0000000..14bd7fb --- /dev/null +++ b/dav_auth/urls.py @@ -0,0 +1,10 @@ +from django.conf.urls import url + +from . import views + +urlpatterns = [ + url(r'^login$', views.LoginView.as_view(), name='login'), + url(r'^logout$', views.LogoutView.as_view(), name='logout'), + url(r'^password$', views.SetPasswordView.as_view(), name='set_password'), + url(r'^password/reset$', views.ResetPasswordView.as_view(), name='reset_password'), +] diff --git a/dav_events/views/auth.py b/dav_auth/views.py similarity index 66% rename from dav_events/views/auth.py rename to dav_auth/views.py index 728e1da..dd12c3c 100644 --- a/dav_events/views/auth.py +++ b/dav_auth/views.py @@ -1,24 +1,28 @@ import logging +from django.apps import apps from django.contrib import messages from django.contrib.auth import views as auth_views, get_user_model +from django.shortcuts import resolve_url from django.urls import reverse_lazy from django.utils.translation import ugettext as _ from django.views import generic -from .. import emails -from .. import forms +from . import emails +from . import forms +app_config = apps.get_containing_app_config(__package__) logger = logging.getLogger(__name__) class LoginView(auth_views.LoginView): - form_class = forms.auth.LoginForm - next_page = reverse_lazy('dav_events:event_list') - template_name = 'dav_events/auth/login_form.html' + form_class = forms.LoginForm + template_name = 'dav_auth/forms/login.html' - def get_success_url(self): - url = self.get_redirect_url() - return url or self.next_page + def get_redirect_url(self): + url = super(LoginView, self).get_redirect_url() + if not url and app_config.settings.login_redirect_url: + url = resolve_url(app_config.settings.login_redirect_url) + return url def form_valid(self, form): r = super(LoginView, self).form_valid(form) @@ -27,7 +31,11 @@ class LoginView(auth_views.LoginView): class LogoutView(auth_views.LogoutView): - next_page = reverse_lazy('dav_events:home') + def get_next_page(self): + url = super(LogoutView, self).get_next_page() + if not url and app_config.settings.logout_redirect_url: + url = resolve_url(app_config.settings.logout_redirect_url) + return url def dispatch(self, request, *args, **kwargs): r = super(LogoutView, self).dispatch(request, *args, **kwargs) @@ -36,9 +44,11 @@ class LogoutView(auth_views.LogoutView): class SetPasswordView(auth_views.PasswordChangeView): - form_class = forms.auth.SetPasswordForm - template_name = 'dav_events/auth/set_password_form.html' - success_url = reverse_lazy('dav_events:event_list') + form_class = forms.SetPasswordForm + template_name = 'dav_auth/forms/set_password.html' + + def get_success_url(self): + return resolve_url(app_config.settings.login_redirect_url) def form_valid(self, form): r = super(SetPasswordView, self).form_valid(form) @@ -50,9 +60,9 @@ class SetPasswordView(auth_views.PasswordChangeView): class ResetPasswordView(generic.FormView): - form_class = forms.auth.ResetPasswordForm - template_name = 'dav_events/auth/reset_password_form.html' - success_url = reverse_lazy('dav_events:login') + form_class = forms.ResetPasswordForm + template_name = 'dav_auth/forms/reset_password.html' + success_url = reverse_lazy('dav_auth:login') def form_valid(self, form): username = form.cleaned_data.get('username') diff --git a/dav_base/__init__.py b/dav_base/__init__.py new file mode 100644 index 0000000..5467fdc --- /dev/null +++ b/dav_base/__init__.py @@ -0,0 +1 @@ +default_app_config = 'dav_base.apps.AppConfig' diff --git a/dav_events/console_scripts/__init__.py b/dav_base/admin.py similarity index 100% rename from dav_events/console_scripts/__init__.py rename to dav_base/admin.py diff --git a/dav_base/apps.py b/dav_base/apps.py new file mode 100644 index 0000000..e0ad555 --- /dev/null +++ b/dav_base/apps.py @@ -0,0 +1,13 @@ +from .config.apps import AppConfig as _AppConfig, DefaultSetting + +DEFAULT_SETTINGS = ( + DefaultSetting('email_sender', None), + DefaultSetting('email_base_url', None), + DefaultSetting('email_subject_prefix', ''), +) + + +class AppConfig(_AppConfig): + name = 'dav_base' + verbose_name = u'DAV Base App' + default_settings = DEFAULT_SETTINGS diff --git a/dav_base/config/__init__.py b/dav_base/config/__init__.py new file mode 100644 index 0000000..8b9aad3 --- /dev/null +++ b/dav_base/config/__init__.py @@ -0,0 +1,2 @@ +from . import apps +from . import modules diff --git a/dav_base/config/apps.py b/dav_base/config/apps.py new file mode 100644 index 0000000..b86682a --- /dev/null +++ b/dav_base/config/apps.py @@ -0,0 +1,71 @@ +import importlib +import logging +import re +from django.apps import AppConfig as _AppConfig +from django.core.exceptions import ImproperlyConfigured + +logger = logging.getLogger(__name__) + + +class DefaultSetting(object): + def __init__(self, name, value, key_name=None, validator=None): + self.name = name + self.value = value + if key_name is None: + self.key_name = self.name.upper() + else: + self.key_name = key_name + self.validator = validator + + def validate(self, value): + if hasattr(self, 'validator') and self.validator is not None: + if callable(self.validator): + if not self.validator(value): + raise ImproperlyConfigured("Validator callback {clb} returned False".format(clb=self.validator)) + else: + if not re.search(self.validator, value): + raise ImproperlyConfigured("Does not match /{re}/".format(re=self.validator)) + + +class AppSettings(object): + def __init__(self, app_name, defaults): + settings_name = 'main.settings-' + app_name + + try: + settings_module = importlib.import_module(settings_name) + except ImportError: + settings_module = None + + for default in defaults: + if hasattr(settings_module, default.key_name): + value = getattr(settings_module, default.key_name) + try: + default.validate(value) + except ImproperlyConfigured as e: + msg = 'Invalid value of {key} in {module}: {cause}'.format(key=default.key_name, + module=settings_name, + cause=e.message) + raise ImproperlyConfigured(msg) + setattr(self, default.name, value) + elif isinstance(default.value, ImproperlyConfigured): + raise default.value + else: + try: + is_impconf = issubclass(default.value, ImproperlyConfigured) + except TypeError: + is_impconf = False + + if is_impconf: + msg = '{key} must be defined in {module}'.format(key=default.key_name, + module=settings_name) + raise default.value(msg) + else: + setattr(self, default.name, default.value) + + +class AppConfig(_AppConfig): + default_settings = () + + def __init__(self, app_name, app_module): + super(AppConfig, self).__init__(app_name, app_module) + self.settings = AppSettings(app_name, self.default_settings) diff --git a/dav_base/config/modules.py b/dav_base/config/modules.py new file mode 100644 index 0000000..ed5ae00 --- /dev/null +++ b/dav_base/config/modules.py @@ -0,0 +1,132 @@ +import json +import os +import pkg_resources + +from django.conf import settings +from django.conf.urls import url as django_conf_url, include + +DJANGO_MAIN_MODULE = 'main' +MODULE_CONFIG_FILE_NAME = 'module_config.json' + + +class ModuleConfigError(Exception): + pass + + +class ModuleMeta(object): + _json_file = 'module.json' + _root_url_name = 'root' + + def __init__(self, package_name): + self._package_name = package_name + self._additional_apps = [] + self._url_prefix = None + self._load_from_package() + + def __str__(self): + t = '- {}'.format(self._package_name) + return t + + @property + def app(self): + return self._package_name + + @property + def additional_apps(self): + return self._additional_apps + + @property + def url_conf_pattern(self): + url_pattern = '^' + if self._url_prefix is None: + url_pattern += self._package_name + else: + url_pattern += self._url_prefix + url_pattern += '/' + url_conf = self._package_name + '.urls' + return django_conf_url(url_pattern, include(url_conf, self.url_namespace)) + + @property + def url_namespace(self): + return self._package_name.replace('.', '_') + + def _load_from_package(self): + package_name = self._package_name + json_text = pkg_resources.resource_string(package_name, self._json_file) + meta_dict = json.loads(json_text) + meta_dict['package'] = package_name + self.load_from_dict(meta_dict) + + def load_from_dict(self, meta_dict): + self._package_name = meta_dict.get('package', None) + self._additional_apps = meta_dict.get('additional_apps', []) + self._url_prefix = meta_dict.get('url_prefix', None) + + def dump_as_dict(self): + d = { + 'package': self._package_name, + } + if self._additional_apps: + d['additional_apps'] = self._additional_apps + if self._url_prefix: + d['url_prefix'] = self._url_prefix + return d + + +class ModuleConfig(object): + _lazy_load = True + + def __init__(self, config_file_path=None, django_base_dir=None): + if config_file_path is None: + if django_base_dir is None: + django_base_dir = settings.BASE_DIR + config_file_path = os.path.join(django_base_dir, DJANGO_MAIN_MODULE, MODULE_CONFIG_FILE_NAME) + self._config_file_path = config_file_path + + self._modules = dict() + + self._loaded = False + if not self._lazy_load: + self._load() + + def _lazy_init(self): + if not self._loaded: + self._load() + + def _load(self): + path = self._config_file_path + + self._modules = dict() + + with open(path, 'r') as f: + data = json.load(f) + + if 'modules' in data: + for meta_dict in data['modules']: + module_name = meta_dict['package'] + self._modules[module_name] = ModuleMeta(module_name) + self._modules[module_name].load_from_dict(meta_dict) + + self._loaded = True + + @property + def modules(self): + self._lazy_init() + return self._modules + + def save(self): + path = self._config_file_path + + if os.path.exists(path): + self._lazy_init() + else: + self._loaded = True + + data = { + 'modules': [], + } + for meta_obj in self._modules.values(): + data['modules'].append(meta_obj.dump_as_dict()) + + with open(path, 'w') as f: + json.dump(data, f, indent=4) diff --git a/dav_base/console_scripts/__init__.py b/dav_base/console_scripts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dav_events/console_scripts/admin.py b/dav_base/console_scripts/admin.py similarity index 67% rename from dav_events/console_scripts/admin.py rename to dav_base/console_scripts/admin.py index 1afba9b..0db8cb1 100644 --- a/dav_events/console_scripts/admin.py +++ b/dav_base/console_scripts/admin.py @@ -4,13 +4,15 @@ import pkg_resources import posix import sys +from dav_base.config.modules import DJANGO_MAIN_MODULE, ModuleConfig + VERSION = '0.1' class AdminCommand(object): def _setup_argparser(self): kwargs = { - 'description': 'Tool to manage the DAV Events django project installation.', + 'description': 'Tool to manage the DAV django project installation.', 'epilog': 'Off Belay.', } parser = argparse.ArgumentParser(**kwargs) @@ -36,30 +38,31 @@ class AdminCommand(object): return self._argparser.parse_args(argv) def _subcmd_setup(self, cmd_args): - django_project_name = 'main' - django_project_path = cmd_args.path + django_main_module = DJANGO_MAIN_MODULE + django_base_dir = cmd_args.path - sys.stdout.write('Setup installation in \'{path}\'...\n'.format(path=django_project_path)) + sys.stdout.write('Setup installation in \'{path}\'...\n'.format(path=django_base_dir)) - if os.path.exists(django_project_path): - if not os.path.isdir(django_project_path): - sys.stderr.write('{path}: Not a directory.\n'.format(path=django_project_path)) + if os.path.exists(django_base_dir): + if not os.path.isdir(django_base_dir): + sys.stderr.write('{path}: Not a directory.\n'.format(path=django_base_dir)) return posix.EX_USAGE else: - os.makedirs(django_project_path) + os.makedirs(django_base_dir) sys.stdout.write('Creating django project...\n') - django_cmd = 'django-admin startproject {name} "{path}"'.format(name=django_project_name, - path=django_project_path) + django_cmd = 'django-admin startproject {name} "{path}"'.format(name=django_main_module, + path=django_base_dir) exitval = os.system(django_cmd) if exitval != posix.EX_OK: return exitval sys.stdout.write('Creating directories...\n') dirs = [ - os.path.join(django_project_path, 'var', 'db'), - os.path.join(django_project_path, 'var', 'log'), - os.path.join(django_project_path, 'var', 'www', 'static'), + os.path.join(django_base_dir, 'common', 'templates'), + os.path.join(django_base_dir, 'var', 'db'), + os.path.join(django_base_dir, 'var', 'log'), + os.path.join(django_base_dir, 'var', 'www', 'static'), ] for d in dirs: @@ -68,18 +71,21 @@ class AdminCommand(object): sys.stdout.write('Configure django project...\n') - input_file = os.path.join('Resources', 'django.main.additional_settings.py') - output_file = os.path.join(django_project_path, django_project_name, 'settings.py') + config = ModuleConfig(django_base_dir=django_base_dir) + config.save() + + input_file = os.path.join('django_project_config', 'additional_settings.py') + output_file = os.path.join(django_base_dir, django_main_module, 'settings.py') with open(output_file, 'a') as f: f.write(pkg_resources.resource_string(__package__, input_file)) - input_file = os.path.join('Resources', 'django.main.urls.py') - output_file = os.path.join(django_project_path, django_project_name, 'urls.py') + input_file = os.path.join('django_project_config', 'urls.py') + output_file = os.path.join(django_base_dir, django_main_module, 'urls.py') with open(output_file, 'w') as f: f.write(pkg_resources.resource_string(__package__, input_file)) - input_file = os.path.join('Resources', 'django.main.settings-dav_events.py') - output_file = os.path.join(django_project_path, django_project_name, 'settings-dav_events.py') + input_file = os.path.join('django_project_config', 'settings-dav_base.py') + output_file = os.path.join(django_base_dir, django_main_module, 'settings-dav_base.py') with open(output_file, 'w') as f: f.write(pkg_resources.resource_string(__package__, input_file)) diff --git a/dav_events/console_scripts/Resources/django.main.additional_settings.py b/dav_base/console_scripts/django_project_config/additional_settings.py similarity index 82% rename from dav_events/console_scripts/Resources/django.main.additional_settings.py rename to dav_base/console_scripts/django_project_config/additional_settings.py index 488eb5a..d32b9f9 100644 --- a/dav_events/console_scripts/Resources/django.main.additional_settings.py +++ b/dav_base/console_scripts/django_project_config/additional_settings.py @@ -1,20 +1,33 @@ # -# Additional settings for django-dav-events +# Additional settings for django-dav # BASE_VAR_DIR = os.path.join(BASE_DIR, 'var') BASE_SHARE_DIR = os.path.join(BASE_DIR, 'common') +# Get modules config +from dav_base.config.modules import ModuleConfig +MODULE_CONFIG = ModuleConfig() + INSTALLED_APPS += [ 'bootstrap3', 'datetimewidget', 'django_countries', 'django_extensions', # Our main app - 'dav_events', + 'dav_base', ] +# Add apps from our modules +for module_meta_obj in MODULE_CONFIG.modules.values(): + if module_meta_obj.app: + INSTALLED_APPS.append(module_meta_obj.app) + if module_meta_obj.additional_apps: + for app in module_meta_obj.additional_apps: + INSTALLED_APPS.append(app) + +# Add a template engine without html auto escape for rendering plain text templates. TEMPLATES += [ { 'NAME': 'PLAINTEXT', @@ -30,6 +43,10 @@ TEMPLATES += [ }, ] +# Add our local templates directory to the template engine configurations. +for config in TEMPLATES: + config['DIRS'].append(os.path.join(BASE_SHARE_DIR, 'templates')) + DATABASES['default'] = { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_VAR_DIR, 'db', 'devel.sqlite3'), @@ -39,7 +56,7 @@ STATIC_ROOT = os.path.join(BASE_VAR_DIR, 'www', 'static') LANGUAGE_CODE = 'de' -LOGIN_URL = 'dav_events:login' +LOGIN_URL = 'dav_auth:login' BOOTSTRAP3 = { 'set_placeholder': False, diff --git a/dav_base/console_scripts/django_project_config/settings-dav_base.py b/dav_base/console_scripts/django_project_config/settings-dav_base.py new file mode 100644 index 0000000..3833852 --- /dev/null +++ b/dav_base/console_scripts/django_project_config/settings-dav_base.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- + +# E-Mails +EMAIL_SENDER = 'DAV heinzel ' +EMAIL_BASE_URL = 'http://localhost:8000' +EMAIL_SUBJECT_PREFIX = u'[DAV heinzel]' diff --git a/dav_base/console_scripts/django_project_config/urls.py b/dav_base/console_scripts/django_project_config/urls.py new file mode 100644 index 0000000..9b05ae5 --- /dev/null +++ b/dav_base/console_scripts/django_project_config/urls.py @@ -0,0 +1,12 @@ +from django.conf import settings +from django.conf.urls import url, include + +urlpatterns = [] + +for module_meta_obj in settings.MODULE_CONFIG.modules.values(): + if module_meta_obj.url_conf_pattern: + urlpatterns.append(module_meta_obj.url_conf_pattern) + +urlpatterns = [ + url(r'^', include('dav_base.urls')) +] diff --git a/dav_base/emails.py b/dav_base/emails.py new file mode 100644 index 0000000..a92d848 --- /dev/null +++ b/dav_base/emails.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +import logging +from django.apps import apps +from django.core.exceptions import ImproperlyConfigured +from django.core.mail import EmailMessage +from django.template.loader import get_template + +app_config = apps.get_containing_app_config(__package__) +logger = logging.getLogger(__name__) + + +class AbstractMail(object): + _subject = u'' + _template_name = None + + def _get_sender(self): + #app_config = apps.get_containing_app_config(__package__) + return app_config.settings.email_sender + + def _get_subject(self, subject_fmt=None, **kwargs): + if subject_fmt is None: + subject_fmt = self._subject + #app_config = apps.get_containing_app_config(__package__) + if app_config.settings.email_subject_prefix: + subject_fmt = u'%s %s' % (app_config.settings.email_subject_prefix, subject_fmt) + subject = subject_fmt.format(**kwargs) + return subject + + def _get_template(self): + if not self._template_name: + raise ImproperlyConfigured('%s._template_name ist not set.', self.__class__.__name__) + return get_template(self._template_name, using='PLAINTEXT') + + def _get_context_data(self, extra_context=None): + #app_config = apps.get_containing_app_config(__package__) + context = { + 'base_url': app_config.settings.email_base_url, + } + if extra_context: + context.update(extra_context) + return context + + def _get_body(self, context=None): + template = self._get_template() + context = self._get_context_data(extra_context=context) + return template.render(context) + + def _get_recipients(self): + raise NotImplementedError() + + def send(self): + subject = self._get_subject() + body = self._get_body() + sender = self._get_sender() + recipients = self._get_recipients() + + email = EmailMessage(subject=subject, body=body, from_email=sender, to=recipients) + logger.info(u'Send %s to %s', self.__class__.__name__, recipients) + email.send() diff --git a/dav_base/management/__init__.py b/dav_base/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dav_base/management/commands/__init__.py b/dav_base/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dav_base/management/commands/disable_module.py b/dav_base/management/commands/disable_module.py new file mode 100644 index 0000000..e3cfef7 --- /dev/null +++ b/dav_base/management/commands/disable_module.py @@ -0,0 +1,18 @@ +from django.conf import settings +from django.core.management.base import BaseCommand + + +class Command(BaseCommand): + help = 'Disable a modular app from django-dav installation.' + + def add_arguments(self, parser): + parser.add_argument('module') + + def handle(self, *args, **options): + module_name = options['module'] + + config = settings.MODULE_CONFIG + del config.modules[module_name] + config.save() + + self.stdout.write(self.style.SUCCESS('Module \'{}\' disabled.'.format(module_name))) diff --git a/dav_base/management/commands/enable_module.py b/dav_base/management/commands/enable_module.py new file mode 100644 index 0000000..fb15a76 --- /dev/null +++ b/dav_base/management/commands/enable_module.py @@ -0,0 +1,37 @@ +import os +import pkg_resources +from django.conf import settings +from django.core.management.base import BaseCommand, CommandError + +from dav_base.config.modules import DJANGO_MAIN_MODULE, ModuleMeta + + +class Command(BaseCommand): + help = 'Enable a modular app in django-dav installation.' + + def add_arguments(self, parser): + parser.add_argument('module', help='the name of the module') + + def handle(self, *args, **options): + django_base_dir = settings.BASE_DIR + django_main_module = DJANGO_MAIN_MODULE + module_name = options['module'] + + config = settings.MODULE_CONFIG + + if module_name in config.modules.keys(): + raise CommandError('Module \'{}\' is already enabled'.format(module_name)) + + settings_file_name = 'settings-{}.py'.format(module_name) + input_file = os.path.join('django_project_config', settings_file_name) + if pkg_resources.resource_exists(module_name, input_file): + output_file = os.path.join(django_base_dir, django_main_module, settings_file_name) + if not os.path.exists(output_file): + with open(output_file, 'w') as f: + f.write(pkg_resources.resource_string(module_name, input_file)) + + module_meta_obj = ModuleMeta(module_name) + config.modules[module_name] = module_meta_obj + config.save() + + self.stdout.write(self.style.SUCCESS('Module \'{}\' enabled.'.format(module_name))) diff --git a/dav_base/management/commands/list_modules.py b/dav_base/management/commands/list_modules.py new file mode 100644 index 0000000..8299464 --- /dev/null +++ b/dav_base/management/commands/list_modules.py @@ -0,0 +1,17 @@ +from django.conf import settings +from django.core.management.base import BaseCommand + + +class Command(BaseCommand): + help = 'List enabled modular apps in django-dav installation.' + + def add_arguments(self, parser): + pass + + def handle(self, *args, **options): + paragraphs = [] + config = settings.MODULE_CONFIG + for obj in config.modules.values(): + paragraphs.append(str(obj)) + output = '\n'.join(paragraphs) + self.stdout.write(output) diff --git a/dav_base/migrations/__init__.py b/dav_base/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dav_base/models/__init__.py b/dav_base/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dav_events/static/dav_events/bootstrap/config.json b/dav_base/static/dav_base/bootstrap/config.json similarity index 100% rename from dav_events/static/dav_events/bootstrap/config.json rename to dav_base/static/dav_base/bootstrap/config.json diff --git a/dav_events/static/dav_events/bootstrap/css/bootstrap.css b/dav_base/static/dav_base/bootstrap/css/bootstrap.css similarity index 100% rename from dav_events/static/dav_events/bootstrap/css/bootstrap.css rename to dav_base/static/dav_base/bootstrap/css/bootstrap.css diff --git a/dav_events/static/dav_events/bootstrap/css/bootstrap.min.css b/dav_base/static/dav_base/bootstrap/css/bootstrap.min.css similarity index 100% rename from dav_events/static/dav_events/bootstrap/css/bootstrap.min.css rename to dav_base/static/dav_base/bootstrap/css/bootstrap.min.css diff --git a/dav_events/static/dav_events/bootstrap/fonts/glyphicons-halflings-regular.eot b/dav_base/static/dav_base/bootstrap/fonts/glyphicons-halflings-regular.eot similarity index 100% rename from dav_events/static/dav_events/bootstrap/fonts/glyphicons-halflings-regular.eot rename to dav_base/static/dav_base/bootstrap/fonts/glyphicons-halflings-regular.eot diff --git a/dav_events/static/dav_events/bootstrap/fonts/glyphicons-halflings-regular.svg b/dav_base/static/dav_base/bootstrap/fonts/glyphicons-halflings-regular.svg similarity index 100% rename from dav_events/static/dav_events/bootstrap/fonts/glyphicons-halflings-regular.svg rename to dav_base/static/dav_base/bootstrap/fonts/glyphicons-halflings-regular.svg diff --git a/dav_events/static/dav_events/bootstrap/fonts/glyphicons-halflings-regular.ttf b/dav_base/static/dav_base/bootstrap/fonts/glyphicons-halflings-regular.ttf similarity index 100% rename from dav_events/static/dav_events/bootstrap/fonts/glyphicons-halflings-regular.ttf rename to dav_base/static/dav_base/bootstrap/fonts/glyphicons-halflings-regular.ttf diff --git a/dav_events/static/dav_events/bootstrap/fonts/glyphicons-halflings-regular.woff b/dav_base/static/dav_base/bootstrap/fonts/glyphicons-halflings-regular.woff similarity index 100% rename from dav_events/static/dav_events/bootstrap/fonts/glyphicons-halflings-regular.woff rename to dav_base/static/dav_base/bootstrap/fonts/glyphicons-halflings-regular.woff diff --git a/dav_events/static/dav_events/bootstrap/fonts/glyphicons-halflings-regular.woff2 b/dav_base/static/dav_base/bootstrap/fonts/glyphicons-halflings-regular.woff2 similarity index 100% rename from dav_events/static/dav_events/bootstrap/fonts/glyphicons-halflings-regular.woff2 rename to dav_base/static/dav_base/bootstrap/fonts/glyphicons-halflings-regular.woff2 diff --git a/dav_events/static/dav_events/bootstrap/js/bootstrap.js b/dav_base/static/dav_base/bootstrap/js/bootstrap.js similarity index 100% rename from dav_events/static/dav_events/bootstrap/js/bootstrap.js rename to dav_base/static/dav_base/bootstrap/js/bootstrap.js diff --git a/dav_events/static/dav_events/bootstrap/js/bootstrap.min.js b/dav_base/static/dav_base/bootstrap/js/bootstrap.min.js similarity index 100% rename from dav_events/static/dav_events/bootstrap/js/bootstrap.min.js rename to dav_base/static/dav_base/bootstrap/js/bootstrap.min.js diff --git a/dav_events/static/dav_events/css/dataTables.bootstrap.min.css b/dav_base/static/dav_base/css/dataTables.bootstrap.min.css similarity index 100% rename from dav_events/static/dav_events/css/dataTables.bootstrap.min.css rename to dav_base/static/dav_base/css/dataTables.bootstrap.min.css diff --git a/dav_events/static/dav_events/css/local.css b/dav_base/static/dav_base/css/local.css similarity index 100% rename from dav_events/static/dav_events/css/local.css rename to dav_base/static/dav_base/css/local.css diff --git a/dav_events/static/dav_events/img/brand.png b/dav_base/static/dav_base/img/brand.png similarity index 100% rename from dav_events/static/dav_events/img/brand.png rename to dav_base/static/dav_base/img/brand.png diff --git a/dav_events/static/dav_events/img/dav-favicon.ico b/dav_base/static/dav_base/img/dav-favicon.ico similarity index 100% rename from dav_events/static/dav_events/img/dav-favicon.ico rename to dav_base/static/dav_base/img/dav-favicon.ico diff --git a/dav_events/static/dav_events/js/jquery.dataTables.min.js b/dav_base/static/dav_base/js/jquery.dataTables.min.js similarity index 100% rename from dav_events/static/dav_events/js/jquery.dataTables.min.js rename to dav_base/static/dav_base/js/jquery.dataTables.min.js diff --git a/dav_events/static/dav_events/js/jquery.min.js b/dav_base/static/dav_base/js/jquery.min.js similarity index 100% rename from dav_events/static/dav_events/js/jquery.min.js rename to dav_base/static/dav_base/js/jquery.min.js diff --git a/dav_events/templates/400.html b/dav_base/templates/400.html similarity index 84% rename from dav_events/templates/400.html rename to dav_base/templates/400.html index 7e12ab1..72d4509 100644 --- a/dav_events/templates/400.html +++ b/dav_base/templates/400.html @@ -1,4 +1,4 @@ -{% extends "error_base.html" %} +{% extends "dav_base/error_base.html" %} {% block error-code %}400{% endblock %} {% block error-title %}Bad Request{% endblock %} diff --git a/dav_events/templates/403.html b/dav_base/templates/403.html similarity index 84% rename from dav_events/templates/403.html rename to dav_base/templates/403.html index 8e459e2..1da7207 100644 --- a/dav_events/templates/403.html +++ b/dav_base/templates/403.html @@ -1,4 +1,4 @@ -{% extends "error_base.html" %} +{% extends "dav_base/error_base.html" %} {% block error-code %}403{% endblock %} {% block error-title %}Forbidden{% endblock %} diff --git a/dav_events/templates/404.html b/dav_base/templates/404.html similarity index 83% rename from dav_events/templates/404.html rename to dav_base/templates/404.html index dd5e3d0..58ee5a2 100644 --- a/dav_events/templates/404.html +++ b/dav_base/templates/404.html @@ -1,4 +1,4 @@ -{% extends "error_base.html" %} +{% extends "dav_base/error_base.html" %} {% block error-code %}404{% endblock %} {% block error-title %}Not Found{% endblock %} diff --git a/dav_events/templates/500.html b/dav_base/templates/500.html similarity index 84% rename from dav_events/templates/500.html rename to dav_base/templates/500.html index adcdb98..65bcc31 100644 --- a/dav_events/templates/500.html +++ b/dav_base/templates/500.html @@ -1,4 +1,4 @@ -{% extends "error_base.html" %} +{% extends "dav_base/error_base.html" %} {% block error-code %}500{% endblock %} {% block error-title %}Internal Server Error{% endblock %} diff --git a/dav_base/templates/dav_base/base.html b/dav_base/templates/dav_base/base.html new file mode 100644 index 0000000..6d1e909 --- /dev/null +++ b/dav_base/templates/dav_base/base.html @@ -0,0 +1,72 @@ + +{% load static %} +{% load i18n %} +{% load bootstrap3 %} +{% load dav_base %} + + + + + + {% block head-media %} + + + + + + + + + + {{ form.media }} + {% endblock head-media %} + + {% block head-additional %} + {% endblock head-additional %} + + + {% block head-title %}Alpenverein Karlsruhe{% endblock head-title %} + + + +
+ + +
+ {% block messages %} +
+ {% bootstrap_messages %} +
+ {% endblock messages %} +
+ +
+ {% block modals %} + {% endblock modals %} + {% block page-body %} +
+ {% block page-container-fluid %} + {% endblock page-container-fluid %} +
+
+ {% block page-container %} + {% endblock page-container %} +
+ {% endblock page-body %} +
+ + +
+ + diff --git a/dav_events/templates/error_base.html b/dav_base/templates/dav_base/error_base.html similarity index 93% rename from dav_events/templates/error_base.html rename to dav_base/templates/dav_base/error_base.html index bf30f02..795ebe7 100644 --- a/dav_events/templates/error_base.html +++ b/dav_base/templates/dav_base/error_base.html @@ -1,4 +1,4 @@ -{% extends "dav_events/base.html" %} +{% extends "dav_base/base.html" %} {% load bootstrap3 %} {% block head-title %}{% block error-code %}{% endblock %} {% block error-title %}Error{% endblock %} - {{ block.super }}{% endblock head-title %} diff --git a/dav_base/templates/dav_base/root.html b/dav_base/templates/dav_base/root.html new file mode 100644 index 0000000..fd2c078 --- /dev/null +++ b/dav_base/templates/dav_base/root.html @@ -0,0 +1,27 @@ +{% extends "dav_base/base.html" %} +{% load bootstrap3 %} +{% load i18n %} + +{% block page-container %} +
+

Hallo,

+

+ du bist auf dem Entwicklungs- und Testserver der + Sektion Karlsruhe des Deutschen Alpenvereins (DAV) e.V. gelandet. +

+

+ Wenn du Fragen hast, kannst du dich an + heinzel@alpenverein-karlsruhe.de wenden. +

+

 

+ {% if root_urls %} +

Module:

+
+ {% for root_url in root_urls %} + {{ root_url.0 }} + {% endfor %} +
+ {% endif %} +
+{% endblock page-container %} diff --git a/dav_base/templates/project_name.html b/dav_base/templates/project_name.html new file mode 100644 index 0000000..e69de29 diff --git a/dav_base/templatetags/__init__.py b/dav_base/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dav_base/templatetags/dav_base.py b/dav_base/templatetags/dav_base.py new file mode 100644 index 0000000..b8f9b3c --- /dev/null +++ b/dav_base/templatetags/dav_base.py @@ -0,0 +1,53 @@ +from django import template + +register = template.Library() + + +@register.tag('include_if_exist') +def do_include_if_exist(parser, token): + """ + Used to include a template, that maybe does not exist. + + include_if_exist support an optional keyword 'default', which must be followed by the name of a default template. + The default template will be included, if the first template does not exist. + If also the default template does not exist, the behaviour is the same as for the original (builtin) include tag. + + If no default template is given and the first template does not exist an empty string will be returned. + """ + bits = token.split_contents() + if len(bits) < 2: + raise template.TemplateSyntaxError("%r tag takes at least two arguments:" + " the name of the template to be included" % bits[0]) + + try: + pos = bits.index('default') + del bits[pos] + default_template = bits[pos] + del bits[pos] + token = template.base.Token(token.token_type, ' '.join(bits)) + token2 = template.base.Token(token.token_type, bits[0] + ' ' + default_template + ' '.join(bits[2:])) + default_node = template.loader_tags.do_include(parser, token2) + except ValueError: + default_node = template.defaulttags.CommentNode() + except IndexError: + raise template.TemplateSyntaxError("'default' keyword in %r tag requires another arguments:" + " the name of the default template" % bits[0]) + + try: + include_node = template.loader_tags.do_include(parser, token) + except template.TemplateDoesNotExist: + return default_node + + orig_render = include_node.render + orig_template = include_node.template + + def wrapped_render(context, *args, **kwargs): + try: + t = orig_template.resolve(context) + if not callable(getattr(t, 'render', None)): + t = context.template.engine.find_template(t) + return orig_render(context, *args, **kwargs) + except template.TemplateDoesNotExist: + return default_node.render(context, *args, **kwargs) + include_node.render = wrapped_render + return include_node diff --git a/dav_base/urls.py b/dav_base/urls.py new file mode 100644 index 0000000..70c5d83 --- /dev/null +++ b/dav_base/urls.py @@ -0,0 +1,9 @@ +from django.conf.urls import url +from django.contrib import admin + +from . import views + +urlpatterns = [ + url(r'^$', views.RootView.as_view(), name='root'), + url(r'^djangoadmin/', admin.site.urls), +] diff --git a/dav_base/views.py b/dav_base/views.py new file mode 100644 index 0000000..731ea31 --- /dev/null +++ b/dav_base/views.py @@ -0,0 +1,23 @@ +from django.conf import settings +from django.urls import reverse, NoReverseMatch +from django.views import generic + + +class RootView(generic.TemplateView): + template_name = 'dav_base/root.html' + + def get_context_data(self, **kwargs): + c = super(RootView, self).get_context_data(**kwargs) + root_urls = [] + for module_meta_obj in settings.MODULE_CONFIG.modules.values(): + root_url_name = 'root' + if module_meta_obj.url_namespace: + root_url_name = '%s:%s' % (module_meta_obj.url_namespace, root_url_name) + try: + reverse(root_url_name) + root_urls.append((module_meta_obj.app, root_url_name)) + except NoReverseMatch: + pass + + c['root_urls'] = root_urls + return c diff --git a/dav_events/apps.py b/dav_events/apps.py index 8dfbc2a..39d8f06 100644 --- a/dav_events/apps.py +++ b/dav_events/apps.py @@ -1,13 +1,11 @@ from django.core.exceptions import ImproperlyConfigured +from dav_base.config.apps import AppConfig as _AppConfig, DefaultSetting + from . import signals -from .config import AppConfig as _AppConfig, DefaultSetting DEFAULT_SETTINGS = ( DefaultSetting('enable_email_notifications', False), - DefaultSetting('email_sender', None), - DefaultSetting('email_base_url', None), - DefaultSetting('email_subject_prefix', ''), DefaultSetting('group_manage_all', None), DefaultSetting('group_manage_w', None), DefaultSetting('group_manage_s', None), diff --git a/dav_events/config.py b/dav_events/config.py index ca862b4..de43024 100644 --- a/dav_events/config.py +++ b/dav_events/config.py @@ -1,8 +1,5 @@ -import importlib import logging import re -from django.apps import AppConfig as _AppConfig -from django.core.exceptions import ImproperlyConfigured logger = logging.getLogger(__name__) @@ -84,67 +81,3 @@ class FieldInitial(object): return result return None - - -class DefaultSetting(object): - def __init__(self, name, value, key_name=None, validator=None): - self.name = name - self.value = value - if key_name is None: - self.key_name = self.name.upper() - else: - self.key_name = key_name - self.validator = validator - - def validate(self, value): - if hasattr(self, 'validator') and self.validator is not None: - if callable(self.validator): - if not self.validator(value): - raise ImproperlyConfigured("Validator callback {clb} returned False".format(clb=self.validator)) - else: - if not re.search(self.validator, value): - raise ImproperlyConfigured("Does not match /{re}/".format(re=self.validator)) - - -class AppSettings(object): - def __init__(self, app_name, defaults): - settings_name = 'main.settings-' + app_name - - try: - settings_module = importlib.import_module(settings_name) - except ImportError: - settings_module = None - - for default in defaults: - if hasattr(settings_module, default.key_name): - value = getattr(settings_module, default.key_name) - try: - default.validate(value) - except ImproperlyConfigured as e: - msg = 'Invalid value of {key} in {module}: {cause}'.format(key=default.key_name, - module=settings_name, - cause=e.message) - raise ImproperlyConfigured(msg) - setattr(self, default.name, value) - elif isinstance(default.value, ImproperlyConfigured): - raise default.value - else: - try: - is_impconf = issubclass(default.value, ImproperlyConfigured) - except TypeError: - is_impconf = False - - if is_impconf: - msg = '{key} must be defined in {module}'.format(key=default.key_name, - module=settings_name) - raise default.value(msg) - else: - setattr(self, default.name, default.value) - - -class AppConfig(_AppConfig): - default_settings = () - - def __init__(self, app_name, app_module): - super(AppConfig, self).__init__(app_name, app_module) - self.settings = AppSettings(app_name, self.default_settings) diff --git a/dav_events/console_scripts/Resources/django.main.urls.py b/dav_events/console_scripts/Resources/django.main.urls.py deleted file mode 100644 index 26adf37..0000000 --- a/dav_events/console_scripts/Resources/django.main.urls.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.conf.urls import url, include -from django.contrib import admin - -urlpatterns = [ - url(r'^', include('dav_events.urls', namespace='dav_events')), - url(r'^admin/', admin.site.urls), -] diff --git a/dav_events/console_scripts/Resources/django.main.settings-dav_events.py b/dav_events/django_project_config/settings-dav_events.py similarity index 98% rename from dav_events/console_scripts/Resources/django.main.settings-dav_events.py rename to dav_events/django_project_config/settings-dav_events.py index 92b8c35..7faedfa 100644 --- a/dav_events/console_scripts/Resources/django.main.settings-dav_events.py +++ b/dav_events/django_project_config/settings-dav_events.py @@ -4,9 +4,6 @@ from dav_events.config import FieldInitial # E-Mails ENABLE_EMAIL_NOTIFICATIONS = False -EMAIL_SENDER = 'DAV Veranstaltungsheinzel ' -EMAIL_BASE_URL = 'http://localhost:8000' -EMAIL_SUBJECT_PREFIX = u'[DAV Veranstaltungen]' # Authorization Roles / Groups GROUP_MANAGE_ALL = 'Tourenreferenten' diff --git a/dav_events/emails.py b/dav_events/emails.py index 2821813..1e29348 100644 --- a/dav_events/emails.py +++ b/dav_events/emails.py @@ -1,61 +1,5 @@ # -*- coding: utf-8 -*- -import logging -from django.apps import apps -from django.core.exceptions import ImproperlyConfigured -from django.core.mail import EmailMessage -from django.template.loader import get_template - -logger = logging.getLogger(__name__) - - -class AbstractMail(object): - _subject = u'' - _template_name = None - - def _get_sender(self): - app_config = apps.get_containing_app_config(__package__) - return app_config.settings.email_sender - - def _get_subject(self, subject_fmt=None, **kwargs): - if subject_fmt is None: - subject_fmt = self._subject - app_config = apps.get_containing_app_config(__package__) - if app_config.settings.email_subject_prefix: - subject_fmt = u'%s %s' % (app_config.settings.email_subject_prefix, subject_fmt) - subject = subject_fmt.format(**kwargs) - return subject - - def _get_template(self): - if not self._template_name: - raise ImproperlyConfigured('%s._template_name ist not set.', self.__class__.__name__) - return get_template(self._template_name, using='PLAINTEXT') - - def _get_context_data(self, extra_context=None): - app_config = apps.get_containing_app_config(__package__) - context = { - 'base_url': app_config.settings.email_base_url, - } - if extra_context: - context.update(extra_context) - return context - - def _get_body(self, context=None): - template = self._get_template() - context = self._get_context_data(extra_context=context) - return template.render(context) - - def _get_recipients(self): - raise NotImplementedError() - - def send(self): - subject = self._get_subject() - body = self._get_body() - sender = self._get_sender() - recipients = self._get_recipients() - - emo = EmailMessage(subject=subject, body=body, from_email=sender, to=recipients) - logger.info(u'Send %s to %s', self.__class__.__name__, recipients) - emo.send() +from dav_base.emails import AbstractMail class AbstractEventMail(AbstractMail): @@ -149,26 +93,3 @@ class EventToPublishMail(AbstractEventMail): context['editor'] = self._editor context['confirm_publication_url'] = self._confirm_publication_action.get_absolute_url() return context - - -class PasswordSetEmail(AbstractMail): - _subject = u'Zugangsdaten' - _template_name = 'dav_events/emails/password_set.txt' - - def __init__(self, user, password): - self._user = user - self._password = password - - def _get_recipients(self): - r = u'{fullname} <{email}>'.format(fullname=self._user.get_full_name(), - email=self._user.email) - return [r] - - def _get_context_data(self, extra_context=None): - context = super(PasswordSetEmail, self)._get_context_data(extra_context=extra_context) - context.update({ - 'fullname': self._user.get_full_name(), - 'username': self._user.username, - 'password': self._password - }) - return context diff --git a/dav_events/forms/__init__.py b/dav_events/forms/__init__.py index eabec01..f995b85 100644 --- a/dav_events/forms/__init__.py +++ b/dav_events/forms/__init__.py @@ -1,3 +1,2 @@ from . import generic -from . import auth from . import events diff --git a/dav_events/models/event.py b/dav_events/models/event.py index b5d2fbd..00682bf 100644 --- a/dav_events/models/event.py +++ b/dav_events/models/event.py @@ -254,7 +254,7 @@ class Event(models.Model): date=self.get_formated_date()) def get_absolute_url(self): - return reverse('dav_events:event_detail', kwargs={'pk': self.pk}) + return reverse('dav_events:detail', kwargs={'pk': self.pk}) def save(self, implicit_update=False, **kwargs): creating = False diff --git a/dav_events/models/oneclickaction.py b/dav_events/models/oneclickaction.py index 2cdcb76..b5f9066 100644 --- a/dav_events/models/oneclickaction.py +++ b/dav_events/models/oneclickaction.py @@ -132,7 +132,7 @@ class OneClickAction(models.Model): try: user_id = self.parameters user = get_user_model().objects.get(id=user_id) - url = reverse('dav_events:event_list') + url = reverse('dav_events:list') result['location'] = url result['login'] = user except Exception as e: diff --git a/dav_events/module.json b/dav_events/module.json new file mode 100644 index 0000000..ccce349 --- /dev/null +++ b/dav_events/module.json @@ -0,0 +1,3 @@ +{ + "url_prefix": "events" +} \ No newline at end of file diff --git a/dav_events/templates/dav_events/base.html b/dav_events/templates/dav_events/base.html index bac928e..89d8bf1 100644 --- a/dav_events/templates/dav_events/base.html +++ b/dav_events/templates/dav_events/base.html @@ -1,71 +1,3 @@ - -{% load static %} -{% load i18n %} -{% load bootstrap3 %} - - - - - - {% block head-media %} - - - - +{% extends "dav_base/base.html" %} - - - - - {{ form.media }} - {% endblock head-media %} - - {% block head-additional %} - {% endblock head-additional %} - - - {% block head-title %}Veranstaltungsheinzel - Alpenverein Karlsruhe{% endblock head-title %} - - - -
- - -
- {% block messages %} -
- {% bootstrap_messages %} -
- {% endblock messages %} -
- -
- {% block modals %} - {% endblock modals %} - {% block page-body %} -
- {% block page-container-fluid %} - {% endblock page-container-fluid %} -
-
- {% block page-container %} - {% endblock page-container %} -
- {% endblock page-body %} -
- - -
- - +{% block head-title %}Touren und Kurse - {{ block.super }}{% endblock %} diff --git a/dav_events/templates/dav_events/event_detail.html b/dav_events/templates/dav_events/event_detail.html index cb34cb0..f3f8391 100644 --- a/dav_events/templates/dav_events/event_detail.html +++ b/dav_events/templates/dav_events/event_detail.html @@ -23,7 +23,7 @@

diff --git a/dav_events/templates/dav_events/event_list.html b/dav_events/templates/dav_events/event_list.html index 370b9ef..6488127 100644 --- a/dav_events/templates/dav_events/event_list.html +++ b/dav_events/templates/dav_events/event_list.html @@ -6,18 +6,18 @@ @@ -45,11 +45,11 @@ - {{ event.get_number }}
+ {{ event.get_number }}
({{ event.get_sport_display }}) - {{ event.title }} + {{ event.title }} {{ event.get_trainer_full_name }} diff --git a/dav_events/templates/dav_events/event_list_export_form.html b/dav_events/templates/dav_events/event_list_export_form.html index 969a164..fc22f4c 100644 --- a/dav_events/templates/dav_events/event_list_export_form.html +++ b/dav_events/templates/dav_events/event_list_export_form.html @@ -6,17 +6,17 @@ @@ -29,7 +29,7 @@ {% bootstrap_icon 'download-alt' %}  {% trans 'Herunterladen' %} - + {% bootstrap_icon 'remove' %}  {% trans 'Abbrechen' %} diff --git a/dav_events/templates/dav_events/event_update_form.html b/dav_events/templates/dav_events/event_update_form.html index ebf2721..ac6c95b 100644 --- a/dav_events/templates/dav_events/event_update_form.html +++ b/dav_events/templates/dav_events/event_update_form.html @@ -8,13 +8,13 @@ @@ -50,7 +50,7 @@ {% bootstrap_icon 'hdd' %}  {% trans 'Speichern' %} - + {% bootstrap_icon 'remove' %}  {% trans 'Abbrechen' %} diff --git a/dav_events/templates/dav_events/home.html b/dav_events/templates/dav_events/home.html index 61e9e15..61866f9 100644 --- a/dav_events/templates/dav_events/home.html +++ b/dav_events/templates/dav_events/home.html @@ -18,7 +18,7 @@ Du wirst dann per E-Mail auf dem laufenden gehalten.

- Los geht's! + Los geht's!

@@ -30,7 +30,7 @@ Tourenreferenten und Redakteure können hier Veranstaltungen freigeben und Programmlisten herunterladen.

- Weiter + Weiter

{% endblock page-container-fluid %} diff --git a/dav_events/urls.py b/dav_events/urls.py index 9a898b2..23977d9 100644 --- a/dav_events/urls.py +++ b/dav_events/urls.py @@ -3,18 +3,14 @@ from django.conf.urls import url from . import views urlpatterns = [ - url(r'^$', views.base.HomeView.as_view(), name='home'), - url(r'^user/login$', views.auth.LoginView.as_view(), name='login'), - url(r'^user/logout$', views.auth.LogoutView.as_view(), name='logout'), - url(r'^user/password$', views.auth.SetPasswordView.as_view(), name='set_password'), - url(r'^user/password/reset$', views.auth.ResetPasswordView.as_view(), name='reset_password'), - url(r'^events$', views.events.EventListView.as_view(), name='event_list'), - url(r'^events/export$', views.events.EventListExportView.as_view(), name='event_list_export'), - url(r'^events/create$', views.events.EventCreateView.as_view(), name='event_create'), - url(r'^events/(?P\d+)/confirm/(?P[a-z0-9][a-z0-9]*)', - views.events.EventConfirmStatusView.as_view(), name='event_confirmstatus'), - url(r'^events/(?P\d+)/edit', views.events.EventUpdateView.as_view(), name='event_update'), - url(r'^events/(?P\d+)/', views.events.EventDetailView.as_view(), name='event_detail'), + url(r'^home$', views.base.HomeView.as_view(), name='root'), + url(r'^$', views.events.EventListView.as_view(), name='list'), + url(r'^export$', views.events.EventListExportView.as_view(), name='list_export'), + url(r'^create$', views.events.EventCreateView.as_view(), name='create'), + url(r'^(?P\d+)/confirm/(?P[a-z0-9][a-z0-9]*)', + views.events.EventConfirmStatusView.as_view(), name='confirmstatus'), + url(r'^(?P\d+)/edit', views.events.EventUpdateView.as_view(), name='update'), + url(r'^(?P\d+)/', views.events.EventDetailView.as_view(), name='detail'), url(r'^action/(?P[a-fA-F0-9]{8}-([a-fA-F0-9]{4}-){3}[a-fA-F0-9]{12})/', views.actions.OneClickActionRunView.as_view(), name='action_run'), ] diff --git a/dav_events/views/__init__.py b/dav_events/views/__init__.py index 7454378..52f8853 100644 --- a/dav_events/views/__init__.py +++ b/dav_events/views/__init__.py @@ -1,4 +1,3 @@ from . import base -from . import auth from . import events from . import actions diff --git a/dav_events/views/events.py b/dav_events/views/events.py index 5f4d837..c6dd876 100644 --- a/dav_events/views/events.py +++ b/dav_events/views/events.py @@ -20,7 +20,6 @@ from .. import models from ..utils import has_role from ..workflow import workflow -app_config = apps.get_containing_app_config(__package__) logger = logging.getLogger(__name__) @@ -259,7 +258,7 @@ class EventCreateView(EventPermissionMixin, generic.FormView): form_class = forms.events.EventCreateForm template_dir = os.path.join('dav_events', 'event_create') default_template_name = 'default.html' - abort_url = reverse_lazy('dav_events:home') + abort_url = reverse_lazy('dav_events:root') def get_template_names(self): form = self.get_form() @@ -317,7 +316,7 @@ class EventCreateView(EventPermissionMixin, generic.FormView): owner = event.owner self.clean_session_data() if self.request.user.is_authenticated: - next_url = reverse('dav_events:event_list') + next_url = reverse('dav_events:list') if self.request.user != event.owner: messages.warning(self.request, u'%s %s' % ( @@ -325,10 +324,10 @@ class EventCreateView(EventPermissionMixin, generic.FormView): _(u'Warum machst du sowas?') )) elif owner.has_usable_password(): - next_url = reverse('dav_events:event_list') + next_url = reverse('dav_events:list') else: login(self.request, owner) - next_url = reverse('dav_events:set_password') + next_url = reverse('dav_auth:set_password') messages.success(self.request, _(u'Neuen Benutzer angemeldet: %(username)s') % {'username': owner.username}) messages.warning(self.request, _(u'Bitte neues Passwort setzen!')) diff --git a/dav_events/workflow.py b/dav_events/workflow.py index d62f003..750a5c8 100644 --- a/dav_events/workflow.py +++ b/dav_events/workflow.py @@ -4,6 +4,7 @@ from django.apps import apps from django.utils import timezone from . import emails +from .utils import get_users_by_role logger = logging.getLogger(__name__) @@ -143,7 +144,6 @@ class BasicWorkflow(object): recipients = [event.owner] if event.is_flagged('submitted'): # If the event is already submitted, add managers to the recipients. - from .utils import get_users_by_role recipients += get_users_by_role('manage_all') recipients += get_users_by_role('manage_{}'.format(event.sport.lower())) if event.is_flagged('accepted'): @@ -174,7 +174,6 @@ class BasicWorkflow(object): # Inform managers that they have to accept the event. # Also create OneClickActions for all of them and add the link to the mail, # so they can accept the event with a click into the mail. - from .utils import get_users_by_role recipients = get_users_by_role('manage_all') recipients += get_users_by_role('manage_{}'.format(event.sport.lower())) OneClickAction = app_config.get_model('OneClickAction') @@ -195,7 +194,6 @@ class BasicWorkflow(object): # Inform publishers that they have to publish the event. # Also create OneClickActions for all of them and add the link to the mail, # so they can confirm the publication with a click into the mail. - from .utils import get_users_by_role recipients = get_users_by_role('publish_incremental') OneClickAction = app_config.get_model('OneClickAction') for recipient in recipients: diff --git a/setup.py b/setup.py index 1aafd6d..e116f2b 100644 --- a/setup.py +++ b/setup.py @@ -45,7 +45,7 @@ if sys.version_info.major != 2: setup( name='django-dav-events', - version='0.2', + version='1.0', description='A django based web application project to submit DAV Events.', url='https://www.heinzelwelt.de', maintainer='Jens Kleineheismann', @@ -57,7 +57,7 @@ setup( include_package_data=True, entry_points={ 'console_scripts': [ - 'django-dav-events-admin = dav_events.console_scripts.admin:main', + 'django-dav-admin = dav_base.console_scripts.admin:main', ], }, install_requires=[