Add user mailer backends support. GitLab issue #286.
Add support for creating dynamic forms. Signed-off-by: Roberto Rosario <roberto.rosario.gonzalez@gmail.com>
This commit is contained in:
@@ -6,6 +6,7 @@ from django import forms
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.utils.module_loading import import_string
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from .classes import Filter, Package
|
from .classes import Filter, Package
|
||||||
@@ -83,6 +84,35 @@ class DetailForm(forms.ModelForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DynamicFormMixin(object):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.schema = kwargs.pop('schema')
|
||||||
|
super(DynamicFormMixin, self).__init__(*args, **kwargs)
|
||||||
|
for field in self.schema['fields']:
|
||||||
|
field_class = import_string(field['class'])
|
||||||
|
kwargs = {
|
||||||
|
'label': field['label'],
|
||||||
|
'required': field.get('required', True),
|
||||||
|
'initial': field.get('default', None),
|
||||||
|
'help_text': field.get('help_text'),
|
||||||
|
}
|
||||||
|
kwargs.update(field.get('kwargs', {}))
|
||||||
|
self.fields[field['name']] = field_class(**kwargs)
|
||||||
|
|
||||||
|
for field, widget in self.schema.get('widgets', {}).items():
|
||||||
|
self.fields[field].widget = import_string(
|
||||||
|
widget['class']
|
||||||
|
)(**widget.get('kwargs', {}))
|
||||||
|
|
||||||
|
|
||||||
|
class DynamicForm(DynamicFormMixin, forms.Form):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DynamicModelForm(DynamicFormMixin, forms.ModelForm):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class FileDisplayForm(forms.Form):
|
class FileDisplayForm(forms.Form):
|
||||||
text = forms.CharField(
|
text = forms.CharField(
|
||||||
label='',
|
label='',
|
||||||
|
|||||||
@@ -19,10 +19,10 @@ from django_downloadview import (
|
|||||||
)
|
)
|
||||||
from pure_pagination.mixins import PaginationMixin
|
from pure_pagination.mixins import PaginationMixin
|
||||||
|
|
||||||
from .forms import ChoiceForm
|
from .forms import ChoiceForm, DynamicForm
|
||||||
from .mixins import (
|
from .mixins import (
|
||||||
DeleteExtraDataMixin, ExtraContextMixin, FormExtraKwargsMixin,
|
DeleteExtraDataMixin, DynamicFormViewMixin, ExtraContextMixin,
|
||||||
MultipleObjectMixin, ObjectActionMixin,
|
FormExtraKwargsMixin, MultipleObjectMixin, ObjectActionMixin,
|
||||||
ObjectListPermissionFilterMixin, ObjectNameMixin,
|
ObjectListPermissionFilterMixin, ObjectNameMixin,
|
||||||
ObjectPermissionCheckMixin, RedirectionMixin,
|
ObjectPermissionCheckMixin, RedirectionMixin,
|
||||||
ViewPermissionCheckMixin
|
ViewPermissionCheckMixin
|
||||||
@@ -186,10 +186,14 @@ class ConfirmView(ObjectListPermissionFilterMixin, ObjectPermissionCheckMixin, V
|
|||||||
return HttpResponseRedirect(self.get_success_url())
|
return HttpResponseRedirect(self.get_success_url())
|
||||||
|
|
||||||
|
|
||||||
class FormView(FormExtraKwargsMixin, ViewPermissionCheckMixin, ExtraContextMixin, RedirectionMixin, DjangoFormView):
|
class FormView(ViewPermissionCheckMixin, ExtraContextMixin, RedirectionMixin, FormExtraKwargsMixin, DjangoFormView):
|
||||||
template_name = 'appearance/generic_form.html'
|
template_name = 'appearance/generic_form.html'
|
||||||
|
|
||||||
|
|
||||||
|
class DynamicFormView(DynamicFormViewMixin, FormView):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class MultiFormView(DjangoFormView):
|
class MultiFormView(DjangoFormView):
|
||||||
prefix = None
|
prefix = None
|
||||||
prefixes = {}
|
prefixes = {}
|
||||||
@@ -302,7 +306,7 @@ class SimpleView(ViewPermissionCheckMixin, ExtraContextMixin, TemplateView):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class SingleObjectCreateView(ObjectNameMixin, ViewPermissionCheckMixin, ExtraContextMixin, RedirectionMixin, CreateView):
|
class SingleObjectCreateView(ObjectNameMixin, ViewPermissionCheckMixin, ExtraContextMixin, RedirectionMixin, FormExtraKwargsMixin, CreateView):
|
||||||
template_name = 'appearance/generic_form.html'
|
template_name = 'appearance/generic_form.html'
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
@@ -344,6 +348,10 @@ class SingleObjectCreateView(ObjectNameMixin, ViewPermissionCheckMixin, ExtraCon
|
|||||||
return HttpResponseRedirect(self.get_success_url())
|
return HttpResponseRedirect(self.get_success_url())
|
||||||
|
|
||||||
|
|
||||||
|
class SingleObjectDynamicFormCreateView(DynamicFormViewMixin, SingleObjectCreateView):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class SingleObjectDeleteView(ObjectNameMixin, DeleteExtraDataMixin, ViewPermissionCheckMixin, ObjectPermissionCheckMixin, ExtraContextMixin, RedirectionMixin, DeleteView):
|
class SingleObjectDeleteView(ObjectNameMixin, DeleteExtraDataMixin, ViewPermissionCheckMixin, ObjectPermissionCheckMixin, ExtraContextMixin, RedirectionMixin, DeleteView):
|
||||||
template_name = 'appearance/generic_confirm.html'
|
template_name = 'appearance/generic_confirm.html'
|
||||||
|
|
||||||
@@ -394,7 +402,7 @@ class SingleObjectDownloadView(ViewPermissionCheckMixin, ObjectPermissionCheckMi
|
|||||||
VirtualFile = VirtualFile
|
VirtualFile = VirtualFile
|
||||||
|
|
||||||
|
|
||||||
class SingleObjectEditView(ObjectNameMixin, ViewPermissionCheckMixin, ObjectPermissionCheckMixin, ExtraContextMixin, RedirectionMixin, UpdateView):
|
class SingleObjectEditView(ObjectNameMixin, ViewPermissionCheckMixin, ObjectPermissionCheckMixin, ExtraContextMixin, FormExtraKwargsMixin, RedirectionMixin, UpdateView):
|
||||||
template_name = 'appearance/generic_form.html'
|
template_name = 'appearance/generic_form.html'
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
@@ -446,6 +454,10 @@ class SingleObjectEditView(ObjectNameMixin, ViewPermissionCheckMixin, ObjectPerm
|
|||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
class SingleObjectDynamicFormEditView(DynamicFormViewMixin, SingleObjectEditView):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class SingleObjectListView(PaginationMixin, ViewPermissionCheckMixin, ObjectListPermissionFilterMixin, ExtraContextMixin, RedirectionMixin, ListView):
|
class SingleObjectListView(PaginationMixin, ViewPermissionCheckMixin, ObjectListPermissionFilterMixin, ExtraContextMixin, RedirectionMixin, ListView):
|
||||||
template_name = 'appearance/generic_list.html'
|
template_name = 'appearance/generic_list.html'
|
||||||
|
|
||||||
|
|||||||
@@ -12,10 +12,11 @@ from permissions import Permission
|
|||||||
|
|
||||||
from acls.models import AccessControlList
|
from acls.models import AccessControlList
|
||||||
|
|
||||||
|
from .forms import DynamicForm
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'DeleteExtraDataMixin', 'ExtraContextMixin', 'FormExtraKwargsMixin',
|
'DeleteExtraDataMixin', 'DynamicFormViewMixin', 'ExtraContextMixin',
|
||||||
'MultipleObjectMixin', 'ObjectActionMixin',
|
'FormExtraKwargsMixin', 'MultipleObjectMixin', 'ObjectActionMixin',
|
||||||
'ObjectListPermissionFilterMixin', 'ObjectNameMixin',
|
'ObjectListPermissionFilterMixin', 'ObjectNameMixin',
|
||||||
'ObjectPermissionCheckMixin', 'RedirectionMixin',
|
'ObjectPermissionCheckMixin', 'RedirectionMixin',
|
||||||
'ViewPermissionCheckMixin'
|
'ViewPermissionCheckMixin'
|
||||||
@@ -34,6 +35,15 @@ class DeleteExtraDataMixin(object):
|
|||||||
return HttpResponseRedirect(success_url)
|
return HttpResponseRedirect(success_url)
|
||||||
|
|
||||||
|
|
||||||
|
class DynamicFormViewMixin(object):
|
||||||
|
form_class = DynamicForm
|
||||||
|
|
||||||
|
def get_form_kwargs(self):
|
||||||
|
data = super(DynamicFormViewMixin, self).get_form_kwargs()
|
||||||
|
data.update({'schema': self.get_form_schema()})
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
class ExtraContextMixin(object):
|
class ExtraContextMixin(object):
|
||||||
"""
|
"""
|
||||||
Mixin that allows views to pass extra context to the template
|
Mixin that allows views to pass extra context to the template
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .classes import * # NOQA
|
||||||
|
|
||||||
default_app_config = 'mailer.apps.MailerApp'
|
default_app_config = 'mailer.apps.MailerApp'
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from .models import LogEntry
|
from .models import LogEntry, UserMailer
|
||||||
|
|
||||||
|
|
||||||
@admin.register(LogEntry)
|
@admin.register(LogEntry)
|
||||||
@@ -10,3 +10,10 @@ class LogEntryAdmin(admin.ModelAdmin):
|
|||||||
date_hierarchy = 'datetime'
|
date_hierarchy = 'datetime'
|
||||||
list_display = ('datetime', 'message')
|
list_display = ('datetime', 'message')
|
||||||
readonly_fields = ('datetime', 'message')
|
readonly_fields = ('datetime', 'message')
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(UserMailer)
|
||||||
|
class UserMailerAdmin(admin.ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
'label', 'default', 'enabled', 'backend_path', 'backend_data'
|
||||||
|
)
|
||||||
|
|||||||
@@ -6,17 +6,26 @@ from django.apps import apps
|
|||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from acls import ModelPermission
|
from acls import ModelPermission
|
||||||
from common import MayanAppConfig, menu_object, menu_multi_item, menu_tools
|
from common import (
|
||||||
|
MayanAppConfig, menu_object, menu_multi_item, menu_secondary, menu_setup,
|
||||||
|
menu_tools
|
||||||
|
)
|
||||||
|
from common.widgets import two_state_template
|
||||||
from mayan.celery import app
|
from mayan.celery import app
|
||||||
from navigation import SourceColumn
|
from navigation import SourceColumn
|
||||||
|
|
||||||
|
from .classes import MailerBackend
|
||||||
from .links import (
|
from .links import (
|
||||||
link_document_mailing_error_log, link_send_document_link,
|
link_send_document_link, link_send_document, link_send_multiple_document,
|
||||||
link_send_document, link_send_multiple_document,
|
link_send_multiple_document_link, link_system_mailer_error_log,
|
||||||
link_send_multiple_document_link
|
link_user_mailer_create, link_user_mailer_delete, link_user_mailer_edit,
|
||||||
|
link_user_mailer_list, link_user_mailer_log_list, link_user_mailer_setup,
|
||||||
|
link_user_mailer_test
|
||||||
)
|
)
|
||||||
from .permissions import (
|
from .permissions import (
|
||||||
permission_mailing_link, permission_mailing_send_document
|
permission_mailing_link, permission_mailing_send_document,
|
||||||
|
permission_user_mailer_delete, permission_user_mailer_edit,
|
||||||
|
permission_user_mailer_use, permission_user_mailer_view,
|
||||||
)
|
)
|
||||||
from .queues import * # NOQA
|
from .queues import * # NOQA
|
||||||
|
|
||||||
@@ -34,14 +43,34 @@ class MailerApp(MayanAppConfig):
|
|||||||
)
|
)
|
||||||
|
|
||||||
LogEntry = self.get_model('LogEntry')
|
LogEntry = self.get_model('LogEntry')
|
||||||
|
UserMailer = self.get_model('UserMailer')
|
||||||
|
|
||||||
|
MailerBackend.initialize()
|
||||||
|
|
||||||
SourceColumn(
|
SourceColumn(
|
||||||
source=LogEntry, label=_('Date and time'), attribute='datetime'
|
source=LogEntry, label=_('Date and time'), attribute='datetime'
|
||||||
)
|
)
|
||||||
|
|
||||||
SourceColumn(
|
SourceColumn(
|
||||||
source=LogEntry, label=_('Message'), attribute='message'
|
source=LogEntry, label=_('Message'), attribute='message'
|
||||||
)
|
)
|
||||||
|
SourceColumn(
|
||||||
|
source=UserMailer, label=_('Label'), attribute='label'
|
||||||
|
)
|
||||||
|
SourceColumn(
|
||||||
|
source=UserMailer, label=_('Default?'),
|
||||||
|
func=lambda context: two_state_template(
|
||||||
|
context['object'].default
|
||||||
|
)
|
||||||
|
)
|
||||||
|
SourceColumn(
|
||||||
|
source=UserMailer, label=_('Enabled?'),
|
||||||
|
func=lambda context: two_state_template(
|
||||||
|
context['object'].enabled
|
||||||
|
)
|
||||||
|
)
|
||||||
|
SourceColumn(
|
||||||
|
source=UserMailer, label=_('Label'), attribute='backend_label'
|
||||||
|
)
|
||||||
|
|
||||||
ModelPermission.register(
|
ModelPermission.register(
|
||||||
model=Document, permissions=(
|
model=Document, permissions=(
|
||||||
@@ -49,6 +78,13 @@ class MailerApp(MayanAppConfig):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ModelPermission.register(
|
||||||
|
model=UserMailer, permissions=(
|
||||||
|
permission_user_mailer_delete, permission_user_mailer_edit,
|
||||||
|
permission_user_mailer_view, permission_user_mailer_use
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
app.conf.CELERY_QUEUES.append(
|
app.conf.CELERY_QUEUES.append(
|
||||||
Queue('mailing', Exchange('mailing'), routing_key='mailing'),
|
Queue('mailing', Exchange('mailing'), routing_key='mailing'),
|
||||||
)
|
)
|
||||||
@@ -73,4 +109,22 @@ class MailerApp(MayanAppConfig):
|
|||||||
), sources=(Document,)
|
), sources=(Document,)
|
||||||
)
|
)
|
||||||
|
|
||||||
menu_tools.bind_links(links=(link_document_mailing_error_log,))
|
menu_object.bind_links(
|
||||||
|
links=(
|
||||||
|
link_user_mailer_edit, link_user_mailer_log_list,
|
||||||
|
link_user_mailer_test, link_user_mailer_delete,
|
||||||
|
), sources=(UserMailer,)
|
||||||
|
)
|
||||||
|
|
||||||
|
menu_secondary.bind_links(
|
||||||
|
links=(
|
||||||
|
link_user_mailer_list, link_user_mailer_create,
|
||||||
|
), sources=(
|
||||||
|
UserMailer, 'mailer:user_mailer_list',
|
||||||
|
'mailer:user_mailer_create'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
menu_tools.bind_links(links=(link_system_mailer_error_log,))
|
||||||
|
|
||||||
|
menu_setup.bind_links(links=(link_user_mailer_setup,))
|
||||||
|
|||||||
71
mayan/apps/mailer/classes.py
Normal file
71
mayan/apps/mailer/classes.py
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from importlib import import_module
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from django.apps import apps
|
||||||
|
from django.utils import six
|
||||||
|
from django.utils.encoding import force_text
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
__ALL__ = ('MailerBackend',)
|
||||||
|
|
||||||
|
|
||||||
|
class MailerBackendMetaclass(type):
|
||||||
|
_registry = {}
|
||||||
|
|
||||||
|
def __new__(mcs, name, bases, attrs):
|
||||||
|
new_class = super(MailerBackendMetaclass, mcs).__new__(
|
||||||
|
mcs, name, bases, attrs
|
||||||
|
)
|
||||||
|
if not new_class.__module__ == 'mailer.classes':
|
||||||
|
mcs._registry[
|
||||||
|
'{}.{}'.format(new_class.__module__, name)
|
||||||
|
] = new_class
|
||||||
|
|
||||||
|
return new_class
|
||||||
|
|
||||||
|
|
||||||
|
class MailerBackendBase(object):
|
||||||
|
"""
|
||||||
|
Base class for the mailing backends. This class is mainly a wrapper
|
||||||
|
for other Django backends that adds a few metadata to specify the
|
||||||
|
fields it needs to be instanciated at runtime.
|
||||||
|
|
||||||
|
The fields attribute is a list of dictionaries with the format:
|
||||||
|
{
|
||||||
|
'name': '' # Field internal name
|
||||||
|
'label': '' # Label to show to users
|
||||||
|
'class': '' # Field class to use. Field classes are Python dot
|
||||||
|
paths to Django's form fields.
|
||||||
|
'initial': '' # Field initial value
|
||||||
|
'default': '' # Default value.
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
class_path = '' # Dot path to the actual class that will handle the mail
|
||||||
|
fields = ()
|
||||||
|
|
||||||
|
|
||||||
|
class MailerBackend(six.with_metaclass(MailerBackendMetaclass, MailerBackendBase)):
|
||||||
|
@classmethod
|
||||||
|
def get(cls, name):
|
||||||
|
return cls._registry[name]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_all(cls):
|
||||||
|
return cls._registry
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def initialize():
|
||||||
|
for app in apps.get_app_configs():
|
||||||
|
try:
|
||||||
|
import_module('{}.mailers'.format(app.name))
|
||||||
|
except ImportError as exception:
|
||||||
|
if force_text(exception) != 'No module named mailers':
|
||||||
|
logger.error(
|
||||||
|
'Error importing %s mailers.py file; %s', app.name,
|
||||||
|
exception
|
||||||
|
)
|
||||||
@@ -1,9 +1,17 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from acls.models import AccessControlList
|
||||||
|
from common.forms import DynamicModelForm
|
||||||
|
|
||||||
|
from .classes import MailerBackend
|
||||||
|
from .models import UserMailer
|
||||||
|
from .permissions import permission_user_mailer_use
|
||||||
from .settings import (
|
from .settings import (
|
||||||
setting_document_body_template, setting_document_subject_template,
|
setting_document_body_template, setting_document_subject_template,
|
||||||
setting_link_body_template, setting_link_subject_template
|
setting_link_body_template, setting_link_subject_template
|
||||||
@@ -13,6 +21,7 @@ from .settings import (
|
|||||||
class DocumentMailForm(forms.Form):
|
class DocumentMailForm(forms.Form):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
as_attachment = kwargs.pop('as_attachment', False)
|
as_attachment = kwargs.pop('as_attachment', False)
|
||||||
|
user = kwargs.pop('user', None)
|
||||||
super(DocumentMailForm, self).__init__(*args, **kwargs)
|
super(DocumentMailForm, self).__init__(*args, **kwargs)
|
||||||
if as_attachment:
|
if as_attachment:
|
||||||
self.fields[
|
self.fields[
|
||||||
@@ -33,8 +42,70 @@ class DocumentMailForm(forms.Form):
|
|||||||
'project_title': settings.PROJECT_TITLE,
|
'project_title': settings.PROJECT_TITLE,
|
||||||
'project_website': settings.PROJECT_WEBSITE
|
'project_website': settings.PROJECT_WEBSITE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
queryset = AccessControlList.objects.filter_by_access(
|
||||||
|
permission=permission_user_mailer_use, user=user,
|
||||||
|
queryset=UserMailer.objects.filter(enabled=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.fields['user_mailer'].queryset = queryset
|
||||||
|
try:
|
||||||
|
self.fields['user_mailer'].initial = queryset.get(default=True)
|
||||||
|
except UserMailer.DoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
email = forms.EmailField(label=_('Email address'))
|
email = forms.EmailField(label=_('Email address'))
|
||||||
subject = forms.CharField(label=_('Subject'), required=False)
|
subject = forms.CharField(label=_('Subject'), required=False)
|
||||||
body = forms.CharField(
|
body = forms.CharField(
|
||||||
label=_('Body'), widget=forms.widgets.Textarea(), required=False
|
label=_('Body'), widget=forms.widgets.Textarea(), required=False
|
||||||
)
|
)
|
||||||
|
user_mailer = forms.ModelChoiceField(
|
||||||
|
label=_('Mailing profile'), queryset=UserMailer.objects.none()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class UserMailerBackendSelectionForm(forms.Form):
|
||||||
|
backend = forms.ChoiceField(choices=(), label=_('Backend'))
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(UserMailerBackendSelectionForm, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self.fields['backend'].choices = [
|
||||||
|
(
|
||||||
|
key, backend.label
|
||||||
|
) for key, backend in MailerBackend.get_all().items()
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class UserMailerDynamicForm(DynamicModelForm):
|
||||||
|
class Meta:
|
||||||
|
fields = ('label', 'default', 'enabled', 'backend_data')
|
||||||
|
model = UserMailer
|
||||||
|
widgets = {'backend_data': forms.widgets.HiddenInput}
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
result = super(UserMailerDynamicForm, self).__init__(*args, **kwargs)
|
||||||
|
if self.instance.backend_data:
|
||||||
|
for key, value in json.loads(self.instance.backend_data).items():
|
||||||
|
self.fields[key].initial = value
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
data = super(UserMailerDynamicForm, self).clean()
|
||||||
|
|
||||||
|
# Consolidate the dynamic fields into a single JSON field called
|
||||||
|
# 'backend_data'.
|
||||||
|
backend_data = {}
|
||||||
|
|
||||||
|
for field in self.schema['fields']:
|
||||||
|
backend_data[field['name']] = data.pop(
|
||||||
|
field['name'], field.get('default', None)
|
||||||
|
)
|
||||||
|
|
||||||
|
data['backend_data'] = json.dumps(backend_data)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class UserMailerTestForm(forms.Form):
|
||||||
|
email = forms.EmailField(label=_('Email address'))
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ from navigation import Link
|
|||||||
|
|
||||||
from .permissions import (
|
from .permissions import (
|
||||||
permission_mailing_link, permission_mailing_send_document,
|
permission_mailing_link, permission_mailing_send_document,
|
||||||
permission_view_error_log
|
permission_user_mailer_create, permission_user_mailer_delete,
|
||||||
|
permission_user_mailer_edit, permission_user_mailer_use,
|
||||||
|
permission_user_mailer_view, permission_view_error_log
|
||||||
)
|
)
|
||||||
|
|
||||||
link_send_document = Link(
|
link_send_document = Link(
|
||||||
@@ -23,7 +25,35 @@ link_send_multiple_document = Link(
|
|||||||
link_send_multiple_document_link = Link(
|
link_send_multiple_document_link = Link(
|
||||||
text=_('Email link'), view='mailer:send_multiple_document_link'
|
text=_('Email link'), view='mailer:send_multiple_document_link'
|
||||||
)
|
)
|
||||||
link_document_mailing_error_log = Link(
|
link_system_mailer_error_log = Link(
|
||||||
icon='fa fa-envelope', permissions=(permission_view_error_log,),
|
icon='fa fa-envelope', permissions=(permission_view_error_log,),
|
||||||
text=_('Document mailing error log'), view='mailer:error_log',
|
text=_('System mailer error log'), view='mailer:system_mailer_error_log',
|
||||||
|
)
|
||||||
|
link_user_mailer_create = Link(
|
||||||
|
icon='fa fa-envelope', permissions=(permission_user_mailer_create,),
|
||||||
|
text=_('User mailer create'), view='mailer:user_mailer_backend_selection',
|
||||||
|
)
|
||||||
|
link_user_mailer_delete = Link(
|
||||||
|
args='resolved_object.pk', permissions=(permission_user_mailer_delete,),
|
||||||
|
tags='dangerous', text=_('Delete'), view='mailer:user_mailer_delete',
|
||||||
|
)
|
||||||
|
link_user_mailer_edit = Link(
|
||||||
|
args='object.pk', permissions=(permission_user_mailer_edit,),
|
||||||
|
text=_('Edit'), view='mailer:user_mailer_edit',
|
||||||
|
)
|
||||||
|
link_user_mailer_log_list = Link(
|
||||||
|
args='object.pk', permissions=(permission_user_mailer_view,),
|
||||||
|
text=_('Log'), view='mailer:user_mailer_log',
|
||||||
|
)
|
||||||
|
link_user_mailer_list = Link(
|
||||||
|
icon='fa fa-envelope', permissions=(permission_user_mailer_view,),
|
||||||
|
text=_('User mailer list'), view='mailer:user_mailer_list',
|
||||||
|
)
|
||||||
|
link_user_mailer_setup = Link(
|
||||||
|
icon='fa fa-envelope', permissions=(permission_user_mailer_view,),
|
||||||
|
text=_('User mailers'), view='mailer:user_mailer_list',
|
||||||
|
)
|
||||||
|
link_user_mailer_test = Link(
|
||||||
|
args='object.pk', permissions=(permission_user_mailer_use,),
|
||||||
|
text=_('Test'), view='mailer:user_mailer_test',
|
||||||
)
|
)
|
||||||
|
|||||||
71
mayan/apps/mailer/mailers.py
Normal file
71
mayan/apps/mailer/mailers.py
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from .classes import MailerBackend
|
||||||
|
|
||||||
|
__all__ = ('DjangoSMTP', 'DjangoFileBased')
|
||||||
|
|
||||||
|
|
||||||
|
class DjangoSMTP(MailerBackend):
|
||||||
|
class_path = 'django.core.mail.backends.smtp.EmailBackend'
|
||||||
|
fields = (
|
||||||
|
{
|
||||||
|
'name': 'host', 'label': _('Host'),
|
||||||
|
'class': 'django.forms.CharField', 'default': 'localhost',
|
||||||
|
'help_text': _('The host to use for sending email.'),
|
||||||
|
'kwargs': {
|
||||||
|
'max_length': 48
|
||||||
|
}, 'required': False
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'port', 'label': _('Port'),
|
||||||
|
'class': 'django.forms.IntegerField', 'default': 25,
|
||||||
|
'help_text': _('Port to use for the SMTP server.'),
|
||||||
|
'required': False
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'user', 'label': _('Username'),
|
||||||
|
'class': 'django.forms.CharField', 'default': '',
|
||||||
|
'help_text': _(
|
||||||
|
'Username to use for the SMTP server. If empty, '
|
||||||
|
'authentication won\'t attempted.'
|
||||||
|
), 'kwargs': {
|
||||||
|
'max_length': 48
|
||||||
|
}, 'required': False
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'password', 'label': _('Password'),
|
||||||
|
'class': 'django.forms.CharField', 'default': '',
|
||||||
|
'help_text': _(
|
||||||
|
'Password to use for the SMTP server. This setting is used '
|
||||||
|
'in conjunction with the username when authenticating to '
|
||||||
|
'the SMTP server. If either of these settings is empty, '
|
||||||
|
'authentication won\'t be attempted.'
|
||||||
|
), 'kwargs': {
|
||||||
|
'max_length': 48
|
||||||
|
}, 'required': False
|
||||||
|
},
|
||||||
|
)
|
||||||
|
widgets = {
|
||||||
|
'password': {
|
||||||
|
'class': 'django.forms.widgets.PasswordInput',
|
||||||
|
'kwargs': {
|
||||||
|
'render_value': True
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
label = _('Django SMTP backend')
|
||||||
|
|
||||||
|
|
||||||
|
class DjangoFileBased(MailerBackend):
|
||||||
|
class_path = 'django.core.mail.backends.filebased.EmailBackend'
|
||||||
|
fields = (
|
||||||
|
{
|
||||||
|
'name': 'file_path', 'label': _('File path'),
|
||||||
|
'class': 'django.forms.CharField', 'kwargs': {
|
||||||
|
'max_length': 48
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
label = _('Django file based backend')
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2017-07-02 08:12
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('mailer', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='UserMailer',
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
'id', models.AutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False,
|
||||||
|
verbose_name='ID'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'label', models.CharField(
|
||||||
|
max_length=32, unique=True, verbose_name='Label'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'default', models.BooleanField(
|
||||||
|
default=True, verbose_name='Default'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'backend_path', models.CharField(
|
||||||
|
help_text='The dotted Python path to the backend '
|
||||||
|
'class.', max_length=128, verbose_name='Backend path'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'backend_data', models.TextField(
|
||||||
|
blank=True, verbose_name='Backend data'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ('label',),
|
||||||
|
'verbose_name': 'User mailer',
|
||||||
|
'verbose_name_plural': 'User mailers',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='UserMailerLogEntry',
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
'id', models.AutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False,
|
||||||
|
verbose_name='ID'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'datetime', models.DateTimeField(
|
||||||
|
auto_now_add=True, verbose_name='Date time'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'message', models.TextField(
|
||||||
|
blank=True, editable=False, verbose_name='Message'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'user_mailer', models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to='mailer.UserMailer', verbose_name='User mailer'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ('-datetime',),
|
||||||
|
'get_latest_by': 'datetime',
|
||||||
|
'verbose_name': 'User mailer log entry',
|
||||||
|
'verbose_name_plural': 'User mailer log entries',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
30
mayan/apps/mailer/migrations/0003_auto_20170703_1535.py
Normal file
30
mayan/apps/mailer/migrations/0003_auto_20170703_1535.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2017-07-03 15:35
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('mailer', '0002_usermailer_usermailerlogentry'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='usermailer',
|
||||||
|
name='enabled',
|
||||||
|
field=models.BooleanField(default=True, verbose_name='Enabled'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='usermailerlogentry',
|
||||||
|
name='user_mailer',
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name='error_log', to='mailer.UserMailer',
|
||||||
|
verbose_name='User mailer'
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from django.core import mail
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.utils.module_loading import import_string
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -21,3 +24,110 @@ class LogEntry(models.Model):
|
|||||||
ordering = ('-datetime',)
|
ordering = ('-datetime',)
|
||||||
verbose_name = _('Log entry')
|
verbose_name = _('Log entry')
|
||||||
verbose_name_plural = _('Log entries')
|
verbose_name_plural = _('Log entries')
|
||||||
|
|
||||||
|
|
||||||
|
class UserMailer(models.Model):
|
||||||
|
label = models.CharField(
|
||||||
|
max_length=32, unique=True, verbose_name=_('Label')
|
||||||
|
)
|
||||||
|
default = models.BooleanField(
|
||||||
|
default=True, help_text=_(
|
||||||
|
'If default, this mailing profile will be pre-selected on the '
|
||||||
|
'document mailing form.'
|
||||||
|
), verbose_name=_('Default')
|
||||||
|
)
|
||||||
|
enabled = models.BooleanField(default=True, verbose_name=_('Enabled'))
|
||||||
|
backend_path = models.CharField(
|
||||||
|
max_length=128,
|
||||||
|
help_text=_('The dotted Python path to the backend class.'),
|
||||||
|
verbose_name=_('Backend path')
|
||||||
|
)
|
||||||
|
backend_data = models.TextField(
|
||||||
|
blank=True, verbose_name=_('Backend data')
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ('label',)
|
||||||
|
verbose_name = _('User mailer')
|
||||||
|
verbose_name_plural = _('User mailers')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.label
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if self.default:
|
||||||
|
UserMailer.objects.select_for_update().exclude(pk=self.pk).update(
|
||||||
|
default=False
|
||||||
|
)
|
||||||
|
|
||||||
|
return super(UserMailer, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
def backend_label(self):
|
||||||
|
return self.get_backend().label
|
||||||
|
|
||||||
|
def get_backend(self):
|
||||||
|
return import_string(self.backend_path)
|
||||||
|
|
||||||
|
def get_connection(self):
|
||||||
|
return mail.get_connection(
|
||||||
|
backend=self.get_backend().class_path, **self.loads()
|
||||||
|
)
|
||||||
|
|
||||||
|
def loads(self):
|
||||||
|
return json.loads(self.backend_data)
|
||||||
|
|
||||||
|
def dumps(self, data):
|
||||||
|
self.backend_data = json.dumps(data)
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def send(self, **kwargs):
|
||||||
|
"""
|
||||||
|
https://docs.djangoproject.com/en/1.11/topics/email
|
||||||
|
#django.core.mail.EmailMessage
|
||||||
|
subject: The subject line of the email.
|
||||||
|
body: The body text. This should be a plain text message.
|
||||||
|
from_email: The sender's address. Both fred@example.com and Fred
|
||||||
|
<fred@example.com> forms are legal. If omitted,
|
||||||
|
the DEFAULT_FROM_EMAIL setting is used.
|
||||||
|
to: A list or tuple of recipient addresses.
|
||||||
|
bcc: A list or tuple of addresses used in the "Bcc" header when
|
||||||
|
sending the email.
|
||||||
|
connection: An email backend instance. Use this parameter if you want
|
||||||
|
to use the same connection for multiple messages. If omitted, a new
|
||||||
|
connection is created when send() is called.
|
||||||
|
attachments: A list of attachments to put on the message. These can be
|
||||||
|
either email.MIMEBase.MIMEBase instances, or (filename, content,
|
||||||
|
mimetype) triples.
|
||||||
|
headers: A dictionary of extra headers to put on the message. The
|
||||||
|
keys are the header name, values are the header values. It's up to
|
||||||
|
the caller to ensure header names and values are in the correct
|
||||||
|
format for an email message. The corresponding attribute is
|
||||||
|
extra_headers.
|
||||||
|
cc: A list or tuple of recipient addresses used in the "Cc"
|
||||||
|
header when sending the email.
|
||||||
|
reply_to: A list or tuple of recipient addresses used in the
|
||||||
|
"Reply-To" header when sending the email.
|
||||||
|
"""
|
||||||
|
with self.get_connection() as connection:
|
||||||
|
mail.EmailMessage(connection=connection, **kwargs).send()
|
||||||
|
|
||||||
|
def test(self, to):
|
||||||
|
self.send(to=to, subject=_('Test email from Mayan EDMS'))
|
||||||
|
|
||||||
|
|
||||||
|
class UserMailerLogEntry(models.Model):
|
||||||
|
user_mailer = models.ForeignKey(
|
||||||
|
UserMailer, related_name='error_log', verbose_name=_('User mailer')
|
||||||
|
)
|
||||||
|
datetime = models.DateTimeField(
|
||||||
|
auto_now_add=True, editable=False, verbose_name=_('Date time')
|
||||||
|
)
|
||||||
|
message = models.TextField(
|
||||||
|
blank=True, editable=False, verbose_name=_('Message')
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
get_latest_by = 'datetime'
|
||||||
|
ordering = ('-datetime',)
|
||||||
|
verbose_name = _('User mailer log entry')
|
||||||
|
verbose_name_plural = _('User mailer log entries')
|
||||||
|
|||||||
@@ -13,5 +13,20 @@ permission_mailing_send_document = namespace.add_permission(
|
|||||||
name='mail_document', label=_('Send document via email')
|
name='mail_document', label=_('Send document via email')
|
||||||
)
|
)
|
||||||
permission_view_error_log = namespace.add_permission(
|
permission_view_error_log = namespace.add_permission(
|
||||||
name='view_error_log', label=_('View document mailing error log')
|
name='view_error_log', label=_('View system mailing error log')
|
||||||
|
)
|
||||||
|
permission_user_mailer_create = namespace.add_permission(
|
||||||
|
name='user_mailer_create', label=_('Create an user mailer')
|
||||||
|
)
|
||||||
|
permission_user_mailer_delete = namespace.add_permission(
|
||||||
|
name='user_mailer_delete', label=_('Delete an user mailer')
|
||||||
|
)
|
||||||
|
permission_user_mailer_edit = namespace.add_permission(
|
||||||
|
name='user_mailer_edit', label=_('Edit an user mailer')
|
||||||
|
)
|
||||||
|
permission_user_mailer_view = namespace.add_permission(
|
||||||
|
name='user_mailer_view', label=_('View an user mailer')
|
||||||
|
)
|
||||||
|
permission_user_mailer_use = namespace.add_permission(
|
||||||
|
name='user_mailer_use', label=_('Use an user mailer')
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,17 +1,25 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.apps import apps
|
||||||
from django.core.mail import EmailMultiAlternatives
|
from django.core.mail import EmailMultiAlternatives
|
||||||
|
|
||||||
from documents.models import Document
|
from documents.models import Document
|
||||||
from mayan.celery import app
|
from mayan.celery import app
|
||||||
|
|
||||||
from .models import LogEntry
|
|
||||||
|
|
||||||
|
|
||||||
@app.task(ignore_result=True)
|
@app.task(ignore_result=True)
|
||||||
def task_send_document(subject_text, body_text_content, sender, recipient, document_id, as_attachment=False):
|
def task_send_document(subject_text, body_text_content, sender, recipient, document_id, user_mailer_id, as_attachment=False):
|
||||||
|
UserMailer = apps.get_model(
|
||||||
|
app_label='mailer', model_name='UserMailer'
|
||||||
|
)
|
||||||
|
|
||||||
|
user_mailer = UserMailer.objects.get(pk=user_mailer_id)
|
||||||
|
|
||||||
|
connection = user_mailer.get_connection()
|
||||||
|
|
||||||
email_msg = EmailMultiAlternatives(
|
email_msg = EmailMultiAlternatives(
|
||||||
subject_text, body_text_content, sender, [recipient]
|
subject_text, body_text_content, sender, [recipient],
|
||||||
|
connection=connection,
|
||||||
)
|
)
|
||||||
|
|
||||||
if as_attachment:
|
if as_attachment:
|
||||||
@@ -24,6 +32,6 @@ def task_send_document(subject_text, body_text_content, sender, recipient, docum
|
|||||||
try:
|
try:
|
||||||
email_msg.send()
|
email_msg.send()
|
||||||
except Exception as exception:
|
except Exception as exception:
|
||||||
LogEntry.objects.create(message=exception)
|
user_mailer.error_log.create(message=exception)
|
||||||
else:
|
else:
|
||||||
LogEntry.objects.all().delete()
|
user_mailer.error_log.all().delete()
|
||||||
|
|||||||
5
mayan/apps/mailer/tests/literals.py
Normal file
5
mayan/apps/mailer/tests/literals.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
TEST_EMAIL_ADDRESS = 'test@example.com'
|
||||||
|
TEST_USER_MAILER_LABEL = 'test user mailer label'
|
||||||
|
TEST_USER_MAILER_BACKEND_PATH = 'mailer.tests.mailers.TestBackend'
|
||||||
8
mayan/apps/mailer/tests/mailers.py
Normal file
8
mayan/apps/mailer/tests/mailers.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from ..classes import MailerBackend
|
||||||
|
|
||||||
|
|
||||||
|
class TestBackend(MailerBackend):
|
||||||
|
class_path = 'django.core.mail.backends.locmem.EmailBackend'
|
||||||
|
label = 'Django local memory backend'
|
||||||
@@ -2,66 +2,145 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from django.core import mail
|
from django.core import mail
|
||||||
|
|
||||||
from documents.tests.test_views import GenericDocumentViewTestCase
|
from documents.tests.test_views import (
|
||||||
|
GenericDocumentViewTestCase, GenericViewTestCase
|
||||||
from ..permissions import (
|
|
||||||
permission_mailing_link, permission_mailing_send_document
|
|
||||||
)
|
)
|
||||||
|
|
||||||
TEST_EMAIL_ADDRESS = 'test@example.com'
|
from ..models import UserMailer
|
||||||
|
from ..permissions import (
|
||||||
|
permission_mailing_link, permission_mailing_send_document,
|
||||||
|
permission_user_mailer_use, permission_user_mailer_view
|
||||||
|
)
|
||||||
|
|
||||||
|
from .literals import (
|
||||||
|
TEST_EMAIL_ADDRESS, TEST_USER_MAILER_BACKEND_PATH, TEST_USER_MAILER_LABEL
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MailerViewsTestCase(GenericDocumentViewTestCase):
|
class MailerTestMixin(object):
|
||||||
def test_mail_link_view_no_permissions(self):
|
def _create_user_mailer(self):
|
||||||
self.login_user()
|
|
||||||
|
|
||||||
response = self.post(
|
self.user_mailer = UserMailer.objects.create(
|
||||||
'mailer:send_document_link', args=(self.document.pk,),
|
default=True,
|
||||||
data={'email': TEST_EMAIL_ADDRESS},
|
enabled=True,
|
||||||
|
label=TEST_USER_MAILER_LABEL,
|
||||||
|
backend_path=TEST_USER_MAILER_BACKEND_PATH,
|
||||||
|
backend_data='{}'
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 302)
|
|
||||||
|
class MailerViewsTestCase(MailerTestMixin, GenericDocumentViewTestCase):
|
||||||
|
def _request_document_link_send(self):
|
||||||
|
return self.post(
|
||||||
|
'mailer:send_document_link', args=(self.document.pk,),
|
||||||
|
data={
|
||||||
|
'email': TEST_EMAIL_ADDRESS,
|
||||||
|
'user_mailer': self.user_mailer.pk
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_document_send(self):
|
||||||
|
return self.post(
|
||||||
|
'mailer:send_document', args=(self.document.pk,),
|
||||||
|
data={
|
||||||
|
'email': TEST_EMAIL_ADDRESS,
|
||||||
|
'user_mailer': self.user_mailer.pk
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_mail_link_view_no_permissions(self):
|
||||||
|
self._create_user_mailer()
|
||||||
|
self.login_user()
|
||||||
|
|
||||||
|
response = self._request_document_link_send()
|
||||||
|
|
||||||
|
self.assertContains(
|
||||||
|
response, 'Select a valid choice', status_code=200
|
||||||
|
)
|
||||||
|
|
||||||
def test_mail_link_view_with_permission(self):
|
def test_mail_link_view_with_permission(self):
|
||||||
|
self._create_user_mailer()
|
||||||
self.login_user()
|
self.login_user()
|
||||||
|
|
||||||
self.grant(permission_mailing_link)
|
self.grant(permission_mailing_link)
|
||||||
|
self.grant(permission_user_mailer_use)
|
||||||
|
|
||||||
response = self.post(
|
self._request_document_link_send()
|
||||||
'mailer:send_document_link', args=(self.document.pk,),
|
|
||||||
data={'email': TEST_EMAIL_ADDRESS},
|
|
||||||
follow=True
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertContains(
|
|
||||||
response, 'queued', status_code=200
|
|
||||||
)
|
|
||||||
self.assertEqual(len(mail.outbox), 1)
|
self.assertEqual(len(mail.outbox), 1)
|
||||||
self.assertEqual(mail.outbox[0].to, [TEST_EMAIL_ADDRESS])
|
self.assertEqual(mail.outbox[0].to, [TEST_EMAIL_ADDRESS])
|
||||||
|
|
||||||
def test_mail_document_view_no_permissions(self):
|
def test_mail_document_view_no_permissions(self):
|
||||||
|
self._create_user_mailer()
|
||||||
self.login_user()
|
self.login_user()
|
||||||
|
|
||||||
response = self.post(
|
response = self._request_document_send()
|
||||||
'mailer:send_document', args=(self.document.pk,),
|
self.assertContains(
|
||||||
data={'email': TEST_EMAIL_ADDRESS},
|
response, 'Select a valid choice', status_code=200
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 302)
|
|
||||||
|
|
||||||
def test_mail_document_view_with_permission(self):
|
def test_mail_document_view_with_permission(self):
|
||||||
|
self._create_user_mailer()
|
||||||
self.login_user()
|
self.login_user()
|
||||||
|
|
||||||
self.grant(permission_mailing_send_document)
|
self.grant(permission_mailing_send_document)
|
||||||
|
self.grant(permission_user_mailer_use)
|
||||||
|
|
||||||
response = self.post(
|
self._request_document_send()
|
||||||
'mailer:send_document', args=(self.document.pk,),
|
|
||||||
data={'email': TEST_EMAIL_ADDRESS},
|
self.assertEqual(len(mail.outbox), 1)
|
||||||
follow=True
|
self.assertEqual(mail.outbox[0].to, [TEST_EMAIL_ADDRESS])
|
||||||
|
|
||||||
|
|
||||||
|
class UserMailerViewTestCase(MailerTestMixin, GenericViewTestCase):
|
||||||
|
def _request_user_mailer_delete(self):
|
||||||
|
return self.post(
|
||||||
|
'mailer:user_mailer_delete', args=(self.user_mailer.pk,)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_user_mailer_list_view_no_permissions(self):
|
||||||
|
self._create_user_mailer()
|
||||||
|
self.login_user()
|
||||||
|
|
||||||
|
response = self.get(
|
||||||
|
'mailer:user_mailer_list',
|
||||||
|
)
|
||||||
|
self.assertNotContains(
|
||||||
|
response, text=self.user_mailer.label, status_code=200
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_user_mailer_list_view_with_permissions(self):
|
||||||
|
self._create_user_mailer()
|
||||||
|
self.login_user()
|
||||||
|
|
||||||
|
self.grant(permission_user_mailer_view)
|
||||||
|
|
||||||
|
response = self.get(
|
||||||
|
'mailer:user_mailer_list',
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertContains(
|
self.assertContains(
|
||||||
response, 'queued', status_code=200
|
response, text=self.user_mailer.label, status_code=200
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_user_mailer_delete_view_no_permissions(self):
|
||||||
|
self._create_user_mailer()
|
||||||
|
self.login_user()
|
||||||
|
|
||||||
|
self._request_user_mailer_delete()
|
||||||
|
|
||||||
|
self.assertQuerysetEqual(
|
||||||
|
UserMailer.objects.all(), (repr(self.user_mailer),)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_user_mailer_delete_view_with_permissions(self):
|
||||||
|
self._create_user_mailer()
|
||||||
|
self.login_user()
|
||||||
|
|
||||||
|
self.grant(permission_user_mailer_view)
|
||||||
|
|
||||||
|
self._request_user_mailer_delete()
|
||||||
|
|
||||||
|
self.assertNotEqual(
|
||||||
|
[UserMailer.objects.all()], [self.user_mailer]
|
||||||
)
|
)
|
||||||
self.assertEqual(len(mail.outbox), 1)
|
|
||||||
self.assertEqual(mail.outbox[0].to, [TEST_EMAIL_ADDRESS])
|
|
||||||
|
|||||||
@@ -2,7 +2,12 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
|
|
||||||
from .views import LogEntryListView, MailDocumentLinkView, MailDocumentView
|
from .views import (
|
||||||
|
SystemMailerLogEntryListView, MailDocumentLinkView, MailDocumentView,
|
||||||
|
UserMailerBackendSelectionView, UserMailingCreateView,
|
||||||
|
UserMailingDeleteView, UserMailingEditView, UserMailerLogEntryListView,
|
||||||
|
UserMailerTestView, UserMailerListView
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(
|
url(
|
||||||
@@ -22,6 +27,36 @@ urlpatterns = [
|
|||||||
name='send_multiple_document'
|
name='send_multiple_document'
|
||||||
),
|
),
|
||||||
url(
|
url(
|
||||||
r'^log/$', LogEntryListView.as_view(), name='error_log'
|
r'^system_mailer/log/$', SystemMailerLogEntryListView.as_view(),
|
||||||
|
name='system_mailer_error_log'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r'^user_mailers/backend/selection',
|
||||||
|
UserMailerBackendSelectionView.as_view(),
|
||||||
|
name='user_mailer_backend_selection'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r'^user_mailers/(?P<class_path>[a-zA-Z0-9_.]+)/create/$',
|
||||||
|
UserMailingCreateView.as_view(), name='user_mailer_create'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r'^user_mailers/(?P<pk>\d+)/delete/$', UserMailingDeleteView.as_view(),
|
||||||
|
name='user_mailer_delete'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r'^user_mailers/(?P<pk>\d+)/edit/$', UserMailingEditView.as_view(),
|
||||||
|
name='user_mailer_edit'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r'^user_mailers/(?P<pk>\d+)/log/$',
|
||||||
|
UserMailerLogEntryListView.as_view(), name='user_mailer_log'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r'^user_mailers/(?P<pk>\d+)/test/$',
|
||||||
|
UserMailerTestView.as_view(), name='user_mailer_test'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r'^user_mailers/$', UserMailerListView.as_view(),
|
||||||
|
name='user_mailer_list'
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,23 +1,37 @@
|
|||||||
from __future__ import absolute_import, unicode_literals
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
|
from django.core.urlresolvers import reverse, reverse_lazy
|
||||||
|
from django.http import Http404, HttpResponseRedirect
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
from django.template import Context, Template
|
from django.template import Context, Template
|
||||||
from django.utils.html import strip_tags
|
from django.utils.html import strip_tags
|
||||||
from django.utils.translation import ungettext, ugettext_lazy as _
|
from django.utils.translation import ungettext, ugettext_lazy as _
|
||||||
|
|
||||||
from common.generics import MultipleObjectFormActionView, SingleObjectListView
|
from acls.models import AccessControlList
|
||||||
|
from common.generics import (
|
||||||
|
FormView, MultipleObjectFormActionView, SingleObjectDeleteView,
|
||||||
|
SingleObjectDynamicFormCreateView, SingleObjectDynamicFormEditView,
|
||||||
|
SingleObjectListView
|
||||||
|
)
|
||||||
from documents.models import Document
|
from documents.models import Document
|
||||||
|
|
||||||
from .forms import DocumentMailForm
|
from .classes import MailerBackend
|
||||||
from .models import LogEntry
|
from .forms import (
|
||||||
|
DocumentMailForm, UserMailerBackendSelectionForm, UserMailerDynamicForm,
|
||||||
|
UserMailerTestForm
|
||||||
|
)
|
||||||
|
from .models import LogEntry, UserMailer
|
||||||
from .permissions import (
|
from .permissions import (
|
||||||
permission_mailing_link, permission_mailing_send_document,
|
permission_mailing_link, permission_mailing_send_document,
|
||||||
permission_view_error_log
|
permission_user_mailer_create, permission_user_mailer_delete,
|
||||||
|
permission_user_mailer_edit, permission_user_mailer_use,
|
||||||
|
permission_user_mailer_view, permission_view_error_log
|
||||||
)
|
)
|
||||||
from .tasks import task_send_document
|
from .tasks import task_send_document
|
||||||
|
|
||||||
|
|
||||||
class LogEntryListView(SingleObjectListView):
|
class SystemMailerLogEntryListView(SingleObjectListView):
|
||||||
extra_context = {
|
extra_context = {
|
||||||
'hide_object': True,
|
'hide_object': True,
|
||||||
'title': _('Document mailing error log'),
|
'title': _('Document mailing error log'),
|
||||||
@@ -65,7 +79,8 @@ class MailDocumentView(MultipleObjectFormActionView):
|
|||||||
|
|
||||||
def get_form_extra_kwargs(self):
|
def get_form_extra_kwargs(self):
|
||||||
return {
|
return {
|
||||||
'as_attachment': self.as_attachment
|
'as_attachment': self.as_attachment,
|
||||||
|
'user': self.request.user
|
||||||
}
|
}
|
||||||
|
|
||||||
def object_action(self, form, instance):
|
def object_action(self, form, instance):
|
||||||
@@ -83,13 +98,19 @@ class MailDocumentView(MultipleObjectFormActionView):
|
|||||||
subject_template = Template(form.cleaned_data['subject'])
|
subject_template = Template(form.cleaned_data['subject'])
|
||||||
subject_text = strip_tags(subject_template.render(context))
|
subject_text = strip_tags(subject_template.render(context))
|
||||||
|
|
||||||
|
AccessControlList.objects.check_access(
|
||||||
|
permissions=permission_user_mailer_use, user=self.request.user,
|
||||||
|
obj=form.cleaned_data['user_mailer']
|
||||||
|
)
|
||||||
|
|
||||||
task_send_document.apply_async(
|
task_send_document.apply_async(
|
||||||
args=(
|
args=(
|
||||||
subject_text, body_text_content, self.request.user.email,
|
subject_text, body_text_content, self.request.user.email,
|
||||||
form.cleaned_data['email']
|
form.cleaned_data['email']
|
||||||
), kwargs={
|
), kwargs={
|
||||||
'document_id': instance.pk,
|
'document_id': instance.pk,
|
||||||
'as_attachment': self.as_attachment
|
'as_attachment': self.as_attachment,
|
||||||
|
'user_mailer_id': form.cleaned_data['user_mailer'].pk
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -104,3 +125,133 @@ class MailDocumentLinkView(MailDocumentView):
|
|||||||
title = 'Email document link'
|
title = 'Email document link'
|
||||||
title_plural = 'Email document links'
|
title_plural = 'Email document links'
|
||||||
title_document = 'Email link for document: %s'
|
title_document = 'Email link for document: %s'
|
||||||
|
|
||||||
|
|
||||||
|
class UserMailerBackendSelectionView(FormView):
|
||||||
|
extra_context = {
|
||||||
|
'title': _('New mailing profile backend selection'),
|
||||||
|
}
|
||||||
|
form_class = UserMailerBackendSelectionForm
|
||||||
|
view_permission = permission_user_mailer_create
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
backend = form.cleaned_data['backend']
|
||||||
|
return HttpResponseRedirect(
|
||||||
|
reverse('mailer:user_mailer_create', args=(backend,),)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class UserMailingCreateView(SingleObjectDynamicFormCreateView):
|
||||||
|
form_class = UserMailerDynamicForm
|
||||||
|
post_action_redirect = reverse_lazy('mailer:user_mailer_list')
|
||||||
|
view_permission = permission_user_mailer_create
|
||||||
|
|
||||||
|
def get_backend(self):
|
||||||
|
try:
|
||||||
|
return MailerBackend.get(name=self.kwargs['class_path'])
|
||||||
|
except KeyError:
|
||||||
|
raise Http404(
|
||||||
|
'{} class not found'.format(self.kwargs['class_path'])
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'title': _(
|
||||||
|
'Create a "%s" mailing profile'
|
||||||
|
) % self.get_backend().label,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_form_schema(self):
|
||||||
|
return {
|
||||||
|
'fields': self.get_backend().fields,
|
||||||
|
'widgets': getattr(self.get_backend(), 'widgets', {})
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_instance_extra_data(self):
|
||||||
|
return {'backend_path': self.kwargs['class_path']}
|
||||||
|
|
||||||
|
|
||||||
|
class UserMailingDeleteView(SingleObjectDeleteView):
|
||||||
|
model = UserMailer
|
||||||
|
object_permission = permission_user_mailer_delete
|
||||||
|
post_action_redirect = reverse_lazy('mailer:user_mailer_list')
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'title': _('Delete mailing profile: %s') % self.get_object(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class UserMailingEditView(SingleObjectDynamicFormEditView):
|
||||||
|
form_class = UserMailerDynamicForm
|
||||||
|
model = UserMailer
|
||||||
|
object_permission = permission_user_mailer_edit
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
return super(UserMailingEditView, self).form_valid(form)
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'title': _('Edit mailing profile: %s') % self.get_object(),
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_form_schema(self):
|
||||||
|
return {
|
||||||
|
'fields': self.get_object().get_backend().fields,
|
||||||
|
'widgets': getattr(self.get_object().get_backend(), 'widgets', {})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class UserMailerLogEntryListView(SingleObjectListView):
|
||||||
|
model = LogEntry
|
||||||
|
view_permission = permission_user_mailer_view
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'hide_object': True,
|
||||||
|
'object': self.get_user_mailer(),
|
||||||
|
'title': _('%s error log') % self.get_user_mailer(),
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return self.get_user_mailer().error_log.all()
|
||||||
|
|
||||||
|
def get_user_mailer(self):
|
||||||
|
return get_object_or_404(UserMailer, pk=self.kwargs['pk'])
|
||||||
|
|
||||||
|
|
||||||
|
class UserMailerListView(SingleObjectListView):
|
||||||
|
extra_context = {
|
||||||
|
'hide_object': True,
|
||||||
|
'title': _('Mailing profile'),
|
||||||
|
}
|
||||||
|
model = UserMailer
|
||||||
|
object_permission = permission_user_mailer_view
|
||||||
|
|
||||||
|
def get_form_schema(self):
|
||||||
|
return {'fields': self.get_backend().fields}
|
||||||
|
|
||||||
|
|
||||||
|
class UserMailerTestView(FormView):
|
||||||
|
form_class = UserMailerTestForm
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
self.get_user_mailer().test(to=(form.cleaned_data['email'],))
|
||||||
|
return super(UserMailerTestView, self).form_valid(form=form)
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'hide_object': True,
|
||||||
|
'object': self.get_user_mailer(),
|
||||||
|
'submit_label': _('Test'),
|
||||||
|
'title': _('Test mailing profile: %s') % self.get_user_mailer(),
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_user_mailer(self):
|
||||||
|
user_mailer = get_object_or_404(UserMailer, pk=self.kwargs['pk'])
|
||||||
|
AccessControlList.objects.check_access(
|
||||||
|
permissions=permission_user_mailer_use, user=self.request.user,
|
||||||
|
obj=user_mailer
|
||||||
|
)
|
||||||
|
|
||||||
|
return user_mailer
|
||||||
|
|||||||
Reference in New Issue
Block a user