Refactor: split code into several django apps (we call them modules).
This commit is contained in:
1
dav_auth/__init__.py
Normal file
1
dav_auth/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
default_app_config = 'dav_auth.apps.AppConfig'
|
||||
12
dav_auth/apps.py
Normal file
12
dav_auth/apps.py
Normal file
@@ -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
|
||||
2
dav_auth/django_project_config/settings-dav_auth.py
Normal file
2
dav_auth/django_project_config/settings-dav_auth.py
Normal file
@@ -0,0 +1,2 @@
|
||||
# LOGIN_REDIRECT_URL = 'root'
|
||||
# LOGOUT_REDIRECT_URL = 'root'
|
||||
25
dav_auth/emails.py
Normal file
25
dav_auth/emails.py
Normal file
@@ -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
|
||||
3
dav_auth/module.json
Normal file
3
dav_auth/module.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"url_prefix": "auth"
|
||||
}
|
||||
1
dav_auth/templates/dav_auth/base.html
Normal file
1
dav_auth/templates/dav_auth/base.html
Normal file
@@ -0,0 +1 @@
|
||||
{% extends "dav_base/base.html" %}
|
||||
@@ -3,4 +3,4 @@ Hallo {{ fullname }},
|
||||
Benutzername: {{ username }}
|
||||
Passwort: {{ password }}
|
||||
|
||||
URL: {{ base_url }}{% url 'dav_events:home' %}
|
||||
URL: {{ base_url }}{% url 'root' %}
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "dav_events/base.html" %}
|
||||
{% extends "dav_auth/base.html" %}
|
||||
{% load bootstrap3 %}
|
||||
{% load i18n %}
|
||||
|
||||
@@ -15,13 +15,13 @@
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
<div class="pull-right"><a href="{% url 'dav_events:reset_password' %}">{% trans 'Passwort vergessen?' %}</a></div>
|
||||
<div class="pull-right"><a href="{% url 'dav_auth:reset_password' %}">{% trans 'Passwort vergessen?' %}</a></div>
|
||||
{% buttons %}
|
||||
<button type="submit" class="btn btn-success">
|
||||
{% bootstrap_icon 'log-in' %} 
|
||||
{% trans 'Login' %}
|
||||
</button>
|
||||
<a class="btn btn-danger" href="{% url 'dav_events:home' %}">
|
||||
<a class="btn btn-danger" href="{% url 'root' %}">
|
||||
{% bootstrap_icon 'remove' %} 
|
||||
{% trans 'Abbrechen' %}
|
||||
</a>
|
||||
@@ -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' %}
|
||||
</button>
|
||||
<a class="btn btn-danger" href="{% url 'dav_events:home' %}">
|
||||
<a class="btn btn-danger" href="{% url 'root' %}">
|
||||
{% bootstrap_icon 'remove' %} 
|
||||
{% trans 'Abbrechen' %}
|
||||
</a>
|
||||
@@ -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' %}
|
||||
</button>
|
||||
<a class="btn btn-danger" href="{% url 'dav_events:home' %}">
|
||||
<a class="btn btn-danger" href="{% url 'root' %}">
|
||||
{% bootstrap_icon 'remove' %} 
|
||||
{% trans 'Abbrechen' %}
|
||||
</a>
|
||||
@@ -7,12 +7,12 @@
|
||||
{{ user }} <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="user_dropdown_button">
|
||||
<li><a href="{% url 'dav_events:set_password' %}">{% trans 'Passwort ändern' %}</a></li>
|
||||
<li><a href="{% url 'dav_events:logout' %}">{% trans 'Logout' %}</a></li>
|
||||
<li><a href="{% url 'dav_auth:set_password' %}">{% trans 'Passwort ändern' %}</a></li>
|
||||
<li><a href="{% url 'dav_auth:logout' %}">{% trans 'Logout' %}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% else %}
|
||||
<a class="btn btn-default btn-sm" href="{% url 'dav_events:login' %}">
|
||||
<a class="btn btn-default btn-sm" href="{% url 'dav_auth:login' %}">
|
||||
{% bootstrap_icon 'log-in' %}
|
||||
{% trans 'Login' %}
|
||||
</a>
|
||||
10
dav_auth/urls.py
Normal file
10
dav_auth/urls.py
Normal file
@@ -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'),
|
||||
]
|
||||
@@ -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')
|
||||
1
dav_base/__init__.py
Normal file
1
dav_base/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
default_app_config = 'dav_base.apps.AppConfig'
|
||||
13
dav_base/apps.py
Normal file
13
dav_base/apps.py
Normal file
@@ -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
|
||||
2
dav_base/config/__init__.py
Normal file
2
dav_base/config/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from . import apps
|
||||
from . import modules
|
||||
71
dav_base/config/apps.py
Normal file
71
dav_base/config/apps.py
Normal file
@@ -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)
|
||||
132
dav_base/config/modules.py
Normal file
132
dav_base/config/modules.py
Normal file
@@ -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)
|
||||
0
dav_base/console_scripts/__init__.py
Normal file
0
dav_base/console_scripts/__init__.py
Normal file
@@ -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))
|
||||
|
||||
@@ -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,
|
||||
@@ -0,0 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# E-Mails
|
||||
EMAIL_SENDER = 'DAV heinzel <heinzel@alpenverein-karlsruhe.de>'
|
||||
EMAIL_BASE_URL = 'http://localhost:8000'
|
||||
EMAIL_SUBJECT_PREFIX = u'[DAV heinzel]'
|
||||
12
dav_base/console_scripts/django_project_config/urls.py
Normal file
12
dav_base/console_scripts/django_project_config/urls.py
Normal file
@@ -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'))
|
||||
]
|
||||
59
dav_base/emails.py
Normal file
59
dav_base/emails.py
Normal file
@@ -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()
|
||||
0
dav_base/management/__init__.py
Normal file
0
dav_base/management/__init__.py
Normal file
0
dav_base/management/commands/__init__.py
Normal file
0
dav_base/management/commands/__init__.py
Normal file
18
dav_base/management/commands/disable_module.py
Normal file
18
dav_base/management/commands/disable_module.py
Normal file
@@ -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)))
|
||||
37
dav_base/management/commands/enable_module.py
Normal file
37
dav_base/management/commands/enable_module.py
Normal file
@@ -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)))
|
||||
17
dav_base/management/commands/list_modules.py
Normal file
17
dav_base/management/commands/list_modules.py
Normal file
@@ -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)
|
||||
0
dav_base/migrations/__init__.py
Normal file
0
dav_base/migrations/__init__.py
Normal file
0
dav_base/models/__init__.py
Normal file
0
dav_base/models/__init__.py
Normal file
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
@@ -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 %}
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "error_base.html" %}
|
||||
{% extends "dav_base/error_base.html" %}
|
||||
|
||||
{% block error-code %}403{% endblock %}
|
||||
{% block error-title %}Forbidden{% endblock %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
72
dav_base/templates/dav_base/base.html
Normal file
72
dav_base/templates/dav_base/base.html
Normal file
@@ -0,0 +1,72 @@
|
||||
<!DOCTYPE html>
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% load dav_base %}
|
||||
<html lang="{{ LANGUAGE_CODE|default:'de' }}">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
{% block head-media %}
|
||||
<link type="image/x-icon" href="{% static 'dav_base/img/dav-favicon.ico' %}" rel="shortcut icon" />
|
||||
<link type="text/css" href="{% static 'dav_base/bootstrap/css/bootstrap.min.css' %}" rel="stylesheet" />
|
||||
<link type="text/css" href="{% static 'dav_base/css/dataTables.bootstrap.min.css' %}" rel="stylesheet" />
|
||||
<link type="text/css" href="{% static 'dav_base/css/local.css' %}" rel="stylesheet" />
|
||||
|
||||
<script type="text/javascript" src="{% static 'dav_base/js/jquery.min.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'dav_base/js/jquery.dataTables.min.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'dav_base/bootstrap/js/bootstrap.min.js' %}"></script>
|
||||
|
||||
{{ form.media }}
|
||||
{% endblock head-media %}
|
||||
|
||||
{% block head-additional %}
|
||||
{% endblock head-additional %}
|
||||
|
||||
<title>
|
||||
{% block head-title %}Alpenverein Karlsruhe{% endblock head-title %}
|
||||
</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
<div id="page-header">
|
||||
<h2>
|
||||
<a href="{% url 'root' %}">
|
||||
<img width="217" height="30" src="{% static 'dav_base/img/brand.png' %}" />{% block project-name %}{% include_if_exist 'project_name.html' %}{% endblock project-name %}
|
||||
</a>
|
||||
</h2>
|
||||
<div id="login-widget">{% include_if_exist 'dav_auth/includes/login_widget.html' %}</div>
|
||||
</div>
|
||||
|
||||
<div id="messages">
|
||||
{% block messages %}
|
||||
<div class="container-fluid">
|
||||
{% bootstrap_messages %}
|
||||
</div>
|
||||
{% endblock messages %}
|
||||
</div>
|
||||
|
||||
<div id="page-body">
|
||||
{% block modals %}
|
||||
{% endblock modals %}
|
||||
{% block page-body %}
|
||||
<div class="container-fluid">
|
||||
{% block page-container-fluid %}
|
||||
{% endblock page-container-fluid %}
|
||||
</div>
|
||||
<div class="container">
|
||||
{% block page-container %}
|
||||
{% endblock page-container %}
|
||||
</div>
|
||||
{% endblock page-body %}
|
||||
</div>
|
||||
|
||||
<div id="page-footer">
|
||||
<div class="signum">{% block signum %}<a href="mailto:heinzel@alpenverein-karlsruhe.de">heinzel@alpenverein-karlsruhe.de</a>{% endblock signum %}</div>
|
||||
<a href="http://alpenverein-karlsruhe.de" target="_blank">© Sektion Karlsruhe im Deutschen Alpenverein (DAV) e.V.</a>  • 
|
||||
<a href="http://alpenverein-karlsruhe.de/impressum">{% trans 'Impressum' %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</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 %}
|
||||
27
dav_base/templates/dav_base/root.html
Normal file
27
dav_base/templates/dav_base/root.html
Normal file
@@ -0,0 +1,27 @@
|
||||
{% extends "dav_base/base.html" %}
|
||||
{% load bootstrap3 %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page-container %}
|
||||
<div class="jumbotron">
|
||||
<h1>Hallo,</h1>
|
||||
<p>
|
||||
du bist auf dem Entwicklungs- und Testserver der
|
||||
<a href="http://alpenverein-karlsruhe.de">Sektion Karlsruhe des Deutschen Alpenvereins (DAV) e.V.</a> gelandet.
|
||||
</p>
|
||||
<p>
|
||||
Wenn du Fragen hast, kannst du dich an
|
||||
<a href="mailto:heinzel@alpenverein-karlsruhe.de">heinzel@alpenverein-karlsruhe.de</a> wenden.
|
||||
</p>
|
||||
<p> </p>
|
||||
{% if root_urls %}
|
||||
<h3>Module:</h3>
|
||||
<div class="list-group">
|
||||
{% for root_url in root_urls %}
|
||||
<a class="list-group-item list-group-item-warning"
|
||||
href="{% url root_url.1 %}"><span class="glyphicon glyphicon-arrow-right"></span> {{ root_url.0 }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock page-container %}
|
||||
0
dav_base/templates/project_name.html
Normal file
0
dav_base/templates/project_name.html
Normal file
0
dav_base/templatetags/__init__.py
Normal file
0
dav_base/templatetags/__init__.py
Normal file
53
dav_base/templatetags/dav_base.py
Normal file
53
dav_base/templatetags/dav_base.py
Normal file
@@ -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
|
||||
9
dav_base/urls.py
Normal file
9
dav_base/urls.py
Normal file
@@ -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),
|
||||
]
|
||||
23
dav_base/views.py
Normal file
23
dav_base/views.py
Normal file
@@ -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
|
||||
@@ -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),
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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),
|
||||
]
|
||||
@@ -4,9 +4,6 @@ from dav_events.config import FieldInitial
|
||||
|
||||
# E-Mails
|
||||
ENABLE_EMAIL_NOTIFICATIONS = False
|
||||
EMAIL_SENDER = 'DAV Veranstaltungsheinzel <heinzel@alpenverein-karlsruhe.de>'
|
||||
EMAIL_BASE_URL = 'http://localhost:8000'
|
||||
EMAIL_SUBJECT_PREFIX = u'[DAV Veranstaltungen]'
|
||||
|
||||
# Authorization Roles / Groups
|
||||
GROUP_MANAGE_ALL = 'Tourenreferenten'
|
||||
@@ -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
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
from . import generic
|
||||
from . import auth
|
||||
from . import events
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
3
dav_events/module.json
Normal file
3
dav_events/module.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"url_prefix": "events"
|
||||
}
|
||||
@@ -1,71 +1,3 @@
|
||||
<!DOCTYPE html>
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
<html lang="{{ LANGUAGE_CODE|default:'de' }}">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
{% block head-media %}
|
||||
<link type="image/x-icon" href="{% static 'dav_events/img/dav-favicon.ico' %}" rel="shortcut icon" />
|
||||
<link type="text/css" href="{% static 'dav_events/bootstrap/css/bootstrap.min.css' %}" rel="stylesheet" />
|
||||
<link type="text/css" href="{% static 'dav_events/css/dataTables.bootstrap.min.css' %}" rel="stylesheet" />
|
||||
<link type="text/css" href="{% static 'dav_events/css/local.css' %}" rel="stylesheet" />
|
||||
{% extends "dav_base/base.html" %}
|
||||
|
||||
<script type="text/javascript" src="{% static 'dav_events/js/jquery.min.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'dav_events/js/jquery.dataTables.min.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'dav_events/bootstrap/js/bootstrap.min.js' %}"></script>
|
||||
|
||||
{{ form.media }}
|
||||
{% endblock head-media %}
|
||||
|
||||
{% block head-additional %}
|
||||
{% endblock head-additional %}
|
||||
|
||||
<title>
|
||||
{% block head-title %}Veranstaltungsheinzel - Alpenverein Karlsruhe{% endblock head-title %}
|
||||
</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
<div id="page-header">
|
||||
<h2>
|
||||
<a href="{% url 'dav_events:home' %}">
|
||||
<img width="217" height="30" src="{% static 'dav_events/img/brand.png' %}" />Veranstaltungsheinzel
|
||||
</a>
|
||||
</h2>
|
||||
<div id="login-widget">{% include 'dav_events/includes/login_widget.html' %}</div>
|
||||
</div>
|
||||
|
||||
<div id="messages">
|
||||
{% block messages %}
|
||||
<div class="container-fluid">
|
||||
{% bootstrap_messages %}
|
||||
</div>
|
||||
{% endblock messages %}
|
||||
</div>
|
||||
|
||||
<div id="page-body">
|
||||
{% block modals %}
|
||||
{% endblock modals %}
|
||||
{% block page-body %}
|
||||
<div class="container-fluid">
|
||||
{% block page-container-fluid %}
|
||||
{% endblock page-container-fluid %}
|
||||
</div>
|
||||
<div class="container">
|
||||
{% block page-container %}
|
||||
{% endblock page-container %}
|
||||
</div>
|
||||
{% endblock page-body %}
|
||||
</div>
|
||||
|
||||
<div id="page-footer">
|
||||
<div class="signum"><a href="mailto:heinzel@alpenverein-karlsruhe.de">heinzel@alpenverein-karlsruhe.de</a></div>
|
||||
<a href="http://alpenverein-karlsruhe.de" target="_blank">© Sektion Karlsruhe im Deutschen Alpenverein (DAV) e.V.</a>  • 
|
||||
<a href="http://alpenverein-karlsruhe.de/impressum">{% trans 'Impressum' %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{% block head-title %}Touren und Kurse - {{ block.super }}{% endblock %}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a class="btn btn-success" href="{% url 'dav_events:event_confirmstatus' event.pk 'submitted' %}">
|
||||
<a class="btn btn-success" href="{% url 'dav_events:confirmstatus' event.pk 'submitted' %}">
|
||||
{% bootstrap_icon 'ok' %} 
|
||||
{% trans 'Ja, alles klar!' %}
|
||||
</a>
|
||||
@@ -51,7 +51,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a class="btn btn-success" href="{% url 'dav_events:event_confirmstatus' event.pk 'accepted' %}">
|
||||
<a class="btn btn-success" href="{% url 'dav_events:confirmstatus' event.pk 'accepted' %}">
|
||||
{% bootstrap_icon 'ok' %} 
|
||||
{% trans 'Ja, passt schon!' %}
|
||||
</a>
|
||||
@@ -84,7 +84,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a class="btn btn-success" href="{% if event.planned_publication_date %}{% url 'dav_events:event_confirmstatus' event.pk 'publishing' %}{% else %}{% url 'dav_events:event_confirmstatus' event.pk 'published' %}{% endif %}">
|
||||
<a class="btn btn-success" href="{% if event.planned_publication_date %}{% url 'dav_events:confirmstatus' event.pk 'publishing' %}{% else %}{% url 'dav_events:confirmstatus' event.pk 'published' %}{% endif %}">
|
||||
{% bootstrap_icon 'ok' %} 
|
||||
{% trans 'Ja' %}
|
||||
</a>
|
||||
@@ -135,7 +135,7 @@
|
||||
</a>
|
||||
{% endif %}
|
||||
<a class="btn btn-primary"
|
||||
href="{% url 'dav_events:event_create' %}?copy={{ event.pk }}"
|
||||
href="{% url 'dav_events:create' %}?copy={{ event.pk }}"
|
||||
title="{% trans 'Diese Veranstaltung als Vorlage für eine neue Veranstaltung benutzen' %}">
|
||||
{% bootstrap_icon 'duplicate' %} 
|
||||
{% trans 'Kopieren' %}
|
||||
@@ -144,15 +144,15 @@
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li>
|
||||
<a class="btn"
|
||||
href="{% url 'dav_events:event_list' %}">{% trans 'Veranstaltungsliste' %}</a>
|
||||
href="{% url 'dav_events:list' %}">{% trans 'Veranstaltungsliste' %}</a>
|
||||
</li>
|
||||
<li class="active">
|
||||
<a class="btn"
|
||||
href="{% url 'dav_events:event_detail' event.pk %}">{% trans 'Details' %}</a>
|
||||
href="{% url 'dav_events:detail' event.pk %}">{% trans 'Details' %}</a>
|
||||
</li>
|
||||
<li class="{% if not has_permission_update %}disabled{% endif %}">
|
||||
<a class="btn {% if has_permission_update %}btn-warning{% else %}disabled{% endif %}"
|
||||
href="{% url 'dav_events:event_update' event.pk %}">{% trans 'Ändern' %}</a>
|
||||
href="{% url 'dav_events:update' event.pk %}">{% trans 'Ändern' %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -6,18 +6,18 @@
|
||||
<div class="action-tabs">
|
||||
<div class="pull-right">
|
||||
<a class="btn btn-primary"
|
||||
href="{% url 'dav_events:event_create' %}">
|
||||
href="{% url 'dav_events:create' %}">
|
||||
{% bootstrap_icon 'plus' %}
|
||||
{% trans 'Neue Veranstaltung anlegen' %}
|
||||
</a>
|
||||
</div>
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li class="active">
|
||||
<a href="{% url 'dav_events:event_list' %}">{% trans 'Veranstaltungsliste' %}</a>
|
||||
<a href="{% url 'dav_events:list' %}">{% trans 'Veranstaltungsliste' %}</a>
|
||||
</li>
|
||||
<li class="{% if not has_permission_export %}disabled{% endif %}">
|
||||
<a class="btn {% if not has_permission_export %}disabled{% endif %}"
|
||||
href="{% url 'dav_events:event_list_export' %}">{% trans 'Veranstaltungsliste herunterladen' %}</a>
|
||||
href="{% url 'dav_events:list_export' %}">{% trans 'Veranstaltungsliste herunterladen' %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -45,11 +45,11 @@
|
||||
<tr>
|
||||
<td data-order="{{ event.get_number|slice:':1' }}{{ event.get_number|slice:'-2:' }}{{ event.get_number|slice:'1:-2'|cut:'/' }}"
|
||||
data-search="{{ event.get_number }} ({{ event.get_sport_display }})">
|
||||
<a href="{% url 'dav_events:event_detail' event.pk %}">{{ event.get_number }}</a><br />
|
||||
<a href="{% url 'dav_events:detail' event.pk %}">{{ event.get_number }}</a><br />
|
||||
<small>({{ event.get_sport_display }})</small>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{% url 'dav_events:event_detail' event.pk %}">{{ event.title }}</a>
|
||||
<a href="{% url 'dav_events:detail' event.pk %}">{{ event.title }}</a>
|
||||
</td>
|
||||
<td>
|
||||
{{ event.get_trainer_full_name }}
|
||||
|
||||
@@ -6,17 +6,17 @@
|
||||
<div class="action-tabs">
|
||||
<div class="pull-right">
|
||||
<a class="btn btn-primary"
|
||||
href="{% url 'dav_events:event_create' %}">
|
||||
href="{% url 'dav_events:create' %}">
|
||||
{% bootstrap_icon 'plus' %}
|
||||
{% trans 'Neue Veranstaltung anlegen' %}
|
||||
</a>
|
||||
</div>
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li>
|
||||
<a href="{% url 'dav_events:event_list' %}">{% trans 'Veranstaltungsliste' %}</a>
|
||||
<a href="{% url 'dav_events:list' %}">{% trans 'Veranstaltungsliste' %}</a>
|
||||
</li>
|
||||
<li class="active">
|
||||
<a href="{% url 'dav_events:event_list_export' %}">{% trans 'Veranstaltungsliste herunterladen' %}</a>
|
||||
<a href="{% url 'dav_events:list_export' %}">{% trans 'Veranstaltungsliste herunterladen' %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -29,7 +29,7 @@
|
||||
{% bootstrap_icon 'download-alt' %} 
|
||||
{% trans 'Herunterladen' %}
|
||||
</button>
|
||||
<a class="btn btn-danger" href="{% url 'dav_events:event_list' %}">
|
||||
<a class="btn btn-danger" href="{% url 'dav_events:list' %}">
|
||||
{% bootstrap_icon 'remove' %} 
|
||||
{% trans 'Abbrechen' %}
|
||||
</a>
|
||||
|
||||
@@ -8,13 +8,13 @@
|
||||
<div class="action-tabs">
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li>
|
||||
<a href="{% url 'dav_events:event_list' %}">{% trans 'Veranstaltungsliste' %}</a>
|
||||
<a href="{% url 'dav_events:list' %}">{% trans 'Veranstaltungsliste' %}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'dav_events:event_detail' event.pk %}">{% trans 'Details' %}</a>
|
||||
<a href="{% url 'dav_events:detail' event.pk %}">{% trans 'Details' %}</a>
|
||||
</li>
|
||||
<li class="active">
|
||||
<a href="{% url 'dav_events:event_update' event.pk %}">{% trans 'Ändern' %}</a>
|
||||
<a href="{% url 'dav_events:update' event.pk %}">{% trans 'Ändern' %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -50,7 +50,7 @@
|
||||
{% bootstrap_icon 'hdd' %} 
|
||||
{% trans 'Speichern' %}
|
||||
</button>
|
||||
<a class="btn btn-danger" href="{% url 'dav_events:event_detail' event.pk %}">
|
||||
<a class="btn btn-danger" href="{% url 'dav_events:detail' event.pk %}">
|
||||
{% bootstrap_icon 'remove' %} 
|
||||
{% trans 'Abbrechen' %}
|
||||
</a>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
Du wirst dann per E-Mail auf dem laufenden gehalten.
|
||||
</p>
|
||||
<p>
|
||||
<a class="btn btn-primary" href="{% url 'dav_events:event_create' %}">Los geht's!</a>
|
||||
<a class="btn btn-primary" href="{% url 'dav_events:create' %}">Los geht's!</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="well">
|
||||
@@ -30,7 +30,7 @@
|
||||
Tourenreferenten und Redakteure können hier Veranstaltungen freigeben und Programmlisten herunterladen.
|
||||
</p>
|
||||
<p>
|
||||
<a class="btn btn-primary" href="{% url 'dav_events:event_list' %}">Weiter</a>
|
||||
<a class="btn btn-primary" href="{% url 'dav_events:list' %}">Weiter</a>
|
||||
</p>
|
||||
</div>
|
||||
{% endblock page-container-fluid %}
|
||||
|
||||
@@ -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<pk>\d+)/confirm/(?P<status>[a-z0-9][a-z0-9]*)',
|
||||
views.events.EventConfirmStatusView.as_view(), name='event_confirmstatus'),
|
||||
url(r'^events/(?P<pk>\d+)/edit', views.events.EventUpdateView.as_view(), name='event_update'),
|
||||
url(r'^events/(?P<pk>\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<pk>\d+)/confirm/(?P<status>[a-z0-9][a-z0-9]*)',
|
||||
views.events.EventConfirmStatusView.as_view(), name='confirmstatus'),
|
||||
url(r'^(?P<pk>\d+)/edit', views.events.EventUpdateView.as_view(), name='update'),
|
||||
url(r'^(?P<pk>\d+)/', views.events.EventDetailView.as_view(), name='detail'),
|
||||
url(r'^action/(?P<pk>[a-fA-F0-9]{8}-([a-fA-F0-9]{4}-){3}[a-fA-F0-9]{12})/',
|
||||
views.actions.OneClickActionRunView.as_view(), name='action_run'),
|
||||
]
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
from . import base
|
||||
from . import auth
|
||||
from . import events
|
||||
from . import actions
|
||||
|
||||
@@ -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!'))
|
||||
|
||||
@@ -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:
|
||||
|
||||
4
setup.py
4
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=[
|
||||
|
||||
Reference in New Issue
Block a user