diff --git a/docbow_project/docbow/actions.py b/docbow_project/docbow/actions.py index 585f41c..694665c 100644 --- a/docbow_project/docbow/actions.py +++ b/docbow_project/docbow/actions.py @@ -18,22 +18,30 @@ def export_as_csv(modeladmin, request, queryset): field_names = [field.name for field in opts.fields] m2m_field_names = [m2m_field.name for m2m_field in opts.many_to_many] # Write a first row with header information - writer.writerow(field_names+m2m_field_names) + writer.writerow(field_names + m2m_field_names) # Write data rows for obj in queryset: - values = [ force_text(getattr(obj, field)) for field in field_names] + values = [force_text(getattr(obj, field)) for field in field_names] for m2m_field in m2m_field_names: value = getattr(obj, m2m_field) value = u','.join(map(force_text, value.all())) values.append(force_text(value)) writer.writerow(map(lambda x: force_str(x), values)) return response + + export_as_csv.short_description = _("Export selected objects as csv file") + def activate_selected(modeladmin, request, queryset): queryset.update(is_active=True) + + activate_selected.short_description = _('Activate selected objects') + def deactivate_selected(modeladmin, request, queryset): queryset.update(is_active=False) + + deactivate_selected.short_description = _('De-activate selected objects') diff --git a/docbow_project/docbow/admin.py b/docbow_project/docbow/admin.py index e306d82..d631d2c 100644 --- a/docbow_project/docbow/admin.py +++ b/docbow_project/docbow/admin.py @@ -8,6 +8,7 @@ from django.core.exceptions import PermissionDenied from django.urls import reverse, NoReverseMatch from django.conf.urls import url from django.utils.safestring import mark_safe + try: import thread except ImportError: @@ -33,39 +34,41 @@ class DocbowAdminSite(admin.AdminSite): site = DocbowAdminSite('docbow_admin') site.disable_action('delete_selected') + class DocumentAdmin(admin.ModelAdmin): - list_display = [ 'date', 'sender', 'recipients', 'filetype', 'filename_links', 'comment', 'private' ] - list_filter = [ 'sender', 'to_user', 'to_list', 'filetype', 'private'] - fields = [ 'date', 'sender', 'recipients', 'filetype', 'filename_links', 'comment', 'private' ] + list_display = ['date', 'sender', 'recipients', 'filetype', 'filename_links', 'comment', 'private'] + list_filter = ['sender', 'to_user', 'to_list', 'filetype', 'private'] + fields = ['date', 'sender', 'recipients', 'filetype', 'filename_links', 'comment', 'private'] readonly_fields = fields - filter_horizontal = [ 'to_user', 'to_list'] + filter_horizontal = ['to_user', 'to_list'] date_hierarchy = 'date' class SendingLimitationAdmin(admin.ModelAdmin): - list_display = [ 'mailing_list', 'filetypes_list', 'lists_list' ] - filter_horizontal = [ 'filetypes', 'lists' ] - actions = [ actions.export_as_csv, 'delete_selected' ] + list_display = ['mailing_list', 'filetypes_list', 'lists_list'] + filter_horizontal = ['filetypes', 'lists'] + actions = [actions.export_as_csv, 'delete_selected'] def lists_list(self, obj): '''Display method for the field lists''' return ', '.join(obj.lists.values_list('name', flat=True)) + lists_list.short_description = _('Limitation des destinataires') def filetypes_list(self, obj): '''Display method for the field filetypes''' return ', '.join(obj.filetypes.values_list('name', flat=True)) + filetypes_list.short_description = _('Limitation des types de fichier') class MailingListAdmin(admin.ModelAdmin): - list_display = [ 'name', 'is_active' ] - list_filter = [ 'is_active' ] - search_fields = [ 'name', 'members__username', 'members__first_name', - 'members__last_name' ] + list_display = ['name', 'is_active'] + list_filter = ['is_active'] + search_fields = ['name', 'members__username', 'members__first_name', 'members__last_name'] ordering = ['name'] form = forms.MailingListForm - actions = [ actions.export_as_csv ] + actions = [actions.export_as_csv] def get_actions(self, request): '''Show delete actions only if user has delete rights @@ -86,9 +89,7 @@ class AttachedFileAdmin(admin.ModelAdmin): def get_urls(self): urls = super(AttachedFileAdmin, self).get_urls() - attached_file_urls = [ - url(r'^(.+)/download/$', self.download) - ] + attached_file_urls = [url(r'^(.+)/download/$', self.download)] return attached_file_urls + urls def download(self, request, object_id): @@ -110,12 +111,17 @@ class DocbowUserAdmin(auth_admin.UserAdmin): (_('Important dates'), {'fields': ('last_login', 'date_joined')}), (_('Groups'), {'fields': ('groups',)}), ) - readonly_fields = [ 'last_login', 'date_joined' ] - exclude = [ 'user_permissions' ] - actions = [ actions.export_as_csv ] - list_display = auth_admin.UserAdmin.list_display + ( 'guest_account', - 'delegations', 'get_groups', 'get_lists', 'is_active') - inlines = [ DocbowProfileInlineAdmin ] + readonly_fields = ['last_login', 'date_joined'] + exclude = ['user_permissions'] + actions = [actions.export_as_csv] + list_display = auth_admin.UserAdmin.list_display + ( + 'guest_account', + 'delegations', + 'get_groups', + 'get_lists', + 'is_active', + ) + inlines = [DocbowProfileInlineAdmin] if 'mellon' in settings.INSTALLED_APPS: @@ -129,10 +135,12 @@ class DocbowUserAdmin(auth_admin.UserAdmin): def get_groups(self, user): return u', '.join(group.name for group in user.groups.all()) + get_groups.short_description = _('groups') def get_lists(self, user): return u', '.join(_list.name for _list in user.mailing_lists.all()) + get_lists.short_description = _('mailing lists') def get_actions(self, request): @@ -149,22 +157,24 @@ class DocbowUserAdmin(auth_admin.UserAdmin): return instance.docbowprofile.is_guest except models.DocbowProfile.DoesNotExist: return False + guest_account.boolean = True guest_account.short_description = _('Guest account') def delegations(self, instance): - from_users = auth_models.User.objects.filter( - delegations_to__to=instance) + from_users = auth_models.User.objects.filter(delegations_to__to=instance) return models.list_to_csv(from_users, models.username) + delegations.short_description = _('Delegations by') + class DocbowGroupAdmin(auth_admin.GroupAdmin): - exclude = [ 'permissions' ] + exclude = ['permissions'] class MailboxAdmin(admin.ModelAdmin): - list_display = [ 'owner', 'document', 'date' ] - list_filter = [ 'owner', 'outbox' ] + list_display = ['owner', 'document', 'date'] + list_filter = ['owner', 'outbox'] def lookup_allowed(self, *args, **kwargs): '''Allow complex filters''' @@ -172,9 +182,9 @@ class MailboxAdmin(admin.ModelAdmin): class InboxAdmin(MailboxAdmin): - list_display = [ 'date', 'owner', 'document' ] - fields = [ 'date', 'owner', 'document' ] - readonly_fields = [ 'date', 'owner', 'document' ] + list_display = ['date', 'owner', 'document'] + fields = ['date', 'owner', 'document'] + readonly_fields = ['date', 'owner', 'document'] def queryset(self, request): '''Only show input mailboxes''' @@ -184,7 +194,7 @@ class InboxAdmin(MailboxAdmin): class OutboxAdmin(MailboxAdmin): - list_display = [ 'date', 'owner', 'document' ] + list_display = ['date', 'owner', 'document'] fields = list_display readonly_fields = list_display @@ -197,14 +207,13 @@ class OutboxAdmin(MailboxAdmin): class ContentAdmin(admin.ModelAdmin): verbose_name = _('Predefined content description') - actions = [ actions.export_as_csv, 'delete_selected' ] + actions = [actions.export_as_csv, 'delete_selected'] class AutomaticForwardingAdmin(admin.ModelAdmin): - filter_horizontal = [ 'filetypes', 'originaly_to_user', 'forward_to_user', - 'forward_to_list' ] + filter_horizontal = ['filetypes', 'originaly_to_user', 'forward_to_user', 'forward_to_list'] form = forms.AutomaticForwardingForm - actions = [ actions.export_as_csv, 'delete_selected' ] + actions = [actions.export_as_csv, 'delete_selected'] def formfield_for_manytomany(self, db_field, request, **kwargs): if db_field.name in ('originaly_to_user', 'forward_to_user'): @@ -219,16 +228,20 @@ class FileTypeAttachedFileKindAdmin(admin.TabularInline): class FileTypeAdmin(admin.ModelAdmin): - list_display = [ 'name', 'is_active' ] - actions = [ actions.export_as_csv ] - inlines = [ FileTypeAttachedFileKindAdmin ] + list_display = ['name', 'is_active'] + actions = [actions.export_as_csv] + inlines = [FileTypeAttachedFileKindAdmin] class NotificationAdmin(admin.ModelAdmin): - search_fields = ['user__username', 'user__first_name', - 'user__last_name', 'user__docbowprofile__mobile_phone'] - list_display = [ 'create_dt', '_document', 'user', 'kind', 'done', 'failure' ] - readonly_fields = [ 'ctx' ] + search_fields = [ + 'user__username', + 'user__first_name', + 'user__last_name', + 'user__docbowprofile__mobile_phone', + ] + list_display = ['create_dt', '_document', 'user', 'kind', 'done', 'failure'] + readonly_fields = ['ctx'] date_hierarchy = 'create_dt' list_filter = ['user', 'kind', 'done'] actions = ['retry', 'delete_selected'] @@ -236,14 +249,14 @@ class NotificationAdmin(admin.ModelAdmin): def retry(self, request, queryset): queryset.update(done=False, failure=None) thread.start_new_thread(notification.process_notifications, ()) - retry.short_description = _('Clear failure and done field, resubmitting ' - 'the notifications.') + + retry.short_description = _('Clear failure and done field, resubmitting ' 'the notifications.') def object_link(self, obj): if obj is not None: - url = u'{0}:{1}_{2}_change'.format(self.admin_site.name, - obj.__class__._meta.app_label, - obj.__class__._meta.model_name) + url = u'{0}:{1}_{2}_change'.format( + self.admin_site.name, obj.__class__._meta.app_label, obj.__class__._meta.model_name + ) try: url = reverse(url, args=(obj.id,)) return u'{1}'.format(url, obj) @@ -253,6 +266,7 @@ class NotificationAdmin(admin.ModelAdmin): def _document(self, notification): return mark_safe(self.object_link(notification.document)) + _document.short_description = _('Document') @@ -262,21 +276,20 @@ class JournalAdmin(django_journal.admin.JournalAdmin): user, delegate = '', '' for objectdata in entry.objectdata_set.all(): if objectdata.tag.name == 'user': - user = self.object_filter_link(objectdata) + \ - self.object_link(objectdata) + user = self.object_filter_link(objectdata) + self.object_link(objectdata) if objectdata.tag.name == 'delegate': - delegate = self.object_filter_link(objectdata) + \ - self.object_link(objectdata) + delegate = self.object_filter_link(objectdata) + self.object_link(objectdata) if user and delegate: return mark_safe(delegate + _(' as ') + user) elif user: return mark_safe(user) return mark_safe(_('None')) + user.short_description = _('User') class DelegationAdmin(admin.ModelAdmin): - list_display = [ 'id', 'by', 'to' ] + list_display = ['id', 'by', 'to'] # Docbow Admin Site diff --git a/docbow_project/docbow/app_settings.py b/docbow_project/docbow/app_settings.py index 2615466..cb5d252 100644 --- a/docbow_project/docbow/app_settings.py +++ b/docbow_project/docbow/app_settings.py @@ -1,12 +1,12 @@ class AppSettings(object): __DEFAULTS = { - 'PERSONAL_EMAIL': True, - 'MOBILE_PHONE': True, - 'GROUP_LISTING': True, - 'PRIVATE_DOCUMENTS': False, - 'EDIT_EMAIL': False, - 'DELEGATE_TO_EXISTING_USER': True, - 'DEFAULT_ACCEPT_NOTIFICATIONS_FOR_GUEST': True, + 'PERSONAL_EMAIL': True, + 'MOBILE_PHONE': True, + 'GROUP_LISTING': True, + 'PRIVATE_DOCUMENTS': False, + 'EDIT_EMAIL': False, + 'DELEGATE_TO_EXISTING_USER': True, + 'DEFAULT_ACCEPT_NOTIFICATIONS_FOR_GUEST': True, } def __init__(self, prefix): @@ -16,6 +16,7 @@ class AppSettings(object): def settings(self): if not hasattr(self, '_settings'): from django.conf import settings + self._settings = settings return self._settings @@ -26,16 +27,21 @@ class AppSettings(object): @property def DOCBOW_MENU(self): from django.utils.translation import ugettext_noop - return getattr(self.settings, 'DOCBOW_MENU', [ - ('send-file-selector', ugettext_noop('send-file_menu')), - ('inbox', ugettext_noop('inbox_menu')), - ('outbox', ugettext_noop('outbox_menu')), - ('docbow_admin:index', ugettext_noop('admin_menu')), - ('profile', ugettext_noop('profile_menu')), - ('mailing-lists', ugettext_noop('mailing-lists')), - ('help', ugettext_noop('help_menu')), - ('contact', ugettext_noop('contact_menu')), - ]) + + return getattr( + self.settings, + 'DOCBOW_MENU', + [ + ('send-file-selector', ugettext_noop('send-file_menu')), + ('inbox', ugettext_noop('inbox_menu')), + ('outbox', ugettext_noop('outbox_menu')), + ('docbow_admin:index', ugettext_noop('admin_menu')), + ('profile', ugettext_noop('profile_menu')), + ('mailing-lists', ugettext_noop('mailing-lists')), + ('help', ugettext_noop('help_menu')), + ('contact', ugettext_noop('contact_menu')), + ], + ) @property def BASE_URL(self): @@ -47,7 +53,7 @@ class AppSettings(object): @property def MAX_FILE_SIZE(self): - return getattr(self.settings, 'DOCBOW_MAX_FILE_SIZE', 10*1024*1024) + return getattr(self.settings, 'DOCBOW_MAX_FILE_SIZE', 10 * 1024 * 1024) @property def MIME_BUFFER_SIZE(self): @@ -55,10 +61,11 @@ class AppSettings(object): def __getattr__(self, name): from django.conf import settings + if name not in self.__DEFAULTS: raise AttributeError - return getattr(settings, self.__prefix + name, - self.__DEFAULTS[name]) + return getattr(settings, self.__prefix + name, self.__DEFAULTS[name]) + import sys diff --git a/docbow_project/docbow/auth_backend.py b/docbow_project/docbow/auth_backend.py index dba1004..06f6b14 100644 --- a/docbow_project/docbow/auth_backend.py +++ b/docbow_project/docbow/auth_backend.py @@ -2,8 +2,7 @@ from django.contrib.auth.models import User def set_auth_hash_getter(user, delegate): - if hasattr(user, 'get_session_auth_hash') \ - and hasattr(delegate, 'get_session_auth_hash'): + if hasattr(user, 'get_session_auth_hash') and hasattr(delegate, 'get_session_auth_hash'): user.get_session_auth_hash = delegate.get_session_auth_hash @@ -38,7 +37,6 @@ try: import mellon.backends class DocbowMellonAuthBackend(mellon.backends.SAMLBackend): - def get_user(self, user_id): try: delegate = User.objects.get(pk=user_id, docbowprofile__is_guest=True) @@ -51,5 +49,6 @@ try: except User.DoesNotExist: return super(DocbowMellonAuthBackend, self).get_user(user_id) + except ImportError: pass diff --git a/docbow_project/docbow/auth_urls.py b/docbow_project/docbow/auth_urls.py index e2610f6..73d2e3b 100644 --- a/docbow_project/docbow/auth_urls.py +++ b/docbow_project/docbow/auth_urls.py @@ -8,32 +8,37 @@ from docbow_project.docbow import auth_views as docbow_auth_views urlpatterns = [ - url(r'^login/$', - docbow_auth_views.login, - {'template_name': 'registration/login.html'}, - name='auth_login'), - url(r'^logout/$', + url( + r'^login/$', docbow_auth_views.login, {'template_name': 'registration/login.html'}, name='auth_login' + ), + url( + r'^logout/$', auth_views.LogoutView.as_view(template_name='registration/logout.html'), - name='auth_logout'), - url(r'^password/change/$', - views.password_change, - name='auth_password_change'), - url(r'^password/change/done/$', + name='auth_logout', + ), + url(r'^password/change/$', views.password_change, name='auth_password_change'), + url( + r'^password/change/done/$', auth_views.PasswordChangeDoneView.as_view(), - name='auth_password_change_done'), - url(r'^password/reset/$', + name='auth_password_change_done', + ), + url( + r'^password/reset/$', auth_views.PasswordResetView.as_view( success_url=reverse_lazy('auth_password_reset_done'), - form_class=forms.PasswordResetFormWithLogging + form_class=forms.PasswordResetFormWithLogging, ), - name='auth_password_reset'), - url(r'^password/reset/confirm/(?P[0-9A-Za-z_\-]+)/(?P.+)/$', + name='auth_password_reset', + ), + url( + r'^password/reset/confirm/(?P[0-9A-Za-z_\-]+)/(?P.+)/$', auth_views.PasswordResetConfirmView.as_view(), - name='auth_password_reset_confirm'), - url(r'^password/reset/complete/$', + name='auth_password_reset_confirm', + ), + url( + r'^password/reset/complete/$', auth_views.PasswordResetCompleteView.as_view(), - name='password_reset_complete'), - url(r'^password/reset/done/$', - views.password_reset_done, - name='auth_password_reset_done'), + name='password_reset_complete', + ), + url(r'^password/reset/done/$', views.password_reset_done, name='auth_password_reset_done'), ] diff --git a/docbow_project/docbow/auth_views.py b/docbow_project/docbow/auth_views.py index 3de3b24..e6a8562 100644 --- a/docbow_project/docbow/auth_views.py +++ b/docbow_project/docbow/auth_views.py @@ -7,6 +7,7 @@ from django.utils.six.moves.urllib import parse as urllib if 'mellon' in settings.INSTALLED_APPS: from mellon.utils import get_idps else: + def get_idps(): return [] @@ -15,8 +16,9 @@ def login(request, *args, **kwargs): if any(get_idps()): if 'next' not in request.GET: return HttpResponseRedirect(resolve_url('mellon_login')) - return HttpResponseRedirect(resolve_url('mellon_login') + '?next=' - + urllib.quote(request.GET.get('next'))) + return HttpResponseRedirect( + resolve_url('mellon_login') + '?next=' + urllib.quote(request.GET.get('next')) + ) return auth_views.LoginView.as_view(*args, **kwargs)(request) diff --git a/docbow_project/docbow/cbv.py b/docbow_project/docbow/cbv.py index baafa8a..251bb8a 100644 --- a/docbow_project/docbow/cbv.py +++ b/docbow_project/docbow/cbv.py @@ -1,4 +1,3 @@ - class FormWithRequestMixin(object): def get_form_kwargs(self, **kwargs): kwargs = super(FormWithRequestMixin, self).get_form_kwargs(**kwargs) @@ -15,6 +14,7 @@ class FormWithPrefixMixin(object): kwargs['prefix'] = self.prefix return kwargs + class FormWithPostTarget(FormWithPrefixMixin): def get_form_kwargs(self, **kwargs): kwargs = super(FormWithPostTarget, self).get_form_kwargs(**kwargs) @@ -25,4 +25,3 @@ class FormWithPostTarget(FormWithPrefixMixin): def is_post_target(self): return self.prefix + '-validate' in self.request.POST - diff --git a/docbow_project/docbow/context_processors.py b/docbow_project/docbow/context_processors.py index 72a5fd7..fe465e2 100644 --- a/docbow_project/docbow/context_processors.py +++ b/docbow_project/docbow/context_processors.py @@ -1,6 +1,7 @@ from django.conf import settings from django.shortcuts import resolve_url + def settings_url_processor(request): logout_url = settings.LOGOUT_URL logout_url = resolve_url(logout_url) diff --git a/docbow_project/docbow/decorators.py b/docbow_project/docbow/decorators.py index e14106f..32c1f76 100644 --- a/docbow_project/docbow/decorators.py +++ b/docbow_project/docbow/decorators.py @@ -9,16 +9,19 @@ from django.utils.decorators import available_attrs from django.utils.cache import patch_cache_control from django.views.decorators.cache import never_cache as old_never_cache + def no_delegate(view_func): ''' Forbid delegated account to use this view. ''' + @wraps(view_func, assigned=available_attrs(view_func)) def f(request, *args, **kwargs): if hasattr(request.user, 'delegate'): messages.warning(request, _('Your delegation does not allow you to do this action')) return redirect('inbox') return view_func(request, *args, **kwargs) + return f @@ -27,6 +30,7 @@ def as_delegate(view_func): Replace the effective user by the real user of the delegate for the given view. ''' + @wraps(view_func, assigned=available_attrs(view_func)) def f(request, *args, **kwargs): if hasattr(request.user, 'delegate'): @@ -37,12 +41,14 @@ def as_delegate(view_func): return out else: return view_func(request, *args, **kwargs) + return f def never_cache(view_func): '''Block client caching in all browsers.''' view_func = old_never_cache(view_func) + @wraps(view_func, assigned=available_attrs(view_func)) def f(request, *args, **kwargs): result = view_func(request, *args, **kwargs) @@ -50,4 +56,5 @@ def never_cache(view_func): patch_cache_control(result, no_store=True) patch_cache_control(result, must_revalidate=True) return result + return f diff --git a/docbow_project/docbow/email_utils.py b/docbow_project/docbow/email_utils.py index f64220a..1a12db6 100644 --- a/docbow_project/docbow/email_utils.py +++ b/docbow_project/docbow/email_utils.py @@ -9,12 +9,14 @@ sre = re.compile(r'\?=[ \t]+=\?') # re pat for MIME encoded_word (without trailing spaces) mre = re.compile(r'=\?[^?]*?\?[bq]\?[^? \t]*?\?=', re.I) + def decode_mime(m): # substitute matching encoded_word with force_text equiv. h = decode_header(m.group(0)) u = force_text(make_header(h)) return u + def u2u_decode(s): # utility function for (final) decoding of mime header # note: resulting string is in one line (no \n within) @@ -23,4 +25,3 @@ def u2u_decode(s): s = sre.sub('?==?', s) u = mre.sub(decode_mime, s) return u - diff --git a/docbow_project/docbow/fields.py b/docbow_project/docbow/fields.py index 7da46e2..0e82d66 100644 --- a/docbow_project/docbow/fields.py +++ b/docbow_project/docbow/fields.py @@ -7,27 +7,32 @@ from docbow_project.docbow import pyuca from docbow_project.docbow.models import username, MailingList from docbow_project.docbow.widgets import ForcedValueWidget, FilteredSelectMultiple + def order_choices(choices): '''Sort choices using Unicode collations''' - return sorted(list(choices), - key=lambda x: pyuca.collator.sort_key(x[1])) + return sorted(list(choices), key=lambda x: pyuca.collator.sort_key(x[1])) + def order_field_choices(field): '''Order choices of this field''' choices = list(field.choices) - field.choices = [choice for choice in choices if choice[1].startswith('---')] \ - + order_choices([choice for choice in field.choices if not choice[1].startswith('---')]) + field.choices = [choice for choice in choices if choice[1].startswith('---')] + order_choices( + [choice for choice in field.choices if not choice[1].startswith('---')] + ) print(field.choices) + class Func2Iter(object): '''Transform a generator producing function into an iterator''' + def __init__(self, func): self.func = func def __iter__(self): return self.func().__iter__() + class RecipientField(MultipleChoiceField): '''Field allowing selection among user or list for recipients''' @@ -64,12 +69,11 @@ class RecipientField(MultipleChoiceField): list_choices.append((i, mailing_list.name)) list_choices = order_choices(list_choices) if list_choices and user_choices: - choices = list_choices+[('', '---')]+user_choices + choices = list_choices + [('', '---')] + user_choices else: - choices = list_choices+user_choices + choices = list_choices + user_choices if len(choices) == 1: - self.widget = ForcedValueWidget(value=[choices[0][0]], - display_value=choices[0][1]) + self.widget = ForcedValueWidget(value=[choices[0][0]], display_value=choices[0][1]) else: self.widget = FilteredSelectMultiple(_('Recipients'), False) return choices @@ -80,5 +84,3 @@ class RecipientField(MultipleChoiceField): if not value: raise ValidationError(_(u'you must set at least one user recipient or one list recipient...')) return value - - diff --git a/docbow_project/docbow/forms.py b/docbow_project/docbow/forms.py index 3c286a8..e2532db 100644 --- a/docbow_project/docbow/forms.py +++ b/docbow_project/docbow/forms.py @@ -6,7 +6,7 @@ import logging import collections -from django.forms import (ModelForm, Form, Textarea, EmailField, CharField, ModelChoiceField) +from django.forms import ModelForm, Form, Textarea, EmailField, CharField, ModelChoiceField from django import forms from django.contrib.auth.models import User from django.utils.translation import ugettext as _ @@ -22,8 +22,16 @@ from django_journal import journal as django_journal from docbow_project.docbow.models import ( - Document, username, MailingList, Content, AttachedFile, AutomaticForwarding, DocbowProfile, - non_guest_users, is_guest, FileTypeAttachedFileKind + Document, + username, + MailingList, + Content, + AttachedFile, + AutomaticForwarding, + DocbowProfile, + non_guest_users, + is_guest, + FileTypeAttachedFileKind, ) from docbow_project.docbow.widgets import TextInpuWithPredefinedValues, JqueryFileUploadInput from docbow_project.docbow.fields import RecipientField @@ -56,8 +64,10 @@ class RecipientForm(object): self.fields['recipients'].list_qs = list_qs self.fields['recipients'].reset_choices() + class ForwardingForm(RecipientForm, Form): '''Form for forwarding documents''' + recipients = RecipientField(label=_('Recipients'), required=True) sender = ModelChoiceField(label=_('Sender'), queryset=User.objects.all()) @@ -73,10 +83,12 @@ class ForwardingForm(RecipientForm, Form): delegations = User.objects.none() else: self.default_sender = self.user - delegations = non_guest_users().filter( - Q(id=self.user.id) | - Q(delegations_to__to=self.user)) \ - .order_by('last_name', 'first_name', 'username').distinct() + delegations = ( + non_guest_users() + .filter(Q(id=self.user.id) | Q(delegations_to__to=self.user)) + .order_by('last_name', 'first_name', 'username') + .distinct() + ) super(ForwardingForm, self).__init__(*args, **kwargs) if len(delegations) > 1: self.fields['sender'].queryset = delegations @@ -99,23 +111,19 @@ def max_filename_length(): max_length = field.max_length return max_length - len(prefix) + class FileForm(RecipientForm, ModelForm): '''Form for creating a new mailing''' + user = None recipients = RecipientField(label=_('Recipients')) class Meta: model = Document - exclude = ('filetype', 'date', 'to_user', 'to_list', '_timestamp', 'real_sender', - 'reply_to') + exclude = ('filetype', 'date', 'to_user', 'to_list', '_timestamp', 'real_sender', 'reply_to') class Media: - css = { - 'all': ( - 'docbow/css/send-file.css', - 'docbow/css/send_file_form.css' - ) - } + css = {'all': ('docbow/css/send-file.css', 'docbow/css/send_file_form.css')} js = ('js/askdirtyform.js', 'js/url-preload.js') def __init__(self, *args, **kwargs): @@ -129,10 +137,9 @@ class FileForm(RecipientForm, ModelForm): if self.reply_to: doc = self.reply_to initial['sender'] = kwargs.get('user', None) - initial['recipients'] = [ 'user-%s' % doc.sender.id ] + initial['recipients'] = ['user-%s' % doc.sender.id] initial['comment'] = u'Re: ' + doc.comment - super(FileForm, self).__init__(*args, **kwargs) self.content_fields = [] if self.attached_file_kinds: @@ -143,22 +150,24 @@ class FileForm(RecipientForm, ModelForm): label = attached_file_kind.name mime_types = attached_file_kind.get_mime_types() widget = JqueryFileUploadInput( - max_filename_length=max_filename_length(), - extensions=mime_types_to_extensions(mime_types), - attached_file_kind=attached_file_kind) + max_filename_length=max_filename_length(), + extensions=mime_types_to_extensions(mime_types), + attached_file_kind=attached_file_kind, + ) self.fields[key] = forms.Field(label=label, widget=widget) insert_index += 1 else: attached_file_kind = FileTypeAttachedFileKind(mime_types='*/*') - self.content_fields = [ ('content', attached_file_kind) ] + self.content_fields = [('content', attached_file_kind)] widget = JqueryFileUploadInput( - max_filename_length=max_filename_length(), - attached_file_kind=attached_file_kind) + max_filename_length=max_filename_length(), attached_file_kind=attached_file_kind + ) self.fields['content'] = forms.Field(label=_('Attached files'), widget=widget) old_widget = self.fields['comment'].widget - self.fields['comment'].widget = TextInpuWithPredefinedValues(attrs=old_widget.attrs, - choices=self.get_predefined_comments()) + self.fields['comment'].widget = TextInpuWithPredefinedValues( + attrs=old_widget.attrs, choices=self.get_predefined_comments() + ) if len(self.delegations) > 1: self.fields['sender'].queryset = self.delegations self.fields['sender'].label_from_instance = lambda y: username(y) @@ -177,8 +186,8 @@ class FileForm(RecipientForm, ModelForm): '''Return a list of predefined comments, structured as choice list for a form field. ''' - choices = [ (content.description,)*2 for content in Content.objects.all() ] - choices.insert(0, ('---','---')) + choices = [(content.description,) * 2 for content in Content.objects.all()] + choices.insert(0, ('---', '---')) return choices def clean(self): @@ -197,9 +206,9 @@ class FileForm(RecipientForm, ModelForm): if not attached_file_kind.match_file(upload_file): mime_types = attached_file_kind.get_mime_types() file_name = os.path.basename(upload_file.name) - msg = _(u'The file name "{file_name}" does not match the patterns "{extensions}".').format( - file_name=file_name, - extensions=mime_types_to_extensions(mime_types)) + msg = _( + u'The file name "{file_name}" does not match the patterns "{extensions}".' + ).format(file_name=file_name, extensions=mime_types_to_extensions(mime_types)) errors.append(msg) if errors: self._errors[field] = self.error_class(errors) @@ -224,19 +233,21 @@ class FileForm(RecipientForm, ModelForm): upload_id, uploaded_files = self.cleaned_data.get(field, (None, [])) for uploaded_file in uploaded_files: uploaded_file.name = os.path.basename(uploaded_file.name) - AttachedFile(document=instance, - kind=attached_file_kind if attached_file_kind.id else None, - name=truncate_filename(uploaded_file.name), - content=uploaded_file).save() + AttachedFile( + document=instance, + kind=attached_file_kind if attached_file_kind.id else None, + name=truncate_filename(uploaded_file.name), + content=uploaded_file, + ).save() class ContactForm(Form): '''Form to contact administrators of the platform for logged in users.''' - subject = forms.CharField(max_length=100, label=_('Subject'), - required=True) - message = forms.CharField(widget=Textarea(attrs={'rows': 25, 'cols': 80}), - label=_('Message'), required=True) + subject = forms.CharField(max_length=100, label=_('Subject'), required=True) + message = forms.CharField( + widget=Textarea(attrs={'rows': 25, 'cols': 80}), label=_('Message'), required=True + ) class Media: js = ('js/askdirtyform.js',) @@ -247,8 +258,7 @@ class AnonymousContactForm(ContactForm): name = forms.CharField(max_length=100, label=_('Name'), required=True) email = forms.EmailField(max_length=100, label=_('Email'), required=False) - phone_number = forms.CharField(max_length=100, label=_('Phone number'), - required=False) + phone_number = forms.CharField(max_length=100, label=_('Phone number'), required=False) class MailingListForm(ModelForm): @@ -258,14 +268,13 @@ class MailingListForm(ModelForm): model = MailingList fields = ('name', 'is_active', 'members', 'mailing_list_members') widgets = { - 'members': AdminFilteredSelectMultiple(_('Persons'), False), + 'members': AdminFilteredSelectMultiple(_('Persons'), False), } def __init__(self, *args, **kwargs): '''Orders members by their username, use their username to display them.''' ModelForm.__init__(self, *args, **kwargs) - self.fields['members'].queryset = non_guest_users() \ - .order_by('username') + self.fields['members'].queryset = non_guest_users().order_by('username') self.fields['members'].label_from_instance = lambda y: username(y) @@ -278,11 +287,11 @@ class UserChoiceField(ModelChoiceField): class DelegationForm(Form): '''Form to manager delegations of users''' + first_name = CharField(label=_('Firstname'), max_length=30, required=False) last_name = CharField(label=_('Lastname'), max_length=30, required=False) email = EmailField(label=_('Email'), required=False) - existing_user = UserChoiceField(label=_('User'), - required=False, queryset=User.objects.all()) + existing_user = UserChoiceField(label=_('User'), required=False, queryset=User.objects.all()) def __init__(self, *args, **kwargs): self.user = kwargs.pop('user', None) @@ -306,11 +315,17 @@ class DelegationForm(Form): new = ok1 and ok2 and ok3 ok4 = bool(cleaned_data.get('existing_user')) if not ((ok1 or ok2 or ok3) ^ ok4): - raise ValidationError(_('You must choose between creating a new ' - 'user or delegating to an existing one; the two are mutually ' - 'exclusive.')) + raise ValidationError( + _( + 'You must choose between creating a new ' + 'user or delegating to an existing one; the two are mutually ' + 'exclusive.' + ) + ) if not new and (ok1 or ok2 or ok3): - raise ValidationError(_('To create a new user you must give a first name, a last name and a valid email')) + raise ValidationError( + _('To create a new user you must give a first name, a last name and a valid email') + ) if new: email = cleaned_data.get('email') if email == self.user.email: @@ -322,8 +337,11 @@ class DelegationForm(Form): list_of_names = u', '.join([user.get_full_name() for user in qs]) self.data = {} self.is_bound = False - raise ValidationError(_('This email belong to existing user(s) {0}, look in the list of existing users') \ - .format(list_of_names)) + raise ValidationError( + _('This email belong to existing user(s) {0}, look in the list of existing users').format( + list_of_names + ) + ) if 'mellon' in app_settings.settings.INSTALLED_APPS: # Create user @@ -333,7 +351,7 @@ class DelegationForm(Form): 'last_name': cleaned_data['last_name'], 'email': email, 'send_registration_email': True, - 'send_registration_email_next_url': self._request.build_absolute_uri('/') + 'send_registration_email_next_url': self._request.build_absolute_uri('/'), } err, json_data, err_desc = a2_wscall(url, 'post', json) if err: @@ -345,7 +363,8 @@ class DelegationForm(Form): if role_uuid: url = urllib.parse.urljoin( app_settings.settings.AUTHENTIC_URL, - 'api/roles/%s/members/%s/' % (role_uuid, json_data['uuid'])) + 'api/roles/%s/members/%s/' % (role_uuid, json_data['uuid']), + ) err, json_data, err_desc = a2_wscall(url, 'post') if err: raise ValidationError(err_desc) @@ -389,8 +408,10 @@ class ProfileForm(ModelForm): ModelForm.__init__(self, *args, **kwargs) if app_settings.MOBILE_PHONE: - self.fields['mobile_phone'].help_text = _('Use international phone number ' - 'format, i.e +[country code][number]. A challenge SMS will be sent to you to validate it.') + self.fields['mobile_phone'].help_text = _( + 'Use international phone number ' + 'format, i.e +[country code][number]. A challenge SMS will be sent to you to validate it.' + ) def clean_mobile_phone(self): '''Validate the mobile phone number by sending a HMAC signature as a code by SMS @@ -404,22 +425,30 @@ class ProfileForm(ModelForm): self.cleaned_data['mobile_phone'] = mobile_phone if not self.instance or self.instance.mobile_phone != mobile_phone: date = datetime.date.today() - code = hmac.new(force_bytes(settings.SECRET_KEY), force_text(date) + - mobile_phone, hashlib.sha1).hexdigest() + code = hmac.new( + force_bytes(settings.SECRET_KEY), force_text(date) + mobile_phone, hashlib.sha1 + ).hexdigest() code = '%06d' % (int(code, 16) % 1000000) key = '%s-code' % self.prefix if self.prefix else 'code' if self.data.get(key, '').strip() != code: + def send_sms(mobile_phone, code): try: sms_carrier = notification.SMSNotifier.get_carrier() sms_carrier.send_sms((mobile_phone,), 'code is ' + code, no_stop=False) return True except Exception: - logger.exception('unable to send SMS verification code %r to %r', - code, mobile_phone) - self.request.record('error', 'unable to send SMS verification code {code} to {mobile_phone}', - mobile_phone=mobile_phone, code=code) + logger.exception( + 'unable to send SMS verification code %r to %r', code, mobile_phone + ) + self.request.record( + 'error', + 'unable to send SMS verification code {code} to {mobile_phone}', + mobile_phone=mobile_phone, + code=code, + ) return False + if not send_sms(mobile_phone, code): raise ValidationError(_('Unable to send you the SMS code, try again.')) self.fields['code'] = CharField(label='Code') @@ -470,10 +499,11 @@ class PasswordResetFormWithLogging(PasswordResetForm): """ identifier = self.cleaned_data["identifier"] self.users_cache = User.objects.filter( - Q(email__iexact=identifier)| - Q(username=identifier)| - Q(docbowprofile__personal_email=identifier), - is_active=True).distinct() + Q(email__iexact=identifier) + | Q(username=identifier) + | Q(docbowprofile__personal_email=identifier), + is_active=True, + ).distinct() for user in self.users_cache: try: if not user.email or _unicode_ci_compare(user.docbowprofile.personal_email, identifier): @@ -482,18 +512,28 @@ class PasswordResetFormWithLogging(PasswordResetForm): pass self.users_cache = [user for user in self.users_cache if user.email] if not len(self.users_cache): - raise forms.ValidationError(_("That e-mail address or identifier doesn't have an associated user account. Are you sure you've registered?")) + raise forms.ValidationError( + _( + "That e-mail address or identifier doesn't have an associated user account. Are you sure you've registered?" + ) + ) return identifier def get_users(self, *args): return self.users_cache - def save(self, domain_override=None, - subject_template_name='registration/password_reset_subject.txt', - email_template_name='registration/password_reset_email.html', - use_https=False, token_generator=default_token_generator, - from_email=None, request=None, html_email_template_name=None, - extra_email_context=None): + def save( + self, + domain_override=None, + subject_template_name='registration/password_reset_subject.txt', + email_template_name='registration/password_reset_email.html', + use_https=False, + token_generator=default_token_generator, + from_email=None, + request=None, + html_email_template_name=None, + extra_email_context=None, + ): """ Generates a one-use only link for resetting password and sends to the user. @@ -519,20 +559,28 @@ class PasswordResetFormWithLogging(PasswordResetForm): if extra_email_context is not None: context.update(extra_email_context) self.send_mail( - subject_template_name, email_template_name, context, from_email, - user_email, html_email_template_name=html_email_template_name, + subject_template_name, + email_template_name, + context, + from_email, + user_email, + html_email_template_name=html_email_template_name, ) for user in self.users_cache: - django_journal.record('password-reset', 'password reset link sent to {email}', user=user, - email=user.email, ip=get_extra()['ip']) + django_journal.record( + 'password-reset', + 'password reset link sent to {email}', + user=user, + email=user.email, + ip=get_extra()['ip'], + ) class PasswordChangeFormWithLogging(PasswordChangeForm): def save(self, *args, **kwargs): super(PasswordChangeFormWithLogging, self).save(*args, **kwargs) - django_journal.record('password-change', 'changed its email', user=self.user, - ip=get_extra()['ip']) + django_journal.record('password-change', 'changed its email', user=self.user, ip=get_extra()['ip']) class FilterForm(forms.Form): @@ -542,11 +590,11 @@ class FilterForm(forms.Form): class Media: js = ( - 'jquery-ui/js/jquery-ui-1.12.1-autocomplete-datepicker.min.js', - 'docbow/js/filter-form.js', + 'jquery-ui/js/jquery-ui-1.12.1-autocomplete-datepicker.min.js', + 'docbow/js/filter-form.js', ) css = { - 'all': ('jquery-ui/css/jquery-ui-1.12.1.css',), + 'all': ('jquery-ui/css/jquery-ui-1.12.1.css',), } def __init__(self, *args, **kwargs): @@ -557,27 +605,26 @@ class FilterForm(forms.Form): for field in ('sort', 'page'): if field in request.GET: - self.fields[field] = forms.CharField( - initial=request.GET.get(field), - widget=forms.HiddenInput) + self.fields[field] = forms.CharField(initial=request.GET.get(field), widget=forms.HiddenInput) def clean(self): cleaned_data = super(FilterForm, self).clean() - if cleaned_data.get('not_before') and cleaned_data.get('not_after') and \ - cleaned_data['not_before'] > cleaned_data['not_after']: - raise ValidationError(_('From must be inferior or equal to To')) + if ( + cleaned_data.get('not_before') + and cleaned_data.get('not_after') + and cleaned_data['not_before'] > cleaned_data['not_after'] + ): + raise ValidationError(_('From must be inferior or equal to To')) return cleaned_data class EmailForm(ModelForm): - old_email = forms.EmailField(label=_('Old email'), required=False, - widget=forms.TextInput(attrs={'disabled': 'on'})) - password = forms.CharField(label=_("Password"), - widget=forms.PasswordInput()) + old_email = forms.EmailField( + label=_('Old email'), required=False, widget=forms.TextInput(attrs={'disabled': 'on'}) + ) + password = forms.CharField(label=_("Password"), widget=forms.PasswordInput()) email = forms.EmailField(label=_('New email'), required=True, initial='') - email2 = forms.EmailField(label=_('New email (repeated)'), - required=True, - widget=forms.TextInput()) + email2 = forms.EmailField(label=_('New email (repeated)'), required=True, widget=forms.TextInput()) class Meta: model = User @@ -604,7 +651,6 @@ class EmailForm(ModelForm): class NotificationPreferencesForm(Form): - class Media: js = ('docbow/js/checkall.js',) @@ -631,12 +677,13 @@ class NotificationPreferencesForm(Form): choices=self.choices, initial=self.initials[filetype.id], widget=widgets.CheckboxMultipleSelect, - required=False) + required=False, + ) def save(self): cleaned_data = self.cleaned_data - adds = collections.defaultdict(lambda:[]) - removes = collections.defaultdict(lambda:[]) + adds = collections.defaultdict(lambda: []) + removes = collections.defaultdict(lambda: []) for key in cleaned_data: filetype_id = int(key.split('-')[1]) new = set(cleaned_data[key]) @@ -648,15 +695,11 @@ class NotificationPreferencesForm(Form): adds[kind].append(filetype_id) for kind in adds: models.NotificationPreference.objects.filter( - user=self.user, - kind=kind, - filetype__in=adds[kind]).delete() + user=self.user, kind=kind, filetype__in=adds[kind] + ).delete() for kind in removes: for filetype_id in removes[kind]: filetype = models.FileType.objects.get(id=filetype_id) - np, created = models.NotificationPreference \ - .objects.get_or_create( - user=self.user, - kind=kind, - filetype=filetype, - value=False) + np, created = models.NotificationPreference.objects.get_or_create( + user=self.user, kind=kind, filetype=filetype, value=False + ) diff --git a/docbow_project/docbow/management/commands/add-list.py b/docbow_project/docbow/management/commands/add-list.py index b707142..2b72323 100644 --- a/docbow_project/docbow/management/commands/add-list.py +++ b/docbow_project/docbow/management/commands/add-list.py @@ -15,37 +15,29 @@ def get_object(model, ref): else: return model.objects.get(name=ref) + class Command(BaseCommand): help = '''Create or update a list''' def add_arguments(self, parser): + parser.add_argument('ml_name', type=str, help='Name of the mailing list') parser.add_argument( - 'ml_name', type=str, - help='Name of the mailing list') - parser.add_argument( - "--add-list", action="append", - help='add a list as a sublist', default=[], + "--add-list", action="append", help='add a list as a sublist', default=[], ) parser.add_argument( - "--remove-list", action="append", - help='remove list as a sublist', default=[], - ) - parser.add_argument( - "--add-user", action="append", - help='add a user member', default=[] - ) - parser.add_argument( - "--remove-user", action="append", - help='remove a user member', default=[] + "--remove-list", action="append", help='remove list as a sublist', default=[], ) + parser.add_argument("--add-user", action="append", help='add a user member', default=[]) + parser.add_argument("--remove-user", action="append", help='remove a user member', default=[]) @transaction.atomic def handle(self, **options): locale.setlocale(locale.LC_ALL, '') locale_encoding = locale.nl_langinfo(locale.CODESET) mailing_list, created = MailingList.objects.get_or_create( - name=force_text(options['ml_name'], locale_encoding)) + name=force_text(options['ml_name'], locale_encoding) + ) try: for l in options['add_list']: l = get_object(MailingList, l) @@ -54,7 +46,7 @@ class Command(BaseCommand): l = get_object(MailingList, l) mailing_list.mailing_list_members.remove(l) except MailingList.DoesNotExist: - raise CommandError('list %r does not exist' % l) + raise CommandError('list %r does not exist' % l) try: for g in options['add_user']: g = get_object(User, g) diff --git a/docbow_project/docbow/management/commands/add-user.py b/docbow_project/docbow/management/commands/add-user.py index 6c366ae..55ae097 100644 --- a/docbow_project/docbow/management/commands/add-user.py +++ b/docbow_project/docbow/management/commands/add-user.py @@ -15,6 +15,7 @@ def get_object(model, ref): else: return model.objects.get(name=ref) + class Command(BaseCommand): args = '' help = '''Create a new user or update an existing user @@ -23,31 +24,25 @@ List and groups can be referred by name or by id. ''' option_list = BaseCommand.option_list + ( - make_option("--first-name", help='set first name'), - make_option("--last-name", help='set last name'), - make_option("--email", help='set email'), - make_option("--mobile-phone", - help='set mobile phone used for SMS notifications'), - make_option("--personal-email", help='set personal email'), - make_option("--add-list", - help='add user to list', action="append", default=[]), - make_option("--add-group", - help='add user to group', action="append", default=[]), - make_option("--remove-list", - help='remove user from list', action="append", default=[]), - make_option("--remove-group", - help='remove user from group', action="append", default=[]), - make_option("--activate", action="store_true", - help='activate the user (default at creation)', default=None), - make_option("--deactivate", dest='activate', action="store_false", - help='deactivate the user'), - make_option("--superuser", action="store_true", - help='set the superuser flag', default=None), - make_option("--no-superuser", dest='superuser', - action="store_false", help='unset the superuser flag'), + make_option("--first-name", help='set first name'), + make_option("--last-name", help='set last name'), + make_option("--email", help='set email'), + make_option("--mobile-phone", help='set mobile phone used for SMS notifications'), + make_option("--personal-email", help='set personal email'), + make_option("--add-list", help='add user to list', action="append", default=[]), + make_option("--add-group", help='add user to group', action="append", default=[]), + make_option("--remove-list", help='remove user from list', action="append", default=[]), + make_option("--remove-group", help='remove user from group', action="append", default=[]), + make_option( + "--activate", action="store_true", help='activate the user (default at creation)', default=None + ), + make_option("--deactivate", dest='activate', action="store_false", help='deactivate the user'), + make_option("--superuser", action="store_true", help='set the superuser flag', default=None), + make_option( + "--no-superuser", dest='superuser', action="store_false", help='unset the superuser flag' + ), ) - @transaction.atomic def handle(self, *args, **options): if len(args) != 1: @@ -71,7 +66,7 @@ List and groups can be referred by name or by id. l = get_object(MailingList, l) l.members.remove(user) except MailingList.DoesNotExist: - raise CommandError('list %r does not exist' % l) + raise CommandError('list %r does not exist' % l) try: for g in options['add_group']: g = get_object(Group, g) diff --git a/docbow_project/docbow/management/commands/archive.py b/docbow_project/docbow/management/commands/archive.py index f654a11..e315fb7 100644 --- a/docbow_project/docbow/management/commands/archive.py +++ b/docbow_project/docbow/management/commands/archive.py @@ -14,6 +14,7 @@ from django.conf import settings from ... import models from ....log import models as log_models + class Command(BaseCommand): args = '' help = 'Save logs, and user list' @@ -24,12 +25,7 @@ class Command(BaseCommand): def save_users(self, path): with open(os.path.join(path, 'users.csv'), 'w') as f: - headers = ['username', - 'prenom', - 'nom', - 'email', - 'profil', - 'groupe'] + headers = ['username', 'prenom', 'nom', 'email', 'profil', 'groupe'] csv_handle = csv.DictWriter(f, headers) users = auth_models.User.objects.filter(is_superuser=False) csv_handle.writerow(dict(zip(csv_handle.fieldnames, csv_handle.fieldnames))) @@ -42,19 +38,15 @@ class Command(BaseCommand): 'nom': user.last_name, 'email': user.email, 'profil': ','.join([ml.name for ml in user.mailing_lists.all()]), - 'groupe': ','.join([group.name for group in user.groups.all()])} + 'groupe': ','.join([group.name for group in user.groups.all()]), + } self.dict_to_utf8(d) csv_handle.writerow(d) users.delete() def save_logs(self, path): with open(os.path.join(path, 'log.csv'), 'w') as f: - headers = ['timestamp', - 'name', - 'levelname', - 'ip', - 'user', - 'message'] + headers = ['timestamp', 'name', 'levelname', 'ip', 'user', 'message'] csv_handle = csv.DictWriter(f, headers) csv_handle.writerow(dict(zip(csv_handle.fieldnames, csv_handle.fieldnames))) logs = log_models.LogLine.objects.all() diff --git a/docbow_project/docbow/management/commands/clean-documents.py b/docbow_project/docbow/management/commands/clean-documents.py index a598665..ff6835d 100644 --- a/docbow_project/docbow/management/commands/clean-documents.py +++ b/docbow_project/docbow/management/commands/clean-documents.py @@ -2,8 +2,10 @@ from __future__ import print_function from django.core.management.base import BaseCommand from ... import models + class Command(BaseCommand): '''Undelete all documents''' + help = 'Undelete all documents' def handle(self, *args, **options): diff --git a/docbow_project/docbow/management/commands/docbow_dumpdata.py b/docbow_project/docbow/management/commands/docbow_dumpdata.py index feb9287..b33971a 100644 --- a/docbow_project/docbow/management/commands/docbow_dumpdata.py +++ b/docbow_project/docbow/management/commands/docbow_dumpdata.py @@ -6,25 +6,60 @@ from django.utils.datastructures import SortedDict from optparse import make_option + class Command(BaseCommand): option_list = BaseCommand.option_list + ( - make_option('--format', default='json', dest='format', - help='Specifies the output serialization format for fixtures.'), - make_option('--indent', default=None, dest='indent', type='int', - help='Specifies the indent level to use when pretty-printing output'), - make_option('--database', action='store', dest='database', - default=DEFAULT_DB_ALIAS, help='Nominates a specific database to dump ' - 'fixtures from. Defaults to the "default" database.'), - make_option('-e', '--exclude', dest='exclude',action='append', default=[], - help='An appname or appname.ModelName to exclude (use multiple --exclude to exclude multiple apps/models).'), - make_option('-n', '--natural', action='store_true', dest='use_natural_keys', default=False, - help='Use natural keys if they are available.'), - make_option('-a', '--all', action='store_true', dest='use_base_manager', default=False, - help="Use Django's base manager to dump all models stored in the database, including those that would otherwise be filtered or modified by a custom manager."), + make_option( + '--format', + default='json', + dest='format', + help='Specifies the output serialization format for fixtures.', + ), + make_option( + '--indent', + default=None, + dest='indent', + type='int', + help='Specifies the indent level to use when pretty-printing output', + ), + make_option( + '--database', + action='store', + dest='database', + default=DEFAULT_DB_ALIAS, + help='Nominates a specific database to dump ' + 'fixtures from. Defaults to the "default" database.', + ), + make_option( + '-e', + '--exclude', + dest='exclude', + action='append', + default=[], + help='An appname or appname.ModelName to exclude (use multiple --exclude to exclude multiple apps/models).', + ), + make_option( + '-n', + '--natural', + action='store_true', + dest='use_natural_keys', + default=False, + help='Use natural keys if they are available.', + ), + make_option( + '-a', + '--all', + action='store_true', + dest='use_base_manager', + default=False, + help="Use Django's base manager to dump all models stored in the database, including those that would otherwise be filtered or modified by a custom manager.", + ), + ) + help = ( + "Output the contents of the database as a fixture of the given " + "format (using each model's default manager unless --all is " + "specified)." ) - help = ("Output the contents of the database as a fixture of the given " - "format (using each model's default manager unless --all is " - "specified).") args = '[appname appname.ModelName ...]' def handle(self, *app_labels, **options): @@ -55,7 +90,11 @@ class Command(BaseCommand): raise CommandError('Unknown app in excludes: %s' % exclude) if len(app_labels) == 0: - app_list = SortedDict((app, [ model for model in get_models(app) if model not in excluded_models ]) for app in get_apps() if app not in excluded_apps) + app_list = SortedDict( + (app, [model for model in get_models(app) if model not in excluded_models]) + for app in get_apps() + if app not in excluded_apps + ) else: app_list = SortedDict() for label in app_labels: @@ -110,13 +149,15 @@ class Command(BaseCommand): objects.extend(model._default_manager.using(using).all()) try: - return serializers.serialize(format, objects, indent=indent, - use_natural_foreign_keys=use_natural_keys) + return serializers.serialize( + format, objects, indent=indent, use_natural_foreign_keys=use_natural_keys + ) except Exception as e: if show_traceback: raise raise CommandError("Unable to serialize database: %s" % e) + def sort_dependencies(app_list): """Sort a list of app,modellist pairs into a single list of models. @@ -125,6 +166,7 @@ def sort_dependencies(app_list): dependency has it's dependencies serialized first. """ from django.db.models import get_model, get_models + # Process the list of models, and get the list of dependencies model_dependencies = [] models = set() @@ -186,9 +228,12 @@ def sort_dependencies(app_list): else: skipped.append((model, deps)) if not changed: - raise CommandError("Can't resolve dependencies for %s in serialized app list." % - ', '.join('%s.%s' % (model._meta.app_label, model._meta.object_name) - for model, deps in sorted(skipped, key=lambda obj: obj[0].__name__)) + raise CommandError( + "Can't resolve dependencies for %s in serialized app list." + % ', '.join( + '%s.%s' % (model._meta.app_label, model._meta.object_name) + for model, deps in sorted(skipped, key=lambda obj: obj[0].__name__) + ) ) model_dependencies = skipped diff --git a/docbow_project/docbow/management/commands/dump-users-csv.py b/docbow_project/docbow/management/commands/dump-users-csv.py index 96273bb..8e15480 100644 --- a/docbow_project/docbow/management/commands/dump-users-csv.py +++ b/docbow_project/docbow/management/commands/dump-users-csv.py @@ -6,6 +6,7 @@ from django.db import transaction from ... import models + class Command(BaseCommand): args = '' help = 'Dump user list as CSV' @@ -16,13 +17,7 @@ class Command(BaseCommand): def save_users(self, path): with open(path, 'w') as f: - headers = ['username', - 'password', - 'prenom', - 'nom', - 'email', - 'profil', - 'groupe'] + headers = ['username', 'password', 'prenom', 'nom', 'email', 'profil', 'groupe'] csv_handle = csv.DictWriter(f, headers) users = auth_models.User.objects.filter(is_superuser=False, delegations_by__isnull=True) csv_handle.writerow(dict(zip(csv_handle.fieldnames, csv_handle.fieldnames))) @@ -36,7 +31,8 @@ class Command(BaseCommand): 'email': user.email, 'password': user.password, 'profil': ','.join([ml.name for ml in user.mailing_lists.all()]), - 'groupe': ','.join([group.name for group in user.groups.all()])} + 'groupe': ','.join([group.name for group in user.groups.all()]), + } self.dict_to_utf8(d) csv_handle.writerow(d) diff --git a/docbow_project/docbow/management/commands/empty-trash.py b/docbow_project/docbow/management/commands/empty-trash.py index 002b584..3ab6db1 100644 --- a/docbow_project/docbow/management/commands/empty-trash.py +++ b/docbow_project/docbow/management/commands/empty-trash.py @@ -14,7 +14,8 @@ class Command(BaseCommand): @transaction.atomic def handle(self, *args, **kwargs): target_date = now() - timedelta(days=settings.TRASH_DURATION) - for deleted_doc in DeletedDocument.objects.filter(soft_delete=True)\ - .filter(soft_delete_date__lte=target_date): + for deleted_doc in DeletedDocument.objects.filter(soft_delete=True).filter( + soft_delete_date__lte=target_date + ): deleted_doc.soft_delete = False deleted_doc.save() diff --git a/docbow_project/docbow/management/commands/forward-docs.py b/docbow_project/docbow/management/commands/forward-docs.py index c6449cc..7e5a35a 100644 --- a/docbow_project/docbow/management/commands/forward-docs.py +++ b/docbow_project/docbow/management/commands/forward-docs.py @@ -25,25 +25,26 @@ class Command(BaseCommand): parser.add_argument('from_user', type=int) parser.add_argument('to_user', type=int) parser.add_argument( - '-s', '--startdate', required=False, type=valid_date, - help='The start date - format YYYY-MM-DD' + '-s', '--startdate', required=False, type=valid_date, help='The start date - format YYYY-MM-DD' ) parser.add_argument( - '-e', '--enddate', required=False, type=valid_date, - help='The end date - format YYYY-MM-DD' + '-e', '--enddate', required=False, type=valid_date, help='The end date - format YYYY-MM-DD' ) @transaction.atomic def handle(self, from_user, to_user, *args, **kwargs): - verbose = (kwargs.get('verbosity') > 1) + verbose = kwargs.get('verbosity') > 1 from_user = User.objects.get(pk=from_user) to_user = User.objects.get(pk=to_user) from_user_inboxes = Inbox.objects.filter(owner=from_user, outbox=False) to_user_inboxes = Inbox.objects.filter(owner=to_user, outbox=False) - docs = Document.objects.exclude(deleteddocument__user=from_user) \ - .filter(mailboxes__in=from_user_inboxes).exclude(mailboxes__in=to_user_inboxes)\ + docs = ( + Document.objects.exclude(deleteddocument__user=from_user) + .filter(mailboxes__in=from_user_inboxes) + .exclude(mailboxes__in=to_user_inboxes) .distinct() + ) startdate, enddate = kwargs.get('startdate'), kwargs.get('enddate') if startdate: diff --git a/docbow_project/docbow/management/commands/list-lists.py b/docbow_project/docbow/management/commands/list-lists.py index 89405ad..2fc3c94 100644 --- a/docbow_project/docbow/management/commands/list-lists.py +++ b/docbow_project/docbow/management/commands/list-lists.py @@ -10,38 +10,41 @@ from django.utils.encoding import force_text from ...models import MailingList from ...unicodecsv import UnicodeWriter + def print_table(table): col_width = [max(len(x) for x in col) for col in zip(*table)] for line in table: - line = u"| " + u" | ".join(u"{0:>{1}}".format(x, col_width[i]) - for i, x in enumerate(line)) + u" |" + line = u"| " + u" | ".join(u"{0:>{1}}".format(x, col_width[i]) for i, x in enumerate(line)) + u" |" print(line) + class Command(BaseCommand): args = '' help = '''List mailing lists''' - option_list = BaseCommand.option_list + ( - make_option("--csv", action='store_true'), - ) + option_list = BaseCommand.option_list + (make_option("--csv", action='store_true'),) @transaction.atomic def handle(self, *args, **options): locale.setlocale(locale.LC_ALL, '') locale_encoding = locale.nl_langinfo(locale.CODESET) - mailing_lists = MailingList.objects.prefetch_related('members', - 'mailing_list_members') + mailing_lists = MailingList.objects.prefetch_related('members', 'mailing_list_members') for arg in args: key, value = arg.split('=') - mailing_lists = mailing_lists.filter( - **{key: value}) + mailing_lists = mailing_lists.filter(**{key: value}) tables = [('Id', 'Name', 'Members', 'List Members')] for mailing_list in mailing_lists: - tables.append(map(force_text, ( - mailing_list.id, - mailing_list.name, - ','.join(m.name for m in mailing_list.mailing_list_members.all()), - ','.join(g.username for g in mailing_list.members.all())))) + tables.append( + map( + force_text, + ( + mailing_list.id, + mailing_list.name, + ','.join(m.name for m in mailing_list.mailing_list_members.all()), + ','.join(g.username for g in mailing_list.members.all()), + ), + ) + ) if options['csv']: writer = UnicodeWriter(sys.stdout, encoding=locale_encoding) for row in tables: diff --git a/docbow_project/docbow/management/commands/list-users.py b/docbow_project/docbow/management/commands/list-users.py index 03dd5a3..895167d 100644 --- a/docbow_project/docbow/management/commands/list-users.py +++ b/docbow_project/docbow/management/commands/list-users.py @@ -11,31 +11,42 @@ from django.utils.encoding import force_text from ...models import DocbowProfile from ...unicodecsv import UnicodeWriter + def print_table(table): col_width = [max(len(x) for x in col) for col in zip(*table)] for line in table: - line = u"| " + u" | ".join(u"{0:>{1}}".format(x, col_width[i]) - for i, x in enumerate(line)) + u" |" + line = u"| " + u" | ".join(u"{0:>{1}}".format(x, col_width[i]) for i, x in enumerate(line)) + u" |" print(line) + class Command(BaseCommand): args = '' help = '''List users''' - option_list = BaseCommand.option_list + ( - make_option("--csv", action='store_true'), - ) + option_list = BaseCommand.option_list + (make_option("--csv", action='store_true'),) @transaction.atomic def handle(self, *args, **options): locale.setlocale(locale.LC_ALL, '') - users = User.objects.prefetch_related('docbowprofile', 'groups', - 'mailing_lists') + users = User.objects.prefetch_related('docbowprofile', 'groups', 'mailing_lists') for arg in args: key, value = arg.split('=') users = users.filter(**{key: value}) - tables = [('Id', 'Username', 'First name', 'Last name', 'Email', - 'Mobile phone', 'Personal mail', 'Lists', 'Groups', 'Active', 'Superuser')] + tables = [ + ( + 'Id', + 'Username', + 'First name', + 'Last name', + 'Email', + 'Mobile phone', + 'Personal mail', + 'Lists', + 'Groups', + 'Active', + 'Superuser', + ) + ] for user in users: try: mobile_phone = user.docbowprofile.mobile_phone @@ -43,24 +54,27 @@ class Command(BaseCommand): except DocbowProfile.DoesNotExist: mobile_phone = '' personal_email = '' - tables.append(map(force_text, ( - user.id, - user.username, - user.first_name, - user.last_name, - user.email, - mobile_phone, - personal_email, - ','.join(m.name for m in user.mailing_lists.all()), - ','.join(g.name for g in user.groups.all()), - user.is_active, - user.is_superuser))) + tables.append( + map( + force_text, + ( + user.id, + user.username, + user.first_name, + user.last_name, + user.email, + mobile_phone, + personal_email, + ','.join(m.name for m in user.mailing_lists.all()), + ','.join(g.name for g in user.groups.all()), + user.is_active, + user.is_superuser, + ), + ) + ) if options['csv']: writer = UnicodeWriter(sys.stdout, encoding=locale.nl_langinfo(locale.CODESET)) for row in tables: writer.writerow(row) else: print_table(tables) - - - diff --git a/docbow_project/docbow/management/commands/load-users-csv.py b/docbow_project/docbow/management/commands/load-users-csv.py index 4937f6e..9b57e52 100644 --- a/docbow_project/docbow/management/commands/load-users-csv.py +++ b/docbow_project/docbow/management/commands/load-users-csv.py @@ -14,16 +14,16 @@ from ... import models def strip_accents(s): - return ''.join((c for c in unicodedata.normalize('NFD', s) - if unicodedata.category(c) != 'Mn')) + return ''.join((c for c in unicodedata.normalize('NFD', s) if unicodedata.category(c) != 'Mn')) + def keep_letters(s): return ''.join([c for c in s if c.isalpha()]) + def unicode_csv_reader(utf8_csv_data, dialect=csv.excel, **kwargs): # csv.py doesn't do Unicode; encode temporarily as UTF-8: - csv_reader = csv.reader(utf8_csv_data, - dialect=dialect, **kwargs) + csv_reader = csv.reader(utf8_csv_data, dialect=dialect, **kwargs) for row in csv_reader: # decode UTF-8 back to Unicode, cell by cell: yield [force_text(cell, 'utf-8') for cell in row] @@ -32,32 +32,37 @@ def unicode_csv_reader(utf8_csv_data, dialect=csv.excel, **kwargs): def csv_to_list(s): return filter(None, map(six.text_type.strip, s.split(u','))) + # Utilise seulement des majuscules et des chiffres, sauf i,l et 1, O et 0 __pwd_alphabet = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789' + + def create_password(pwd_length=8): - password = ''.join([random.choice(__pwd_alphabet) - for x in range(pwd_length)]) + password = ''.join([random.choice(__pwd_alphabet) for x in range(pwd_length)]) return password + class Command(BaseCommand): args = '[--profile default_profile1,default_profile2] [--group defaut_group1,default_group2] [--password default_password] [--generate-password] file.csv' help = 'Load a CSV file containg user definitions' option_list = BaseCommand.option_list + ( - make_option("--profile", action='append', default=[]), - make_option("--group", action='append', default=[]), - make_option("--password", action='store'), - make_option("--activate", action='store_true'), - make_option("--generate-password", action='store_true', dest='generate_password'), - ) + make_option("--profile", action='append', default=[]), + make_option("--group", action='append', default=[]), + make_option("--password", action='store'), + make_option("--activate", action='store_true'), + make_option("--generate-password", action='store_true', dest='generate_password'), + ) - headers = { 'nom': 'last_name', - 'prenom': 'first_name', - 'email': 'email', - 'profil': None, - 'username': None, - 'groupe': None, - 'password': None } + headers = { + 'nom': 'last_name', + 'prenom': 'first_name', + 'email': 'email', + 'profil': None, + 'username': None, + 'groupe': None, + 'password': None, + } def synthesis(self, data, **options): if not data.get('username'): @@ -126,8 +131,9 @@ class Command(BaseCommand): user_profiles = map(profiles.get, user['profil']) user_instance.mailing_lists = user_profiles if user['groupe']: - user_groups = map(lambda x: auth_models.Group.objects.get_or_create(name=x)[0], - user['groupe']) + user_groups = map( + lambda x: auth_models.Group.objects.get_or_create(name=x)[0], user['groupe'] + ) user_instance.groups = user_groups user_instance.is_staff = reduce(bool.__or__, ['Administrateur' in x for x in user['groupe']]) if user.get('password'): diff --git a/docbow_project/docbow/management/commands/notify.py b/docbow_project/docbow/management/commands/notify.py index 14584eb..b7e2225 100644 --- a/docbow_project/docbow/management/commands/notify.py +++ b/docbow_project/docbow/management/commands/notify.py @@ -3,6 +3,7 @@ from django.db import transaction from ... import notification + class Command(BaseCommand): args = '' help = 'Send notifications' diff --git a/docbow_project/docbow/management/commands/sendfile.py b/docbow_project/docbow/management/commands/sendfile.py index be5f220..aa3094d 100644 --- a/docbow_project/docbow/management/commands/sendfile.py +++ b/docbow_project/docbow/management/commands/sendfile.py @@ -8,8 +8,7 @@ from django.db import transaction from django.core.files import File from django.utils.encoding import force_text -from docbow_project.docbow.models import (MailingList, FileType, Document, - AttachedFile) +from docbow_project.docbow.models import MailingList, FileType, Document, AttachedFile def get_object(model, ref, name='name'): @@ -25,9 +24,7 @@ class Command(BaseCommand): help = '''Send a document''' def add_arguments(self, parser): - parser.add_argument( - 'file_tosend', nargs='+', type=str, - help='File to send') + parser.add_argument('file_tosend', nargs='+', type=str, help='File to send') parser.add_argument("--sender") parser.add_argument("--to-list", action="append") parser.add_argument("--to-user", action="append") @@ -41,8 +38,7 @@ class Command(BaseCommand): if 'sender' not in options: raise CommandError('missing --sender parameter') try: - sender = get_object(User, - force_text(options['sender'], locale_encoding), 'username') + sender = get_object(User, force_text(options['sender'], locale_encoding), 'username') except User.DoesNotExist: raise CommandError('user %r does not exist' % options['sender']) to_lists = [] @@ -62,14 +58,14 @@ class Command(BaseCommand): if 'filetype' not in options: raise CommandError('missing --filetype parameter') try: - filetype = get_object(FileType, - force_text(options['filetype'], locale_encoding)) + filetype = get_object(FileType, force_text(options['filetype'], locale_encoding)) except FileType.DoesNotExist: raise CommandError('filetype %r does not exist' % options['filetype']) if not to_users and not to_lists: print(to_users, to_lists, options) - raise CommandError('you must specify at least one list or user ' - 'recipient using --to-list and --to-user.') + raise CommandError( + 'you must specify at least one list or user ' 'recipient using --to-list and --to-user.' + ) document = Document(sender=sender, filetype=filetype) document.save() document.to_user.set(to_users) @@ -77,6 +73,7 @@ class Command(BaseCommand): for arg in options['file_tosend']: if not os.path.isfile(arg): raise CommandError('file %r does not exist') - AttachedFile.objects.create(name=force_text(arg, locale_encoding), - content=File(open(arg)), document=document) + AttachedFile.objects.create( + name=force_text(arg, locale_encoding), content=File(open(arg)), document=document + ) document.post() diff --git a/docbow_project/docbow/middleware.py b/docbow_project/docbow/middleware.py index 5152e22..482fced 100644 --- a/docbow_project/docbow/middleware.py +++ b/docbow_project/docbow/middleware.py @@ -11,12 +11,14 @@ NO_IP = NO_USER logger = logging.getLogger('docbow') + def get_extra(): '''Extract current remote ip and current user from the thread local storage from the KeepUserAroundMiddleware middleware class. ''' k = KeepUserAroundMiddleware - return { 'ip': k.get_global_ip(), 'user': k.get_global_user() } + return {'ip': k.get_global_ip(), 'user': k.get_global_user()} + class KeepUserAroundMiddleware(MiddlewareMixin): ''' @@ -24,6 +26,7 @@ class KeepUserAroundMiddleware(MiddlewareMixin): variable, so that logging calls inside signals handler can know about them. ''' + __middleware_ctx = threading.local() __middleware_ctx.user = NO_USER __middleware_ctx.ip = NO_USER @@ -39,14 +42,16 @@ class KeepUserAroundMiddleware(MiddlewareMixin): return response def process_exception(self, request, exception): - logger.error('Internal Server Error: %s' % request.path, + logger.error( + 'Internal Server Error: %s' % request.path, exc_info=sys.exc_info, extra={ 'status_code': 500, 'request': request, 'ip': self.get_global_ip(), 'user': self.get_global_user(), - }) + }, + ) self.__middleware_ctx.user = NO_USER self.__middleware_ctx.ip = NO_IP return None diff --git a/docbow_project/docbow/migrations/0001_initial.py b/docbow_project/docbow/migrations/0001_initial.py index d8f9117..e4f8340 100644 --- a/docbow_project/docbow/migrations/0001_initial.py +++ b/docbow_project/docbow/migrations/0001_initial.py @@ -20,18 +20,30 @@ class Migration(migrations.Migration): migrations.CreateModel( name='AttachedFile', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ( + 'id', + models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True), + ), ('name', models.CharField(max_length=300, verbose_name='Name')), - ('content', models.FileField(upload_to=docbow_project.docbow.models.generate_filename, max_length=300, verbose_name='File')), + ( + 'content', + models.FileField( + upload_to=docbow_project.docbow.models.generate_filename, + max_length=300, + verbose_name='File', + ), + ), ], - options={ - }, + options={}, bases=(models.Model,), ), migrations.CreateModel( name='AutomaticForwarding', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ( + 'id', + models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True), + ), ], options={ 'verbose_name': 'Automatic forwarding rule', @@ -42,7 +54,10 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Content', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ( + 'id', + models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True), + ), ('description', models.CharField(unique=True, max_length=128)), ], options={ @@ -55,9 +70,28 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Delegation', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('by', models.ForeignKey(related_name='delegations_to', verbose_name='From', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), - ('to', models.ForeignKey(related_name='delegations_by', verbose_name='To', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), + ( + 'id', + models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True), + ), + ( + 'by', + models.ForeignKey( + related_name='delegations_to', + verbose_name='From', + to=settings.AUTH_USER_MODEL, + on_delete=models.CASCADE, + ), + ), + ( + 'to', + models.ForeignKey( + related_name='delegations_by', + verbose_name='To', + to=settings.AUTH_USER_MODEL, + on_delete=models.CASCADE, + ), + ), ], options={ 'ordering': ['by'], @@ -70,7 +104,10 @@ class Migration(migrations.Migration): migrations.CreateModel( name='DeletedDocument', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ( + 'id', + models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True), + ), ], options={ 'ordering': ('-document',), @@ -82,36 +119,74 @@ class Migration(migrations.Migration): migrations.CreateModel( name='DeletedMailbox', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ( + 'id', + models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True), + ), ('delegate', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), ], - options={ - }, + options={}, bases=(models.Model,), ), migrations.CreateModel( name='DocbowProfile', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ( + 'id', + models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True), + ), ('is_guest', models.BooleanField(default=False, verbose_name='Guest user')), - ('mobile_phone', models.CharField(blank=True, max_length=32, verbose_name='Mobile phone', validators=[django.core.validators.RegexValidator('^\\+\\d+$')])), - ('personal_email', models.EmailField(help_text='if you provide a personal email address, notifications of new documents will also be sent to this address.', max_length=75, verbose_name='personal email address', blank=True)), - ('accept_notifications', models.BooleanField(default=True, help_text='If unchecked you will not received notifications anymore, by email or SMS.', verbose_name='Accept to be notified')), + ( + 'mobile_phone', + models.CharField( + blank=True, + max_length=32, + verbose_name='Mobile phone', + validators=[django.core.validators.RegexValidator('^\\+\\d+$')], + ), + ), + ( + 'personal_email', + models.EmailField( + help_text='if you provide a personal email address, notifications of new documents will also be sent to this address.', + max_length=75, + verbose_name='personal email address', + blank=True, + ), + ), + ( + 'accept_notifications', + models.BooleanField( + default=True, + help_text='If unchecked you will not received notifications anymore, by email or SMS.', + verbose_name='Accept to be notified', + ), + ), ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), ], - options={ - }, + options={}, bases=(models.Model,), ), migrations.CreateModel( name='Document', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ( + 'id', + models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True), + ), ('real_sender', models.CharField(max_length=64, verbose_name='Real sender', blank=True)), - ('date', models.DateTimeField(default=django.utils.timezone.now, verbose_name="Date d'envoi")), + ( + 'date', + models.DateTimeField(default=django.utils.timezone.now, verbose_name="Date d'envoi"), + ), ('comment', models.TextField(verbose_name='Comments', blank=True)), ('_timestamp', models.TextField(blank=True)), - ('private', models.BooleanField(default=False, help_text='delegates cannot see this document', verbose_name='Private')), + ( + 'private', + models.BooleanField( + default=False, help_text='delegates cannot see this document', verbose_name='Private' + ), + ), ], options={ 'base_manager_name': 'objects', @@ -125,40 +200,76 @@ class Migration(migrations.Migration): migrations.CreateModel( name='DocumentForwarded', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ( + 'id', + models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True), + ), ('date', models.DateTimeField(auto_now_add=True)), ('automatic', models.BooleanField(default=False)), - ('from_document', models.ForeignKey(related_name='document_forwarded_to', to='docbow.Document', on_delete=models.CASCADE)), - ('to_document', models.ForeignKey(related_name='document_forwarded_from', to='docbow.Document', on_delete=models.CASCADE)), + ( + 'from_document', + models.ForeignKey( + related_name='document_forwarded_to', to='docbow.Document', on_delete=models.CASCADE + ), + ), + ( + 'to_document', + models.ForeignKey( + related_name='document_forwarded_from', to='docbow.Document', on_delete=models.CASCADE + ), + ), ], - options={ - }, + options={}, bases=(models.Model,), ), migrations.CreateModel( name='FileType', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ( + 'id', + models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True), + ), ('name', models.CharField(unique=True, max_length=128)), ('is_active', models.BooleanField(default=True, verbose_name='is active')), ], - options={ - 'ordering': ['name'], - 'verbose_name': 'File type', - 'verbose_name_plural': 'File types', - }, + options={'ordering': ['name'], 'verbose_name': 'File type', 'verbose_name_plural': 'File types',}, bases=(docbow_project.docbow.models.NameNaturalKey, models.Model), ), migrations.CreateModel( name='FileTypeAttachedFileKind', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ( + 'id', + models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True), + ), ('name', models.CharField(max_length=128, verbose_name='name')), - ('mime_types', models.TextField(help_text='mime types separated by spaces, wildcards are allowed', verbose_name='mime types', blank=True)), - ('cardinality', models.PositiveSmallIntegerField(default=0, help_text='zero is a special value setting no limitation', verbose_name='cardinality')), - ('minimum', models.PositiveSmallIntegerField(default=0, verbose_name='minimum number of files')), + ( + 'mime_types', + models.TextField( + help_text='mime types separated by spaces, wildcards are allowed', + verbose_name='mime types', + blank=True, + ), + ), + ( + 'cardinality', + models.PositiveSmallIntegerField( + default=0, + help_text='zero is a special value setting no limitation', + verbose_name='cardinality', + ), + ), + ( + 'minimum', + models.PositiveSmallIntegerField(default=0, verbose_name='minimum number of files'), + ), ('position', models.PositiveSmallIntegerField(verbose_name='position')), - ('file_type', models.ForeignKey(verbose_name='document type', to='docbow.FileType', on_delete=models.CASCADE)), + ( + 'file_type', + models.ForeignKey( + verbose_name='document type', to='docbow.FileType', on_delete=models.CASCADE + ), + ), ], options={ 'ordering': ('file_type', 'position', 'name'), @@ -170,27 +281,61 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Mailbox', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ( + 'id', + models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True), + ), ('outbox', models.BooleanField(default=False, db_index=True, verbose_name='Outbox message')), ('date', models.DateTimeField(auto_now_add=True)), - ('document', models.ForeignKey(related_name='mailboxes', verbose_name='Document', to='docbow.Document', on_delete=models.CASCADE)), - ('owner', models.ForeignKey(related_name='documents', verbose_name='Mailbox owner', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), + ( + 'document', + models.ForeignKey( + related_name='mailboxes', + verbose_name='Document', + to='docbow.Document', + on_delete=models.CASCADE, + ), + ), + ( + 'owner', + models.ForeignKey( + related_name='documents', + verbose_name='Mailbox owner', + to=settings.AUTH_USER_MODEL, + on_delete=models.CASCADE, + ), + ), ], - options={ - 'ordering': ['-date'], - 'verbose_name': 'Mailbox', - 'verbose_name_plural': 'Mailboxes', - }, + options={'ordering': ['-date'], 'verbose_name': 'Mailbox', 'verbose_name_plural': 'Mailboxes',}, bases=(models.Model,), ), migrations.CreateModel( name='MailingList', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ( + 'id', + models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True), + ), ('name', models.CharField(max_length=400, verbose_name='Name')), ('is_active', models.BooleanField(default=True, verbose_name='is active')), - ('mailing_list_members', models.ManyToManyField(related_name='members_lists', verbose_name='Mailing lists members', to='docbow.MailingList', blank=True)), - ('members', models.ManyToManyField(related_name='mailing_lists', verbose_name='Members', to=settings.AUTH_USER_MODEL, blank=True)), + ( + 'mailing_list_members', + models.ManyToManyField( + related_name='members_lists', + verbose_name='Mailing lists members', + to='docbow.MailingList', + blank=True, + ), + ), + ( + 'members', + models.ManyToManyField( + related_name='mailing_lists', + verbose_name='Members', + to=settings.AUTH_USER_MODEL, + blank=True, + ), + ), ], options={ 'ordering': ['name'], @@ -202,28 +347,50 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Notification', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ( + 'id', + models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True), + ), ('create_dt', models.DateTimeField(auto_now_add=True)), ('kind', models.CharField(default='new-document', max_length=32)), ('done', models.BooleanField(default=False)), ('failure', models.TextField(null=True, blank=True)), ('ctx', picklefield.fields.PickledObjectField(null=True, editable=False, blank=True)), - ('document', models.ForeignKey(blank=True, to='docbow.Document', null=True, on_delete=models.CASCADE)), - ('user', models.ForeignKey(blank=True, to=settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE)), + ( + 'document', + models.ForeignKey(blank=True, to='docbow.Document', null=True, on_delete=models.CASCADE), + ), + ( + 'user', + models.ForeignKey( + blank=True, to=settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE + ), + ), ], - options={ - 'ordering': ('-id',), - }, + options={'ordering': ('-id',),}, bases=(models.Model,), ), migrations.CreateModel( name='NotificationPreference', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ( + 'id', + models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True), + ), ('kind', models.CharField(max_length=8, verbose_name='kind')), ('value', models.BooleanField(default=True, verbose_name='value')), - ('filetype', models.ForeignKey(verbose_name='file type', to='docbow.FileType', on_delete=models.CASCADE)), - ('user', models.ForeignKey(verbose_name='user', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), + ( + 'filetype', + models.ForeignKey( + verbose_name='file type', to='docbow.FileType', on_delete=models.CASCADE + ), + ), + ( + 'user', + models.ForeignKey( + verbose_name='user', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE + ), + ), ], options={ 'ordering': ('user__last_name', 'user__first_name', 'kind'), @@ -235,7 +402,10 @@ class Migration(migrations.Migration): migrations.CreateModel( name='SeenDocument', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ( + 'id', + models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True), + ), ('document', models.ForeignKey(to='docbow.Document', on_delete=models.CASCADE)), ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), ], @@ -249,10 +419,33 @@ class Migration(migrations.Migration): migrations.CreateModel( name='SendingLimitation', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('filetypes', models.ManyToManyField(related_name='filetype_limitation', verbose_name='Limitation des types de fichier', to='docbow.FileType', blank=True)), - ('lists', models.ManyToManyField(related_name='lists_limitation', verbose_name='Limitation des destinataires', to='docbow.MailingList')), - ('mailing_list', models.OneToOneField(verbose_name='Mailing list', to='docbow.MailingList', on_delete=models.CASCADE)), + ( + 'id', + models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True), + ), + ( + 'filetypes', + models.ManyToManyField( + related_name='filetype_limitation', + verbose_name='Limitation des types de fichier', + to='docbow.FileType', + blank=True, + ), + ), + ( + 'lists', + models.ManyToManyField( + related_name='lists_limitation', + verbose_name='Limitation des destinataires', + to='docbow.MailingList', + ), + ), + ( + 'mailing_list', + models.OneToOneField( + verbose_name='Mailing list', to='docbow.MailingList', on_delete=models.CASCADE + ), + ), ], options={ 'verbose_name': 'Limitation par liste de destinataires', @@ -261,36 +454,56 @@ class Migration(migrations.Migration): bases=(models.Model,), ), migrations.AlterUniqueTogether( - name='filetypeattachedfilekind', - unique_together=set([('name', 'file_type')]), + name='filetypeattachedfilekind', unique_together=set([('name', 'file_type')]), ), migrations.AddField( model_name='document', name='filetype', - field=models.ForeignKey(verbose_name='Document type', to='docbow.FileType', on_delete=models.CASCADE), + field=models.ForeignKey( + verbose_name='Document type', to='docbow.FileType', on_delete=models.CASCADE + ), preserve_default=True, ), migrations.AddField( model_name='document', name='reply_to', - field=models.ForeignKey(related_name='replies', verbose_name='Reply to', blank=True, to='docbow.Document', null=True, on_delete=models.CASCADE), + field=models.ForeignKey( + related_name='replies', + verbose_name='Reply to', + blank=True, + to='docbow.Document', + null=True, + on_delete=models.CASCADE, + ), preserve_default=True, ), migrations.AddField( model_name='document', name='sender', - field=models.ForeignKey(related_name='documents_sent', verbose_name='Sender', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE), + field=models.ForeignKey( + related_name='documents_sent', + verbose_name='Sender', + to=settings.AUTH_USER_MODEL, + on_delete=models.CASCADE, + ), preserve_default=True, ), migrations.AddField( model_name='document', name='to_list', - field=models.ManyToManyField(to='docbow.MailingList', verbose_name='Groups to send to', blank=True), + field=models.ManyToManyField( + to='docbow.MailingList', verbose_name='Groups to send to', blank=True + ), ), migrations.AddField( model_name='document', name='to_user', - field=models.ManyToManyField(related_name='directly_received_documents', verbose_name='Users to send to', to=settings.AUTH_USER_MODEL, blank=True), + field=models.ManyToManyField( + related_name='directly_received_documents', + verbose_name='Users to send to', + to=settings.AUTH_USER_MODEL, + blank=True, + ), ), migrations.AddField( model_name='deletedmailbox', @@ -310,83 +523,91 @@ class Migration(migrations.Migration): field=models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE), preserve_default=True, ), - migrations.AlterUniqueTogether( - name='delegation', - unique_together=set([('by', 'to')]), - ), + migrations.AlterUniqueTogether(name='delegation', unique_together=set([('by', 'to')]),), migrations.AddField( model_name='automaticforwarding', name='filetypes', - field=models.ManyToManyField(related_name='forwarding_rules', verbose_name='filetype', to='docbow.FileType'), + field=models.ManyToManyField( + related_name='forwarding_rules', verbose_name='filetype', to='docbow.FileType' + ), preserve_default=True, ), migrations.AddField( model_name='automaticforwarding', name='forward_to_list', - field=models.ManyToManyField(related_name='as_recipient_forwarding_rules', verbose_name='Groups to forward to', to='docbow.MailingList', blank=True) + field=models.ManyToManyField( + related_name='as_recipient_forwarding_rules', + verbose_name='Groups to forward to', + to='docbow.MailingList', + blank=True, + ), ), migrations.AddField( model_name='automaticforwarding', name='forward_to_user', - field=models.ManyToManyField(related_name='as_recipient_forwarding_rules', verbose_name='Users to forward to', to=settings.AUTH_USER_MODEL, blank=True), + field=models.ManyToManyField( + related_name='as_recipient_forwarding_rules', + verbose_name='Users to forward to', + to=settings.AUTH_USER_MODEL, + blank=True, + ), ), migrations.AddField( model_name='automaticforwarding', name='originaly_to_user', - field=models.ManyToManyField(related_name='as_original_recipient_forwarding_rules', to=settings.AUTH_USER_MODEL, blank=True, help_text='At least one recipient must match for the rule to apply.', verbose_name='Original recipients'), + field=models.ManyToManyField( + related_name='as_original_recipient_forwarding_rules', + to=settings.AUTH_USER_MODEL, + blank=True, + help_text='At least one recipient must match for the rule to apply.', + verbose_name='Original recipients', + ), ), migrations.AddField( model_name='attachedfile', name='document', - field=models.ForeignKey(related_name='attached_files', verbose_name='Attached to', to='docbow.Document', on_delete=models.CASCADE), + field=models.ForeignKey( + related_name='attached_files', + verbose_name='Attached to', + to='docbow.Document', + on_delete=models.CASCADE, + ), preserve_default=True, ), migrations.AddField( model_name='attachedfile', name='kind', - field=models.ForeignKey(verbose_name='attached file kind', blank=True, to='docbow.FileTypeAttachedFileKind', null=True, on_delete=models.CASCADE), + field=models.ForeignKey( + verbose_name='attached file kind', + blank=True, + to='docbow.FileTypeAttachedFileKind', + null=True, + on_delete=models.CASCADE, + ), preserve_default=True, ), migrations.CreateModel( name='DocbowGroup', - fields=[ - ], - options={ - 'verbose_name': 'Docbow admin group', - 'proxy': True, - }, + fields=[], + options={'verbose_name': 'Docbow admin group', 'proxy': True,}, bases=('auth.group',), ), migrations.CreateModel( name='DocbowUser', - fields=[ - ], - options={ - 'verbose_name': 'Docbow admin user', - 'proxy': True, - }, + fields=[], + options={'verbose_name': 'Docbow admin user', 'proxy': True,}, bases=('auth.user',), ), migrations.CreateModel( name='Inbox', - fields=[ - ], - options={ - 'verbose_name': 'Inbox', - 'proxy': True, - 'verbose_name_plural': 'Inboxes', - }, + fields=[], + options={'verbose_name': 'Inbox', 'proxy': True, 'verbose_name_plural': 'Inboxes',}, bases=('docbow.mailbox',), ), migrations.CreateModel( name='Outbox', - fields=[ - ], - options={ - 'verbose_name': 'Outbox', - 'proxy': True, - 'verbose_name_plural': 'Outboxes', - }, + fields=[], + options={'verbose_name': 'Outbox', 'proxy': True, 'verbose_name_plural': 'Outboxes',}, bases=('docbow.mailbox',), ), ] diff --git a/docbow_project/docbow/migrations/0002_auto_20190711_1812.py b/docbow_project/docbow/migrations/0002_auto_20190711_1812.py index 8fa84bd..320ceb1 100644 --- a/docbow_project/docbow/migrations/0002_auto_20190711_1812.py +++ b/docbow_project/docbow/migrations/0002_auto_20190711_1812.py @@ -12,7 +12,5 @@ class Migration(migrations.Migration): ] operations = [ - migrations.DeleteModel( - name='DeletedMailbox', - ), + migrations.DeleteModel(name='DeletedMailbox',), ] diff --git a/docbow_project/docbow/migrations/0003_auto_20200319_1129.py b/docbow_project/docbow/migrations/0003_auto_20200319_1129.py index aa7231b..f92867e 100644 --- a/docbow_project/docbow/migrations/0003_auto_20200319_1129.py +++ b/docbow_project/docbow/migrations/0003_auto_20200319_1129.py @@ -14,6 +14,11 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='docbowprofile', name='personal_email', - field=models.EmailField(help_text='if you provide a personal email address, notifications of new documents will also be sent to this address.', max_length=254, verbose_name='personal email address', blank=True), + field=models.EmailField( + help_text='if you provide a personal email address, notifications of new documents will also be sent to this address.', + max_length=254, + verbose_name='personal email address', + blank=True, + ), ), ] diff --git a/docbow_project/docbow/migrations/0005_soft_delete.py b/docbow_project/docbow/migrations/0005_soft_delete.py index 8a50387..c4f4f07 100644 --- a/docbow_project/docbow/migrations/0005_soft_delete.py +++ b/docbow_project/docbow/migrations/0005_soft_delete.py @@ -13,13 +13,9 @@ class Migration(migrations.Migration): operations = [ migrations.AddField( - model_name='deleteddocument', - name='soft_delete', - field=models.BooleanField(default=False), + model_name='deleteddocument', name='soft_delete', field=models.BooleanField(default=False), ), migrations.AddField( - model_name='deleteddocument', - name='soft_delete_date', - field=models.DateTimeField(null=True), + model_name='deleteddocument', name='soft_delete_date', field=models.DateTimeField(null=True), ), ] diff --git a/docbow_project/docbow/models.py b/docbow_project/docbow/models.py index ccee52a..09ced73 100644 --- a/docbow_project/docbow/models.py +++ b/docbow_project/docbow/models.py @@ -7,9 +7,22 @@ import re from collections import defaultdict import time -from django.db.models import (Model, ForeignKey, DateTimeField, CharField, - FileField, ManyToManyField, TextField, Manager, BooleanField, - OneToOneField, Q, EmailField, PositiveSmallIntegerField, CASCADE) +from django.db.models import ( + Model, + ForeignKey, + DateTimeField, + CharField, + FileField, + ManyToManyField, + TextField, + Manager, + BooleanField, + OneToOneField, + Q, + EmailField, + PositiveSmallIntegerField, + CASCADE, +) from django.contrib.auth.models import User, Group from django.conf import settings from django.template.defaultfilters import slugify @@ -33,19 +46,20 @@ DOCBOW_APP = _('Docbow_App') DOCBOW_APP2 = _('Docbow_app') # fixup User ordering -User._meta.ordering = [ 'username' ] +User._meta.ordering = ['username'] def ellipsize(text, length=50): text = html.unescape(strip_tags(text)) if len(text) < length: return text - return text[:(length-10)] + '...' + return text[: (length - 10)] + '...' class GetByNameManager(Manager): '''Manager providing a get_by_natural_key() method to retrieve object by their name. The name field MUST be unique.''' + def get_by_natural_key(self, name): return self.get(name=name) @@ -53,6 +67,7 @@ class GetByNameManager(Manager): class ContentManager(Manager): '''Manager providing a get_by_natural_key() method to retrieve object by their description. The description field MUST be unique.''' + def get_by_natural_key(self, description): return self.get(description=description) @@ -60,6 +75,7 @@ class ContentManager(Manager): class NameNaturalKey(object): '''Model mixin to export the name of a model as a natural key. The name field MUST be unique.''' + def natural_key(self): return (self.name,) @@ -69,6 +85,7 @@ class FileType(NameNaturalKey, Model): ''' A type of file that can be sent inside the application. ''' + objects = GetByNameManager() name = CharField(max_length=128, unique=True) @@ -90,22 +107,25 @@ class FileTypeAttachedFileKindManager(Manager): @python_2_unicode_compatible class FileTypeAttachedFileKind(Model): - MIME_TYPES_RE = re.compile(r'^\s*(?:(?:text|image|audio|application|video)' - r'/(?:\*|[a-z-]+)(?:\s+(?:text|image|audio|application|video)' - r'/(?:\*|[a-z-.]+))*\s*)?$') + MIME_TYPES_RE = re.compile( + r'^\s*(?:(?:text|image|audio|application|video)' + r'/(?:\*|[a-z-]+)(?:\s+(?:text|image|audio|application|video)' + r'/(?:\*|[a-z-.]+))*\s*)?$' + ) objects = FileTypeAttachedFileKindManager() name = CharField(max_length=128, verbose_name=_('name')) file_type = ForeignKey('FileType', verbose_name=_('document type'), on_delete=CASCADE) - mime_types = TextField(verbose_name=_('mime types'), - help_text=('mime types separated by spaces, wildcards are allowed'), - blank=True) - cardinality = PositiveSmallIntegerField(default=0, - verbose_name=_('cardinality'), - help_text=_('zero is a special value setting no limitation')) - minimum = PositiveSmallIntegerField(default=0, - verbose_name=_('minimum number of files')) + mime_types = TextField( + verbose_name=_('mime types'), + help_text=('mime types separated by spaces, wildcards are allowed'), + blank=True, + ) + cardinality = PositiveSmallIntegerField( + default=0, verbose_name=_('cardinality'), help_text=_('zero is a special value setting no limitation') + ) + minimum = PositiveSmallIntegerField(default=0, verbose_name=_('minimum number of files')) position = PositiveSmallIntegerField(verbose_name=_('position')) class Meta: @@ -116,7 +136,9 @@ class FileTypeAttachedFileKind(Model): def clean(self): if self.cardinality != 0 and not self.minimum <= self.cardinality: - raise ValidationError(_('minimum must be inferior to maximum number of files if maximum is not zero')) + raise ValidationError( + _('minimum must be inferior to maximum number of files if maximum is not zero') + ) if self.mime_types: if not self.MIME_TYPES_RE.match(self.mime_types): raise ValidationError(_('invalid mime types list')) @@ -125,8 +147,7 @@ class FileTypeAttachedFileKind(Model): return list(filter(None, re.split(r'\s+', self.mime_types.strip()))) def match_file(self, file_like): - return file_match_mime_types( - file_like, self.get_mime_types(), app_settings.MIME_BUFFER_SIZE) + return file_match_mime_types(file_like, self.get_mime_types(), app_settings.MIME_BUFFER_SIZE) def natural_key(self): return (self.name, self.file_type.name) @@ -138,6 +159,7 @@ class FileTypeAttachedFileKind(Model): @python_2_unicode_compatible class Content(Model): '''Predefined content type''' + objects = ContentManager() description = CharField(max_length=128, unique=True) @@ -186,12 +208,11 @@ FORWARD_PERMISSION = 'FORWARD_DOCUMENT' class DocumentManager(Manager): - def get_query_set(self): '''Prefetch as much as possible.''' - return super(DocumentManager, self).get_query_set() \ - .select_related() \ - .prefetch_related('attached_files') + return ( + super(DocumentManager, self).get_query_set().select_related().prefetch_related('attached_files') + ) @python_2_unicode_compatible @@ -206,45 +227,44 @@ class Document(Model): ordering = ['-date'] verbose_name = _('Document') verbose_name_plural = _('Documents') - permissions = ( - (FORWARD_PERMISSION, _("Can forward documents")), - ) + permissions = ((FORWARD_PERMISSION, _("Can forward documents")),) base_manager_name = 'objects' - - sender = ForeignKey(User, verbose_name=_('Sender'), on_delete=CASCADE, - related_name='documents_sent') + sender = ForeignKey(User, verbose_name=_('Sender'), on_delete=CASCADE, related_name='documents_sent') real_sender = CharField(max_length=64, blank=True, verbose_name=_('Real sender')) - date = DateTimeField(default=now, - verbose_name=_("Date d'envoi")) - to_user = ManyToManyField(User, related_name='directly_received_documents', - blank=True, verbose_name=_('Users to send to')) - to_list = ManyToManyField('MailingList', blank=True, - verbose_name=_('Groups to send to')) - filetype = ForeignKey(FileType, verbose_name=_('Document type'), on_delete=CASCADE, - limit_choices_to={'is_active': True}) + date = DateTimeField(default=now, verbose_name=_("Date d'envoi")) + to_user = ManyToManyField( + User, related_name='directly_received_documents', blank=True, verbose_name=_('Users to send to') + ) + to_list = ManyToManyField('MailingList', blank=True, verbose_name=_('Groups to send to')) + filetype = ForeignKey( + FileType, verbose_name=_('Document type'), on_delete=CASCADE, limit_choices_to={'is_active': True} + ) comment = TextField(blank=True, verbose_name=_('Comments')) _timestamp = TextField(blank=True) - reply_to = ForeignKey('self', verbose_name=_('Reply to'), on_delete=CASCADE, - blank=True, null=True, related_name='replies') - private = BooleanField(default=False, verbose_name=_('Private'), - help_text=_('delegates cannot see this document')) + reply_to = ForeignKey( + 'self', verbose_name=_('Reply to'), on_delete=CASCADE, blank=True, null=True, related_name='replies' + ) + private = BooleanField( + default=False, verbose_name=_('Private'), help_text=_('delegates cannot see this document') + ) def __str__(self): '''Return a displayable representation of the document sending.''' ctx = { - 'id': self.id, - 'date': self.date.date().isoformat(), - 'filetype': self.filetype, - 'comment': self.comment, - 'sender': username(self.sender), - } + 'id': self.id, + 'date': self.date.date().isoformat(), + 'filetype': self.filetype, + 'comment': self.comment, + 'sender': username(self.sender), + } return _('document {id} sent on {date} of type {filetype} about {comment} by {sender}').format(**ctx) def filenames(self): '''Returns a display string for the list of attached files''' - files = [ attached_file for attached_file in self.attached_files.all() ] + files = [attached_file for attached_file in self.attached_files.all()] return ', '.join([f.filename() for f in files]) + filenames.short_description = _('Attached files') def filename_links(self): @@ -263,6 +283,7 @@ class Document(Model): links[-1] = kind.name + ' : ' + links[-1] last_kind_name = kind.name return mark_safe(', '.join(links)) + filename_links.short_description = _('Attached files') def user_human_to(self): @@ -275,14 +296,14 @@ class Document(Model): def human_to(self): '''Return a sorted list of display names for all recipients.''' - return sorted(list(map(username, self.to_user.all())) + \ - list(map(force_text, self.to_list.all()))) + return sorted(list(map(username, self.to_user.all())) + list(map(force_text, self.to_list.all()))) def recipients(self): '''Return a comma separated sorted list of display names for all recipients. ''' return ', '.join(self.human_to()) + recipients.short_description = _('Recipients') def to(self): @@ -315,8 +336,12 @@ class Document(Model): self._timestamp = time.time() self.date = dt.datetime.fromtimestamp(self._timestamp, utc) self.save() - django_journal.record('timestamp', 'timestamped document {document} result is {timestamp}', - document=self, timestamp=self._timestamp) + django_journal.record( + 'timestamp', + 'timestamped document {document} result is {timestamp}', + document=self, + timestamp=self._timestamp, + ) return self._timestamp def post(self, forward=True): @@ -329,30 +354,49 @@ class Document(Model): self.timestamp(to=to) # Record recipient lists for mailing_list in self.to_list.all(): - django_journal.record('delivery', 'deliver document {document} to members of list {mailing_list}', - document=self, mailing_list=mailing_list) + django_journal.record( + 'delivery', + 'deliver document {document} to members of list {mailing_list}', + document=self, + mailing_list=mailing_list, + ) for user in to: Mailbox.objects.get_or_create(owner=user, document=self) - if '--direct--' not in to_with_origins[user] or \ - len(to_with_origins[user]) > 1: + if '--direct--' not in to_with_origins[user] or len(to_with_origins[user]) > 1: lists = u', '.join(m.name for m in to_with_origins[user] if m != '--direct--') if '--direct--' in to_with_origins[user]: - django_journal.record('delivery', 'deliver document ' - '{document} in mailbox of user {recipient} ' - 'member of {lists} and as a direct recipient', - document=self, recipient=user, lists=lists) + django_journal.record( + 'delivery', + 'deliver document ' + '{document} in mailbox of user {recipient} ' + 'member of {lists} and as a direct recipient', + document=self, + recipient=user, + lists=lists, + ) else: - django_journal.record('delivery', 'deliver document ' - '{document} in mailbox of user {recipient} ' - 'member of {lists}', - document=self, recipient=user, lists=lists) + django_journal.record( + 'delivery', + 'deliver document ' '{document} in mailbox of user {recipient} ' 'member of {lists}', + document=self, + recipient=user, + lists=lists, + ) else: - django_journal.record('delivery', 'deliver document {document} in mailbox of user {recipient} as a direct recipient', - document=self, recipient=user) + django_journal.record( + 'delivery', + 'deliver document {document} in mailbox of user {recipient} as a direct recipient', + document=self, + recipient=user, + ) # Deliver to ouput mailbox of the sender Mailbox.objects.get_or_create(owner=self.sender, outbox=True, document=self) - django_journal.record('delivery', 'deliver document {document} in output mailbox of user {recipient}', - document=self, recipient=self.sender) + django_journal.record( + 'delivery', + 'deliver document {document} in output mailbox of user {recipient}', + document=self, + recipient=self.sender, + ) # Push notifications Notification.objects.notify(document=self, users=to) if forward: @@ -368,25 +412,27 @@ class Document(Model): automatic - whether this forwarding is the result of an automatic treatment ''' - document = Document.objects.create(sender=new_sender, - filetype=self.filetype, - comment=self.comment) + document = Document.objects.create(sender=new_sender, filetype=self.filetype, comment=self.comment) document.to_user.set(users) document.to_list.set(lists) - attached_files = [ AttachedFile(name=attached_file.name, - content=attached_file.content, - document=document) for attached_file in self.attached_files.all()] + attached_files = [ + AttachedFile(name=attached_file.name, content=attached_file.content, document=document) + for attached_file in self.attached_files.all() + ] AttachedFile.objects.bulk_create(attached_files) recipients_count = document.post(forward=False) - return recipients_count, DocumentForwarded.objects.create(from_document=self, - to_document=document, automatic=automatic) + return ( + recipients_count, + DocumentForwarded.objects.create(from_document=self, to_document=document, automatic=automatic), + ) def sender_display(self): return username(self.sender) def url(self): - return urlparse.urljoin(app_settings.BASE_URL, - reverse('inbox-message', kwargs=dict(mailbox_id=self.id))) + return urlparse.urljoin( + app_settings.BASE_URL, reverse('inbox-message', kwargs=dict(mailbox_id=self.id)) + ) def has_zip_download(self): return self.attached_files.count() > 1 and settings.ZIP_DOWNLOAD @@ -394,6 +440,7 @@ class Document(Model): class SeenDocument(Model): '''Mark a document as seen''' + document = ForeignKey('Document', on_delete=CASCADE) user = ForeignKey('auth.User', on_delete=CASCADE) @@ -402,8 +449,10 @@ class SeenDocument(Model): verbose_name_plural = _('seen documents') ordering = ('-document',) + class DeletedDocument(Model): '''Mark a document as deleted''' + document = ForeignKey('Document', on_delete=CASCADE) user = ForeignKey('auth.User', on_delete=CASCADE) soft_delete = BooleanField(default=False) @@ -421,6 +470,7 @@ class DocumentForwarded(Model): First use will be to mark message as having been forwarded. ''' + from_document = ForeignKey(Document, related_name='document_forwarded_to', on_delete=CASCADE) to_document = ForeignKey(Document, related_name='document_forwarded_from', on_delete=CASCADE) date = DateTimeField(auto_now_add=True) @@ -429,14 +479,12 @@ class DocumentForwarded(Model): def __str__(self): if self.automatic: return _(u'forwarded document {from_document} as {to_document} on {date} automatically').format( - from_document=self.from_document, - to_document=self.to_document, - date=self.date) + from_document=self.from_document, to_document=self.to_document, date=self.date + ) else: return _(u'forwarded document {from_document} as {to_document} on {date}').format( - from_document=self.from_document, - to_document=self.to_document, - date=self.date) + from_document=self.from_document, to_document=self.to_document, date=self.date + ) def list_to_csv(l, mapping_func=None): @@ -456,17 +504,29 @@ class AutomaticForwarding(Model): Choice of sender and filetype to transfer mail automatically to a list of recipients. ''' - filetypes = ManyToManyField(FileType, related_name='forwarding_rules', - verbose_name=_('filetype'), - limit_choices_to={'is_active': True}) - originaly_to_user = ManyToManyField(User, related_name='as_original_recipient_forwarding_rules', - blank=True, verbose_name=_('Original recipients'), - help_text=_('At least one recipient must match for the rule to ' - 'apply.')) - forward_to_user = ManyToManyField(User, related_name='as_recipient_forwarding_rules', - blank=True, verbose_name=_('Users to forward to')) - forward_to_list = ManyToManyField('MailingList', blank=True, - verbose_name=_('Groups to forward to'), related_name='as_recipient_forwarding_rules') + + filetypes = ManyToManyField( + FileType, + related_name='forwarding_rules', + verbose_name=_('filetype'), + limit_choices_to={'is_active': True}, + ) + originaly_to_user = ManyToManyField( + User, + related_name='as_original_recipient_forwarding_rules', + blank=True, + verbose_name=_('Original recipients'), + help_text=_('At least one recipient must match for the rule to ' 'apply.'), + ) + forward_to_user = ManyToManyField( + User, related_name='as_recipient_forwarding_rules', blank=True, verbose_name=_('Users to forward to') + ) + forward_to_list = ManyToManyField( + 'MailingList', + blank=True, + verbose_name=_('Groups to forward to'), + related_name='as_recipient_forwarding_rules', + ) class Meta: verbose_name = _('Automatic forwarding rule') @@ -475,10 +535,10 @@ class AutomaticForwarding(Model): def __str__(self): '''Return a display string for the forwarding rule.''' ctx = { - 'filetypes': list_to_csv(self.filetypes.all()), - 'originaly_to_user': list_to_csv(map(username, self.originaly_to_user.all())), - 'to': list_to_csv(map(username, self.forward_to_user.all()) - + list(self.forward_to_list.all())) } + 'filetypes': list_to_csv(self.filetypes.all()), + 'originaly_to_user': list_to_csv(map(username, self.originaly_to_user.all())), + 'to': list_to_csv(map(username, self.forward_to_user.all()) + list(self.forward_to_list.all())), + } assert self.filetypes.all() or self.originaly_to_user.all() if self.filetypes.all() and self.originaly_to_user.all(): tpl = _('Forward documents of type {filetypes} and to {originaly_to_user} automatically to {to}') @@ -491,30 +551,44 @@ class AutomaticForwarding(Model): @classmethod def try_forwarding(self, document): '''Try applying matching rules to the document''' - for rule in self.objects.filter(Q(filetypes=document.filetype, - originaly_to_user__in=document.to())| - Q(filetypes=document.filetype, originaly_to_user__isnull=True)): - recipients_count, document_forwarded = document.forward(document.sender, - users=rule.forward_to_user.all(), - lists=rule.forward_to_list.all(), - automatic=True) - django_journal.record('automatic-forward', 'document {document} ' - 'automatically forwarded to {recipients_count} new recipients ' - 'as document {new_document}', document=document_forwarded.from_document, - new_document=document_forwarded.to_document, - recipients_count=recipients_count, - document_forwarded=document_forwarded) + for rule in self.objects.filter( + Q(filetypes=document.filetype, originaly_to_user__in=document.to()) + | Q(filetypes=document.filetype, originaly_to_user__isnull=True) + ): + recipients_count, document_forwarded = document.forward( + document.sender, + users=rule.forward_to_user.all(), + lists=rule.forward_to_list.all(), + automatic=True, + ) + django_journal.record( + 'automatic-forward', + 'document {document} ' + 'automatically forwarded to {recipients_count} new recipients ' + 'as document {new_document}', + document=document_forwarded.from_document, + new_document=document_forwarded.to_document, + recipients_count=recipients_count, + document_forwarded=document_forwarded, + ) @python_2_unicode_compatible class AttachedFile(Model): '''Uploaded file attached to a Document''' + name = CharField(max_length=300, verbose_name=_('Name')) content = FileField(max_length=300, upload_to=generate_filename, verbose_name=_('File')) - document = ForeignKey(Document, verbose_name=_('Attached to'), on_delete=CASCADE, - related_name='attached_files') - kind = ForeignKey('FileTypeAttachedFileKind', blank=True, null=True, on_delete=CASCADE, - verbose_name=_('attached file kind')) + document = ForeignKey( + Document, verbose_name=_('Attached to'), on_delete=CASCADE, related_name='attached_files' + ) + kind = ForeignKey( + 'FileTypeAttachedFileKind', + blank=True, + null=True, + on_delete=CASCADE, + verbose_name=_('attached file kind'), + ) def filename(self): '''Extract the original filename from generated unique filename for @@ -544,6 +618,7 @@ class AttachedFile(Model): def __str__(self): return self.name + def is_guest(user): try: return user.docbowprofile.is_guest @@ -557,19 +632,26 @@ class Delegation(Model): Delegate account, managable by user themselves. ''' - by = ForeignKey(User, related_name='delegations_to', on_delete=CASCADE, - verbose_name=pgettext_lazy('delegation from', "From")) - to = ForeignKey(User, related_name='delegations_by', on_delete=CASCADE, - verbose_name=pgettext_lazy('delegation to', "To")) + by = ForeignKey( + User, + related_name='delegations_to', + on_delete=CASCADE, + verbose_name=pgettext_lazy('delegation from', "From"), + ) + to = ForeignKey( + User, + related_name='delegations_by', + on_delete=CASCADE, + verbose_name=pgettext_lazy('delegation to', "To"), + ) class Meta: - ordering = [ 'by' ] + ordering = ['by'] verbose_name = _('Account delegation') verbose_name_plural = _('Account delegations') db_table = 'auth_delegation' unique_together = (('by', 'to'),) - def __str__(self): return u'delegation from {0}:{0.id} to {1}:{1.id}'.format(self.by, self.to) @@ -590,7 +672,7 @@ class MailingListManager(GetByNameManager): def are_member_of(self, users): lists = set(MailingList.objects.filter(is_active=True).filter(members__in=users)) - while True: # accumulate lists until it grows no more + while True: # accumulate lists until it grows no more old_count = len(lists) lists |= set(MailingList.objects.filter(is_active=True).filter(mailing_list_members__in=lists)) if old_count == len(lists): @@ -603,18 +685,16 @@ class MailingList(NameNaturalKey, Model): '''A list of recipients.''' name = CharField(max_length=400, verbose_name=_('Name')) - members = ManyToManyField(User, verbose_name=_('Members'), blank=True, - related_name='mailing_lists') - mailing_list_members = ManyToManyField('MailingList', - verbose_name=_('Mailing lists members'), - blank=True, related_name='members_lists') - is_active = BooleanField(verbose_name=_('is active'), blank=True, - default=True) + members = ManyToManyField(User, verbose_name=_('Members'), blank=True, related_name='mailing_lists') + mailing_list_members = ManyToManyField( + 'MailingList', verbose_name=_('Mailing lists members'), blank=True, related_name='members_lists' + ) + is_active = BooleanField(verbose_name=_('is active'), blank=True, default=True) objects = MailingListManager() class Meta: - ordering = [ 'name' ] + ordering = ['name'] verbose_name = _('Mailing list') verbose_name_plural = _('Mailing lists') @@ -635,15 +715,14 @@ class MailingList(NameNaturalKey, Model): '''Traverse this list and all its recursive sublist and accumulate members and their origin.''' if sublist_traversed is None: - sublist_traversed = defaultdict(lambda:0) + sublist_traversed = defaultdict(lambda: 0) sublist_traversed[self] += 1 - members = defaultdict(lambda:set()) + members = defaultdict(lambda: set()) for member in self.members.filter(is_active=True): members[member].add(self) for sublist in self.mailing_list_members.all(): if sublist_traversed[sublist] < 3: - sub_members = sublist.recursive_members_with_origin( - sublist_traversed).items() + sub_members = sublist.recursive_members_with_origin(sublist_traversed).items() for member, list_set in sub_members: members[member] |= list_set members[member].add(self) @@ -656,24 +735,22 @@ class MailingList(NameNaturalKey, Model): @python_2_unicode_compatible class Mailbox(Model): '''List of document received by a user''' - owner = ForeignKey(User, verbose_name=_('Mailbox owner'), on_delete=CASCADE, - related_name='documents') - document = ForeignKey(Document, verbose_name=('Document'), on_delete=CASCADE, - related_name='mailboxes') - outbox = BooleanField(verbose_name=_('Outbox message'), blank=True, - default=False, db_index=True) + + owner = ForeignKey(User, verbose_name=_('Mailbox owner'), on_delete=CASCADE, related_name='documents') + document = ForeignKey(Document, verbose_name=('Document'), on_delete=CASCADE, related_name='mailboxes') + outbox = BooleanField(verbose_name=_('Outbox message'), blank=True, default=False, db_index=True) date = DateTimeField(auto_now_add=True) class Meta: - ordering = [ '-date' ] + ordering = ['-date'] verbose_name = _('Mailbox') verbose_name_plural = _('Mailboxes') def __str__(self): - return _(u'mailbox entry {id} of user {user}:{user.id} created on ' - u'{date} for {document}').format(id=self.id, user=self.owner, - date=self.date, - document=self.document) + return _(u'mailbox entry {id} of user {user}:{user.id} created on ' u'{date} for {document}').format( + id=self.id, user=self.owner, date=self.date, document=self.document + ) + class DocbowUser(User): class Meta: @@ -705,14 +782,19 @@ class Outbox(Mailbox): @python_2_unicode_compatible class SendingLimitation(Model): - mailing_list = OneToOneField(MailingList, unique=True, on_delete=CASCADE, - verbose_name=MailingList._meta.verbose_name) - filetypes = ManyToManyField(FileType, blank=True, - related_name='filetype_limitation', - verbose_name=_('Limitation des types de fichier'), - limit_choices_to={'is_active': True}) - lists = ManyToManyField(MailingList, related_name='lists_limitation', - verbose_name=_('Limitation des destinataires')) + mailing_list = OneToOneField( + MailingList, unique=True, on_delete=CASCADE, verbose_name=MailingList._meta.verbose_name + ) + filetypes = ManyToManyField( + FileType, + blank=True, + related_name='filetype_limitation', + verbose_name=_('Limitation des types de fichier'), + limit_choices_to={'is_active': True}, + ) + lists = ManyToManyField( + MailingList, related_name='lists_limitation', verbose_name=_('Limitation des destinataires') + ) class Meta: verbose_name = _('Limitation par liste de destinataires') @@ -720,48 +802,57 @@ class SendingLimitation(Model): verbose_name_plural = verbose_name def __str__(self): - return _(u'sending limitation for list {mailing_list} to filetypes' - u'{filetypes} and lists {lists}').format( - mailing_list=self.mailing_list, - filetypes=list_to_csv(self.filetypes.all()), - lists=list_to_csv(self.lists.all())) + return _( + u'sending limitation for list {mailing_list} to filetypes' u'{filetypes} and lists {lists}' + ).format( + mailing_list=self.mailing_list, + filetypes=list_to_csv(self.filetypes.all()), + lists=list_to_csv(self.lists.all()), + ) @python_2_unicode_compatible class DocbowProfile(Model): '''Hold extra user attributes''' + user = OneToOneField(User, unique=True, on_delete=CASCADE) - is_guest = BooleanField(verbose_name=_('Guest user'), blank=True, - default=False) - mobile_phone = CharField(max_length=32, verbose_name=_('Mobile phone'), - blank=True, validators=[validate_phone]) - personal_email = EmailField(_('personal email address'), blank=True, - help_text=_('if you provide a personal email address, notifications ' - 'of new documents will also be sent to this address.')) + is_guest = BooleanField(verbose_name=_('Guest user'), blank=True, default=False) + mobile_phone = CharField( + max_length=32, verbose_name=_('Mobile phone'), blank=True, validators=[validate_phone] + ) + personal_email = EmailField( + _('personal email address'), + blank=True, + help_text=_( + 'if you provide a personal email address, notifications ' + 'of new documents will also be sent to this address.' + ), + ) accept_notifications = BooleanField( - verbose_name=_('Accept to be notified'), blank=True, default=True, - help_text=_("If unchecked you will not received notifications " - "anymore, by email or SMS.")) + verbose_name=_('Accept to be notified'), + blank=True, + default=True, + help_text=_("If unchecked you will not received notifications " "anymore, by email or SMS."), + ) external_id = CharField(max_length=256, verbose_name=_('External identifer'), blank=True) def __str__(self): - return _(u'docbow profile of {user}:{user.id} with mobile phone ' - u'{mobile_phone} and personal email {personal_email}').format( - user=self.user, - mobile_phone=self.mobile_phone, - personal_email=self.personal_email) + return _( + u'docbow profile of {user}:{user.id} with mobile phone ' + u'{mobile_phone} and personal email {personal_email}' + ).format(user=self.user, mobile_phone=self.mobile_phone, personal_email=self.personal_email) class NotificationManager(Manager): def notify(self, document=None, users=None, kind='new-document', ctx=None): '''Build notifications in bulk''' - users = set(users) + users = set(users) if document and not document.private: users |= set(User.objects.filter(delegations_by__by__in=users, is_active=True)) - users = User.objects.filter(pk__in=[u.pk for u in users]) \ - .exclude(docbowprofile__accept_notifications=False) - notifications = [Notification(user=user, document=document, kind=kind, ctx=ctx) - for user in users] + users = User.objects.filter(pk__in=[u.pk for u in users]).exclude( + docbowprofile__accept_notifications=False + ) + notifications = [Notification(user=user, document=document, kind=kind, ctx=ctx) for user in users] self.bulk_create(notifications) @@ -778,6 +869,7 @@ class Notification(Model): was successful if this is empty. ctx - a pickled object ''' + objects = NotificationManager() create_dt = DateTimeField(auto_now_add=True) @@ -797,6 +889,7 @@ class Notification(Model): class NotificationPreference(Model): '''Store preferences of users toward notification methods''' + user = ForeignKey(User, verbose_name=_('user'), on_delete=CASCADE) filetype = ForeignKey(FileType, verbose_name=_('file type'), on_delete=CASCADE) kind = CharField(max_length=8, verbose_name=_('kind')) @@ -807,15 +900,18 @@ class NotificationPreference(Model): verbose_name = _('notification preference') verbose_name_plural = _('notification preferences') + def guest_users(): return User.objects.filter(docbowprofile__is_guest=True).filter(is_active=True) + def non_guest_users(): return User.objects.exclude(docbowprofile__is_guest=True).filter(is_active=True) import watson.search as watson + class DocumentAdapter(watson.SearchAdapter): def gather_strings(self, obj): yield obj.comment diff --git a/docbow_project/docbow/notification.py b/docbow_project/docbow/notification.py index 0fdde4b..cd28f27 100644 --- a/docbow_project/docbow/notification.py +++ b/docbow_project/docbow/notification.py @@ -13,6 +13,7 @@ from docbow_project.docbow import app_settings logger = logging.getLogger(__name__) + class BaseNotifier(object): def __init__(self): # accumulate preferences of users first @@ -46,6 +47,7 @@ class BaseNotifier(object): def finish(self): pass + class MailNotifier(BaseNotifier): description = _('Email') key = 'email' @@ -79,17 +81,18 @@ class MailNotifier(BaseNotifier): subject = subject.replace('\n', '').replace('\r', '') body = self.generate_part(self.body_template, notification) html_body = self.generate_part(self.html_body_template, notification) - mail = EmailMultiAlternatives(to=list(to), subject=subject, body=body, - headers=headers) + mail = EmailMultiAlternatives(to=list(to), subject=subject, body=body, headers=headers) if html_body: mail.attach_alternative(html_body, 'text/html') mail.send(fail_silently=False) - django_journal.record('mail-notify', 'mail notification {notification} ' - 'sent for document {document} to {to} of user {recipient}', - notification=notification, - recipient=notification.user, - document=notification.document, - to=','.join(to)) + django_journal.record( + 'mail-notify', + 'mail notification {notification} ' 'sent for document {document} to {to} of user {recipient}', + notification=notification, + recipient=notification.user, + document=notification.document, + to=','.join(to), + ) class SMSNotifier(BaseNotifier): @@ -97,6 +100,7 @@ class SMSNotifier(BaseNotifier): - document - settings ''' + description = _('SMS') key = 'sms' body_template = 'docbow/sms-notification_{kind}_body.txt' @@ -114,8 +118,9 @@ class SMSNotifier(BaseNotifier): return if not profile.mobile_phone: return - self.mobile_phones.setdefault((notification.document, notification.kind), []) \ - .append((notification.user, profile.mobile_phone)) + self.mobile_phones.setdefault((notification.document, notification.kind), []).append( + (notification.user, profile.mobile_phone) + ) @classmethod def get_carrier(cls): @@ -132,11 +137,14 @@ class SMSNotifier(BaseNotifier): body = self.generate_part(self.body_template, notification) sms_carrier = self.get_carrier() for user, phone_number in value: - django_journal.record('sms-notify', - 'sms notification for document {document} to user ' - '{recipient} with phone number {phone_number}', - document=document, recipient=user, - phone_number=phone_number) + django_journal.record( + 'sms-notify', + 'sms notification for document {document} to user ' + '{recipient} with phone number {phone_number}', + document=document, + recipient=user, + phone_number=phone_number, + ) sms_carrier.send_sms([v[1] for v in value], body) except Exception as e: exc = e @@ -144,7 +152,6 @@ class SMSNotifier(BaseNotifier): raise exc - def resolve_class(class_path): module, cls_name = class_path.rsplit('.', 1) module = importlib.import_module(module) @@ -152,30 +159,33 @@ def resolve_class(class_path): def get_notifiers(): - notification_classes = getattr(settings, 'DOCBOW_NOTIFIERS', [ - 'docbow_project.docbow.notification.MailNotifier' ]) + notification_classes = getattr( + settings, 'DOCBOW_NOTIFIERS', ['docbow_project.docbow.notification.MailNotifier'] + ) return [resolve_class(class_path)() for class_path in notification_classes] def process_notifications(): notifiers = get_notifiers() - for notification in models.Notification.objects.order_by('id') \ - .select_for_update().filter(done=False): + for notification in models.Notification.objects.order_by('id').select_for_update().filter(done=False): for notifier in notifiers: failures = [] if not notifier.skip(notification): try: notifier.process(notification) except Exception as e: - failures.append(u'Exception %r when handling with notifier %r' - % (force_text(e), notifier.__class__)) - logger.exception('Exception when handling notification %r with notifier %r', - notification, notifier) - django_journal.error_record('error', - 'notification {notification} failed for ' - 'notifier {notifier}', - notification=notification, - notifier=notifier.__class__) + failures.append( + u'Exception %r when handling with notifier %r' % (force_text(e), notifier.__class__) + ) + logger.exception( + 'Exception when handling notification %r with notifier %r', notification, notifier + ) + django_journal.error_record( + 'error', + 'notification {notification} failed for ' 'notifier {notifier}', + notification=notification, + notifier=notifier.__class__, + ) notification.done = True if len(failures) > 0: notification.failure = ','.join(failures) @@ -184,9 +194,14 @@ def process_notifications(): try: notifier.finish() except Exception as e: - logger.exception('Exception when finishing handling ' - 'notification %r with notifier %r', notification, notifier) - django_journal.error_record('error', 'unable to finish sending ' - 'notification with notifier {notifier}, error: {error}', - notifier=notifier.__class__, - error=force_text(e)) + logger.exception( + 'Exception when finishing handling ' 'notification %r with notifier %r', + notification, + notifier, + ) + django_journal.error_record( + 'error', + 'unable to finish sending ' 'notification with notifier {notifier}, error: {error}', + notifier=notifier.__class__, + error=force_text(e), + ) diff --git a/docbow_project/docbow/ods.py b/docbow_project/docbow/ods.py index 6b8b011..cf53060 100644 --- a/docbow_project/docbow/ods.py +++ b/docbow_project/docbow/ods.py @@ -64,17 +64,23 @@ class Workbook(object): z = zipfile.ZipFile(output, 'w') z.writestr('content.xml', self.get_data()) z.writestr('mimetype', 'application/vnd.oasis.opendocument.spreadsheet') - z.writestr('META-INF/manifest.xml', ''' + z.writestr( + 'META-INF/manifest.xml', + ''' -''') - z.writestr('styles.xml', ''' +''', + ) + z.writestr( + 'styles.xml', + ''' -''') +''', + ) z.close() @@ -93,9 +99,9 @@ class WorkSheet(object): root = ET.Element('{%s}table' % TABLE_NS) root.attrib['{%s}name' % TABLE_NS] = self.name ET.SubElement(root, '{%s}table-column' % TABLE_NS) - for i in range(0, max(self.cells.keys())+1): + for i in range(0, max(self.cells.keys()) + 1): row = ET.SubElement(root, '{%s}table-row' % TABLE_NS) - for j in range(0, max(self.cells.get(i).keys())+1): + for j in range(0, max(self.cells.get(i).keys()) + 1): cell = self.cells.get(i, {}).get(j, None) if not cell: ET.SubElement(row, '{%s}table-cell' % TABLE_NS) diff --git a/docbow_project/docbow/profile_views.py b/docbow_project/docbow/profile_views.py index 1d41cb6..e703750 100644 --- a/docbow_project/docbow/profile_views.py +++ b/docbow_project/docbow/profile_views.py @@ -36,8 +36,7 @@ class ProfileView(cbv.FormWithRequestMixin, cbv.FormWithPostTarget, UpdateView): return 'profile-validate' in self.request.POST def form_valid(self, form): - self.request.record('update-profile', 'modified its profile', - **form.cleaned_data) + self.request.record('update-profile', 'modified its profile', **form.cleaned_data) return super(ProfileView, self).form_valid(form) @@ -51,17 +50,17 @@ class DelegateView(cbv.FormWithPostTarget, FormView): super(DelegateView, self).__init__(*args, **kwargs) def add_journal_to_delegations(self, delegations): - delegations__to = [ delegation.to for delegation in delegations ] - journals = Journal.objects \ - .for_objects(delegations__to) \ - .filter(tag__name='login') \ - .order_by('-time')[:len(delegations__to)*5] + delegations__to = [delegation.to for delegation in delegations] + journals = ( + Journal.objects.for_objects(delegations__to) + .filter(tag__name='login') + .order_by('-time')[: len(delegations__to) * 5] + ) journal_by_users = dict() for journal in journals: for objectdata in journal.objectdata_set.all(): if objectdata.tag.name == 'delegate': - journal_by_users.setdefault(objectdata.object_id, []) \ - .append(journal.time) + journal_by_users.setdefault(objectdata.object_id, []).append(journal.time) for delegation in delegations: delegation.journals = journal_by_users.get(delegation.to.id, [])[:5] @@ -77,7 +76,7 @@ class DelegateView(cbv.FormWithPostTarget, FormView): def get_form_kwargs(self, **kwargs): kwargs = super(DelegateView, self).get_form_kwargs(**kwargs) kwargs['user'] = self.request.user - kwargs['delegatees'] = [ delegation.to for delegation in self.delegations ] + kwargs['delegatees'] = [delegation.to for delegation in self.delegations] kwargs['request'] = self.request return kwargs @@ -85,48 +84,49 @@ class DelegateView(cbv.FormWithPostTarget, FormView): request = self.request delegation.delete() delegate_user = delegation.to - request.record('delete-delegation', 'deleted delegation ' - '{delegation} to user {delegated}', - delegation=delegation.id, - delegated=delegate_user) + request.record( + 'delete-delegation', + 'deleted delegation ' '{delegation} to user {delegated}', + delegation=delegation.id, + delegated=delegate_user, + ) user = request.user # notify delegate ctx = { - 'user': utils.clean_ldap_user(user), - 'delegate': delegate_user, - 'to': [delegate_user.email], - 'reply_to': user.email, - } + 'user': utils.clean_ldap_user(user), + 'delegate': delegate_user, + 'to': [delegate_user.email], + 'reply_to': user.email, + } models.Notification.objects.create(kind='delete-delegation', ctx=ctx) - messages.info(request, _('Delegation %s supressed') % - delegate_user) - messages.info(request, _('%s has been notified.') % - delegate_user.email) - request.record('notify', 'notified {email} of ' - 'the deletion of its delegate user', - email=delegate_user.email) + messages.info(request, _('Delegation %s supressed') % delegate_user) + messages.info(request, _('%s has been notified.') % delegate_user.email) + request.record( + 'notify', 'notified {email} of ' 'the deletion of its delegate user', email=delegate_user.email + ) # delete guest accounts if delegation.guest_delegate: - if 'mellon' in app_settings.settings.INSTALLED_APPS \ - and delegate_user.saml_identifiers.count(): + if 'mellon' in app_settings.settings.INSTALLED_APPS and delegate_user.saml_identifiers.count(): err, json_data, err_desc = utils.a2_wscall( urllib.parse.urljoin( app_settings.settings.AUTHENTIC_URL, - 'api/users/%s' % delegate_user.saml_identifiers.first().name_id + 'api/users/%s' % delegate_user.saml_identifiers.first().name_id, ), - 'delete') + 'delete', + ) delegate_user.delete() - request.record('delete-delegate', 'deleted delegate ' - 'user {username}, {first_name} {last_name}, ' - '({email})', - username=delegate_user.username, - first_name=delegate_user.first_name, - last_name=delegate_user.last_name, - email=delegate_user.email) + request.record( + 'delete-delegate', + 'deleted delegate ' 'user {username}, {first_name} {last_name}, ' '({email})', + username=delegate_user.username, + first_name=delegate_user.first_name, + last_name=delegate_user.last_name, + email=delegate_user.email, + ) return HttpResponseRedirect(self.success_url) def is_post_target(self): @@ -134,8 +134,7 @@ class DelegateView(cbv.FormWithPostTarget, FormView): return True for delegation in self.delegations: username = delegation.to.username - if 'delegate-delete-{username}.x'.format(username=username) in \ - self.request.POST: + if 'delegate-delete-{username}.x'.format(username=username) in self.request.POST: return True return False @@ -145,8 +144,7 @@ class DelegateView(cbv.FormWithPostTarget, FormView): return super(DelegateView, self).post(request, *args, **kwargs) for delegation in self.delegations: username = delegation.to.username - if 'delegate-delete-{username}.x'.format(username=username) in \ - self.request.POST: + if 'delegate-delete-{username}.x'.format(username=username) in self.request.POST: return self.delete(delegation) return self.get(request, *args, **kwargs) @@ -155,67 +153,70 @@ class DelegateView(cbv.FormWithPostTarget, FormView): is_guest = not form.cleaned_data.get('existing_user') ctx = { 'user': utils.clean_ldap_user(request.user), - 'reply_to' :request.user.email, + 'reply_to': request.user.email, 'is_guest': is_guest, } if is_guest: delegate_username = utils.get_free_delegation_number(request.user) - delegate_user = User(username=delegate_username, - first_name=form.cleaned_data.get('first_name', ''), - last_name=form.cleaned_data.get('last_name', ''), - email=form.cleaned_data.get('email', '')) + delegate_user = User( + username=delegate_username, + first_name=form.cleaned_data.get('first_name', ''), + last_name=form.cleaned_data.get('last_name', ''), + email=form.cleaned_data.get('email', ''), + ) delegate_user.set_unusable_password() delegate_user.save() - models.DocbowProfile.objects.create(user=delegate_user, - is_guest=True, accept_notifications=app_settings.DEFAULT_ACCEPT_NOTIFICATIONS_FOR_GUEST) - delegation, created = models.Delegation.objects.get_or_create(by=request.user, - to=delegate_user) + models.DocbowProfile.objects.create( + user=delegate_user, + is_guest=True, + accept_notifications=app_settings.DEFAULT_ACCEPT_NOTIFICATIONS_FOR_GUEST, + ) + delegation, created = models.Delegation.objects.get_or_create(by=request.user, to=delegate_user) if 'mellon' in app_settings.settings.INSTALLED_APPS: import mellon + ctx['sso'] = True mellon.models.UserSAMLIdentifier.objects.create( name_id=form.cleaned_data['name_id'], - issuer=urllib.parse.urljoin( - app_settings.settings.AUTHENTIC_URL, 'idp/saml2/metadata' - ), - user=delegate_user + issuer=urllib.parse.urljoin(app_settings.settings.AUTHENTIC_URL, 'idp/saml2/metadata'), + user=delegate_user, ) - request.record('create-delegate', 'created delegate with ' - 'parameters {first_name} {last_name} <{email}>', - **form.cleaned_data) - ctx.update({ - 'password_reset_link': - utils.make_password_reset_url(request, delegate_user), - 'to': [form.cleaned_data['email']], - 'delegate': delegate_user, - 'delegation': delegation, - }) - messages.info(request, - _('New delegation to user %s created.') % delegate_user.username) - messages.info(request, - _('A notification was sent to %s about this new delegation') % delegate_user.email) + request.record( + 'create-delegate', + 'created delegate with ' 'parameters {first_name} {last_name} <{email}>', + **form.cleaned_data, + ) + ctx.update( + { + 'password_reset_link': utils.make_password_reset_url(request, delegate_user), + 'to': [form.cleaned_data['email']], + 'delegate': delegate_user, + 'delegation': delegation, + } + ) + messages.info(request, _('New delegation to user %s created.') % delegate_user.username) + messages.info( + request, _('A notification was sent to %s about this new delegation') % delegate_user.email + ) else: delegate_user = form.cleaned_data.get('existing_user') delegate_username = delegate_user.username - delegation, created = models.Delegation.objects.get_or_create(by=request.user, - to=delegate_user) - ctx.update({ - 'to': models.all_emails(delegate_user), - 'delegate': delegate_user, - 'delegation': delegation, - }) - messages.info(request, - _('New delegation to user %s created.') % - delegate_user.username) - messages.info(request, - _('A notification was sent to %s about this new delegation') % delegate_user.get_full_name()) + delegation, created = models.Delegation.objects.get_or_create(by=request.user, to=delegate_user) + ctx.update( + {'to': models.all_emails(delegate_user), 'delegate': delegate_user, 'delegation': delegation,} + ) + messages.info(request, _('New delegation to user %s created.') % delegate_user.username) + messages.info( + request, + _('A notification was sent to %s about this new delegation') % delegate_user.get_full_name(), + ) models.Notification.objects.create(kind='new-delegation', ctx=ctx) - request.record('create-delegation', 'created delegation to ' - 'user {delegated}', delegated=delegate_user) + request.record( + 'create-delegation', 'created delegation to ' 'user {delegated}', delegated=delegate_user + ) return super(DelegateView, self).form_valid(form) - def get_context_data(self, **kwargs): ctx = super(DelegateView, self).get_context_data(**kwargs) ctx['delegations'] = self.delegations @@ -255,10 +256,10 @@ class EmailView(cbv.FormWithPostTarget, UpdateView): def form_valid(self, form): messages.info(self.request, _('Email changed')) - self.request.record('update-email', 'modified its email', - **form.cleaned_data) + self.request.record('update-email', 'modified its email', **form.cleaned_data) return super(EmailView, self).form_valid(form) + class NotificationPreferenceView(cbv.FormWithPostTarget, cbv.FormWithRequestMixin, FormView): form_class = forms.NotificationPreferencesForm template_name = 'docbow/notifications.html' @@ -286,15 +287,15 @@ class FullProfileView(TemplateResponseMixin, View): # multiplex all profile views template_name = 'docbow/full-profile.html' subviews = ( - ('notifications_form', NotificationPreferenceView), - ('delegate_form', DelegateView), + ('notifications_form', NotificationPreferenceView), + ('delegate_form', DelegateView), ) if 'mellon' not in app_settings.settings.INSTALLED_APPS: subviews = subviews + (('password_change_form', PasswordChangeView),) if app_settings.MOBILE_PHONE or app_settings.PERSONAL_EMAIL: - subviews = (('profile_form', ProfileView),) + subviews + subviews = (('profile_form', ProfileView),) + subviews if app_settings.EDIT_EMAIL and 'mellon' not in app_settings.settings.INSTALLED_APPS: - subviews += (('email_form', EmailView),) + subviews += (('email_form', EmailView),) def dispatch(self, request, *args, **kwargs): if models.is_guest(request.user): @@ -314,8 +315,7 @@ class FullProfileView(TemplateResponseMixin, View): def get_view(self, var_name, view_class): view = view_class() - view.request, view.args, view.kwargs = \ - self.request, self.args, self.kwargs + view.request, view.args, view.kwargs = self.request, self.args, self.kwargs if hasattr(view, 'get_object'): view.object = view.get_object() if self.request.method == 'POST' and view.is_post_target(): diff --git a/docbow_project/docbow/pyuca.py b/docbow_project/docbow/pyuca.py index 3973a38..7bee33e 100644 --- a/docbow_project/docbow/pyuca.py +++ b/docbow_project/docbow/pyuca.py @@ -5,17 +5,17 @@ # http://jtauber.com/ # Copyright (c) 2006 James Tauber -# +# # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: -# +# # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. -# +# # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -50,7 +50,6 @@ import os.path class Trie: - def __init__(self): self.root = [None, {}] @@ -72,7 +71,6 @@ class Trie: class Collator: - def __init__(self, filename): self.table = Trie() @@ -84,10 +82,10 @@ class Collator: continue if line.strip() == "": continue - line = line[:line.find("#")] + "\n" - line = line[:line.find("%")] + "\n" + line = line[: line.find("#")] + "\n" + line = line[: line.find("%")] + "\n" line = line.strip() - + if line.startswith("@"): pass else: @@ -98,20 +96,20 @@ class Collator: while True: begin = x.find("[") if begin == -1: - break + break end = x[begin:].find("]") - collElement = x[begin:begin+end+1] - x = x[begin + 1:] - + collElement = x[begin : begin + end + 1] + x = x[begin + 1 :] + alt = collElement[1] chars = collElement[2:-1].split(".") - + collElements.append((alt, chars)) integer_points = [int(ch, 16) for ch in charList] self.table.add(integer_points, collElements) def sort_key(self, string): - + collation_elements = [] lookup_key = [ord(ch) for ch in string] @@ -121,17 +119,18 @@ class Collator: # @@@ raise ValueError(map(hex, lookup_key)) collation_elements.extend(value) - + sort_key = [] - + for level in range(4): if level: - sort_key.append(0) # level separator + sort_key.append(0) # level separator for element in collation_elements: ce_l = int(element[1][level], 16) if ce_l: sort_key.append(ce_l) - + return tuple(sort_key) + collator = Collator(os.path.join(os.path.dirname(__file__), 'allkeys.txt')) diff --git a/docbow_project/docbow/signals.py b/docbow_project/docbow/signals.py index 898a8c8..ce664ab 100644 --- a/docbow_project/docbow/signals.py +++ b/docbow_project/docbow/signals.py @@ -32,8 +32,10 @@ def logged_out_handler(sender, request, user, **kwargs): user = user.user django_journal.record('logout', msg, user=user, ip=extra['ip'], **kwargs) + do_log = True + def modified_data(sender, instance, created, raw, using, **kwargs): global do_log if using != 'default': @@ -51,8 +53,9 @@ def modified_data(sender, instance, created, raw, using, **kwargs): else: tag = 'modify' msg = '{user} modified {model} {instance}' - django_journal.record(tag, msg, user=extra['user'], ip=extra['ip'], - model=sender._meta.model_name, instance=instance) + django_journal.record( + tag, msg, user=extra['user'], ip=extra['ip'], model=sender._meta.model_name, instance=instance + ) def list_m2m_changed_handler(sender, instance, action, reverse, model, pk_set, using, **kwargs): @@ -75,8 +78,8 @@ def list_m2m_changed_handler(sender, instance, action, reverse, model, pk_set, u elif action == 'post_remove': msg = N_('removed user {member} from mailing list {list}') for user in users: - django_journal.record(action, msg, list=instance, member=user, - ip=extra['ip'], user=extra['user']) + django_journal.record(action, msg, list=instance, member=user, ip=extra['ip'], user=extra['user']) + @receiver(m2m_changed, sender=User.groups.through) def groups_changed(sender, instance, action, **kwargs): @@ -91,10 +94,10 @@ def groups_changed(sender, instance, action, **kwargs): finally: do_log = True + user_logged_in.connect(logged_in_handler) user_logged_out.connect(logged_out_handler) db_post_save.connect(modified_data, sender=models.MailingList) db_post_save.connect(modified_data, sender=auth_models.User) db_post_save.connect(modified_data, sender=auth_models.Group) -m2m_changed.connect(list_m2m_changed_handler, - sender=models.MailingList.members.through) +m2m_changed.connect(list_m2m_changed_handler, sender=models.MailingList.members.through) diff --git a/docbow_project/docbow/sms_carrier_ovh.py b/docbow_project/docbow/sms_carrier_ovh.py index d670f28..a098707 100644 --- a/docbow_project/docbow/sms_carrier_ovh.py +++ b/docbow_project/docbow/sms_carrier_ovh.py @@ -10,6 +10,7 @@ from django_journal import journal as django_journal logger = logging.getLogger(__name__) + class OVHSMSCarrier(object): URL = 'https://www.ovh.com/cgi-bin/sms/http2sms.cgi' SMS_CLASS = 1 @@ -19,29 +20,33 @@ class OVHSMSCarrier(object): sms_class = sms_class or self.SMS_CLASS to = ','.join([t.replace('+', '00') for t in to]) params = { - 'account': settings.OVH_SMS_ACCOUNT, - 'login': settings.OVH_SMS_LOGIN, - 'password': settings.OVH_SMS_PASSWORD, - 'from': settings.OVH_SMS_FROM, - 'to': to, - 'message': payload, - 'contentType': 'text/json', - 'class': sms_class, + 'account': settings.OVH_SMS_ACCOUNT, + 'login': settings.OVH_SMS_LOGIN, + 'password': settings.OVH_SMS_PASSWORD, + 'from': settings.OVH_SMS_FROM, + 'to': to, + 'message': payload, + 'contentType': 'text/json', + 'class': sms_class, } if no_stop: params['no_stop'] = 1 - django_journal.error_record('ovh-sms', - 'OVH SMS CARRIER: sending message {message} to {numbers}', - message=message, numbers=to) + django_journal.error_record( + 'ovh-sms', 'OVH SMS CARRIER: sending message {message} to {numbers}', message=message, numbers=to + ) stream = urlopen('%s?%s' % (self.URL, urlencode(params))) result = json.loads(stream.read()) if 100 <= result['status'] < 200: credit_alert = getattr(settings, 'OVH_SMS_CREDIT_ALERT', 100) credit_left = result['creditLeft'] if credit_left < credit_alert: - django_journal.error_record('error', 'OVH SMS CARRIER: credit ' - 'left({credit_left}) < credit alert limit({credit_alert})', - credit_left=credit_left, credit_alert=credit_alert) + django_journal.error_record( + 'error', + 'OVH SMS CARRIER: credit ' 'left({credit_left}) < credit alert limit({credit_alert})', + credit_left=credit_left, + credit_alert=credit_alert, + ) else: - django_journal.error_record('error', 'OVH SMS CARRIER: status "{status}"' - 'message "{message}"', **result) + django_journal.error_record( + 'error', 'OVH SMS CARRIER: status "{status}"' 'message "{message}"', **result + ) diff --git a/docbow_project/docbow/sql.py b/docbow_project/docbow/sql.py index f0e3e2f..5f3f7a5 100644 --- a/docbow_project/docbow/sql.py +++ b/docbow_project/docbow/sql.py @@ -34,17 +34,19 @@ def get_complex_join(qs, sql, params): def get_unseen_documents_count(related_users, user): query = GET_UNSEEN_DOCUMENTS_SQL % ('(%s)' % ', '.join(['%s'] * len(related_users))) return get_sql_count( - query, (False,) + - tuple(related_users.values_list('id', flat=True)) + (user.pk, user.pk, False, user.pk, True) + query, + (False,) + + tuple(related_users.values_list('id', flat=True)) + + (user.pk, user.pk, False, user.pk, True), ) def get_documents(qs, related_users, user, outbox): query = GET_DOCUMENTS_SQL % ('(%s)' % ', '.join(['%s'] * len(related_users))) qs = get_complex_join( - qs, query, - (outbox, ) + tuple( - related_users.values_list('id', flat=True)) + (user.pk, False, user.pk, True) + qs, + query, + (outbox,) + tuple(related_users.values_list('id', flat=True)) + (user.pk, False, user.pk, True), ) qs = qs.prefetch_related('to_list', 'to_user', 'mailboxes__owner') qs = qs.extra(select={'seen': SEEN_DOCUMENT % user.pk}) @@ -54,9 +56,9 @@ def get_documents(qs, related_users, user, outbox): def get_trash_documents(qs, related_users, user, outbox): query = GET_TRASH_DOCUMENTS_SQL % ('(%s)' % ', '.join(['%s'] * len(related_users))) qs = get_complex_join( - qs, query, - (outbox, ) + tuple( - related_users.values_list('id', flat=True)) + (user.pk, False, user.pk, True) + qs, + query, + (outbox,) + tuple(related_users.values_list('id', flat=True)) + (user.pk, False, user.pk, True), ) qs = qs.prefetch_related('to_list', 'to_user', 'mailboxes__owner') return qs diff --git a/docbow_project/docbow/tables.py b/docbow_project/docbow/tables.py index 3c3e3c5..02806bd 100644 --- a/docbow_project/docbow/tables.py +++ b/docbow_project/docbow/tables.py @@ -10,28 +10,25 @@ from docbow_project.docbow import models class MailboxTable(tables.Table): - class Meta: model = models.Document attrs = {"class": "paleblue"} + # FIXEM: translation in inline templates are ignored _('self') + class OutboxCsvTable(tables.Table): - official_sender = tables.Column(accessor='sender.get_full_name', - verbose_name=_('official_sender_header')) - recipients = tables.Column(accessor='recipients', - verbose_name=_('recipients_header'), orderable=False) + official_sender = tables.Column(accessor='sender.get_full_name', verbose_name=_('official_sender_header')) + recipients = tables.Column(accessor='recipients', verbose_name=_('recipients_header'), orderable=False) real_sender = tables.TemplateColumn( - '{% load i18n %}{% if record.real_sender %}{{ record.real_sender }}{% else %}{% trans "self" %}{% endif %}', - verbose_name=_('real_sender_header')) - filetype = tables.Column(accessor='filetype', - verbose_name=_('type_header')) - filenames = tables.Column(accessor='filenames', - verbose_name=_('filename_header'), orderable=False) - date = tables.Column(accessor='date', - verbose_name=_('date_header')) + '{% load i18n %}{% if record.real_sender %}{{ record.real_sender }}{% else %}{% trans "self" %}{% endif %}', + verbose_name=_('real_sender_header'), + ) + filetype = tables.Column(accessor='filetype', verbose_name=_('type_header')) + filenames = tables.Column(accessor='filenames', verbose_name=_('filename_header'), orderable=False) + date = tables.Column(accessor='date', verbose_name=_('date_header')) class Meta: model = models.Document @@ -40,27 +37,27 @@ class OutboxCsvTable(tables.Table): attrs = {"class": "paleblue mailbox-table"} empty_text = _('No message') + SELECT_ALL = mark_safe('') class OutboxBaseTable(tables.Table): - official_sender = tables.Column(accessor='sender.get_full_name', - order_by=('sender__last_name', - 'sender__first_name', - 'sender__username'), - verbose_name=_('official_sender_header')) - recipients = tables.Column(accessor='recipients', - verbose_name=_('recipients_header'), orderable=False) + official_sender = tables.Column( + accessor='sender.get_full_name', + order_by=('sender__last_name', 'sender__first_name', 'sender__username'), + verbose_name=_('official_sender_header'), + ) + recipients = tables.Column(accessor='recipients', verbose_name=_('recipients_header'), orderable=False) real_sender = tables.TemplateColumn( - '{% load i18n %}{% if record.real_sender %}{{ record.real_sender }}{% else %}{% trans "self" %}{% endif %}', - order_by=('real_sender',), - verbose_name=_('real_sender_header')) - filetype = tables.Column(accessor='filetype', - verbose_name=_('type_header')) - filenames = tables.Column(accessor='filenames', - verbose_name=_('filename_header'), orderable=False) - date = tables.TemplateColumn('{% load humantime %}{{ record.date|humantime }}', - verbose_name=_('date_header')) + '{% load i18n %}{% if record.real_sender %}{{ record.real_sender }}{% else %}{% trans "self" %}{% endif %}', + order_by=('real_sender',), + verbose_name=_('real_sender_header'), + ) + filetype = tables.Column(accessor='filetype', verbose_name=_('type_header')) + filenames = tables.Column(accessor='filenames', verbose_name=_('filename_header'), orderable=False) + date = tables.TemplateColumn( + '{% load humantime %}{{ record.date|humantime }}', verbose_name=_('date_header') + ) class Meta: model = models.Document @@ -73,8 +70,7 @@ class OutboxBaseTable(tables.Table): class OutboxTrashTable(OutboxBaseTable): restore = tables.TemplateColumn( - template_name='docbow/outbox_restore_column.html', - orderable=False, verbose_name=_('Restore') + template_name='docbow/outbox_restore_column.html', orderable=False, verbose_name=_('Restore') ) class Meta: @@ -87,11 +83,13 @@ class OutboxTrashTable(OutboxBaseTable): class OutboxTable(OutboxBaseTable): select = tables.TemplateColumn( - '', - orderable=False, verbose_name=SELECT_ALL) - delete = tables.TemplateColumn(template_name='docbow/outbox_delete_column.html', - orderable=False, - verbose_name=' ') + '', + orderable=False, + verbose_name=SELECT_ALL, + ) + delete = tables.TemplateColumn( + template_name='docbow/outbox_delete_column.html', orderable=False, verbose_name=' ' + ) class Meta: model = models.Document @@ -102,16 +100,10 @@ class OutboxTable(OutboxBaseTable): class InboxCsvTable(tables.Table): - filetype = tables.Column( - accessor='filetype', - verbose_name=_('type_header')) - filenames = tables.Column( - accessor='filenames', verbose_name=_('filename_header'), - orderable=False) - sender = tables.Column( - accessor='sender', verbose_name=_('sender_header')) - date = tables.Column( - accessor='date', verbose_name=_('date_header')) + filetype = tables.Column(accessor='filetype', verbose_name=_('type_header')) + filenames = tables.Column(accessor='filenames', verbose_name=_('filename_header'), orderable=False) + sender = tables.Column(accessor='sender', verbose_name=_('sender_header')) + date = tables.Column(accessor='date', verbose_name=_('date_header')) class Meta: model = models.Document @@ -120,27 +112,31 @@ class InboxCsvTable(tables.Table): class InboxBaseTable(tables.Table): - seen = tables.BooleanColumn(accessor='seen', yesno=u' ,✔', - verbose_name=' ', orderable=False) - filetype = tables.Column( - accessor='filetype', - verbose_name=_('type_header')) - filenames = tables.Column( - accessor='filenames', verbose_name=_('filename_header'), - orderable=False) + seen = tables.BooleanColumn(accessor='seen', yesno=u' ,✔', verbose_name=' ', orderable=False) + filetype = tables.Column(accessor='filetype', verbose_name=_('type_header')) + filenames = tables.Column(accessor='filenames', verbose_name=_('filename_header'), orderable=False) recipients = tables.TemplateColumn( - '''{% load docbow %}{% for recipient_user in record.delivered_to|intersect:related_users %} + '''{% load docbow %}{% for recipient_user in record.delivered_to|intersect:related_users %} {{ recipient_user|username }}{% if not forloop.last %},{% endif %} -{% endfor %}''', orderable=False, verbose_name=_('recipients_header')) - sender = tables.TemplateColumn('{{ record.sender_display }}', - order_by=('sender__last_name', 'sender__first_name', 'sender__username'), - verbose_name=_('sender_header')) - date = tables.TemplateColumn('{% load humantime %}{{ record.date|humantime }}', - verbose_name=_('date_header')) +{% endfor %}''', + orderable=False, + verbose_name=_('recipients_header'), + ) + sender = tables.TemplateColumn( + '{{ record.sender_display }}', + order_by=('sender__last_name', 'sender__first_name', 'sender__username'), + verbose_name=_('sender_header'), + ) + date = tables.TemplateColumn( + '{% load humantime %}{{ record.date|humantime }}', verbose_name=_('date_header') + ) class Meta: model = models.Document - fields = ('seen', 'filetype',) + fields = ( + 'seen', + 'filetype', + ) attrs = {"class": "paleblue mailbox-table refresh", "id": "outbox-table"} empty_text = _('No message') @@ -148,26 +144,36 @@ class InboxBaseTable(tables.Table): class InboxTrashTable(InboxBaseTable): restore = tables.TemplateColumn( - template_name='docbow/inbox_restore_column.html', - orderable=False, verbose_name=_('Restore') + template_name='docbow/inbox_restore_column.html', orderable=False, verbose_name=_('Restore') ) class Meta: model = models.Document - fields = ('select', 'seen', 'filetype',) + fields = ( + 'select', + 'seen', + 'filetype', + ) attrs = {"class": "paleblue mailbox-table refresh", "id": "inbox-table"} empty_text = _('No message') class InboxTable(InboxBaseTable): select = tables.TemplateColumn( - '', - verbose_name=SELECT_ALL, orderable=False) - delete = tables.TemplateColumn(template_name='docbow/inbox_delete_column.html', - orderable=False, verbose_name=' ') + '', + verbose_name=SELECT_ALL, + orderable=False, + ) + delete = tables.TemplateColumn( + template_name='docbow/inbox_delete_column.html', orderable=False, verbose_name=' ' + ) class Meta: model = models.Document - fields = ('select', 'seen', 'filetype',) + fields = ( + 'select', + 'seen', + 'filetype', + ) attrs = {"class": "paleblue mailbox-table refresh", "id": "outbox-table"} empty_text = _('No message') diff --git a/docbow_project/docbow/templatetags/docbow.py b/docbow_project/docbow/templatetags/docbow.py index d4eb035..f31ca0b 100644 --- a/docbow_project/docbow/templatetags/docbow.py +++ b/docbow_project/docbow/templatetags/docbow.py @@ -8,6 +8,7 @@ from .. import views, models, app_settings, sql register = template.Library() + def paginator(context): """ To be used in conjunction with the object_list generic view. @@ -20,7 +21,7 @@ def paginator(context): page = context['page'] hits = page.paginator.count pn = page.number - link_to_last_page = (page.paginator.num_pages > 2) and (pn < (page.paginator.num_pages-1)) + link_to_last_page = (page.paginator.num_pages > 2) and (pn < (page.paginator.num_pages - 1)) return { 'hits': hits, 'page_number': page.number, @@ -28,14 +29,16 @@ def paginator(context): 'last_page': page.paginator.num_pages, 'first': page.start_index(), 'last': page.end_index(), - 'previous': page.number-1, - 'next': page.number+1, + 'previous': page.number - 1, + 'next': page.number + 1, 'has_next': page.has_next(), 'has_previous': page.has_previous(), } + register.inclusion_tag('docbow/paginator.html', takes_context=True)(paginator) + def menu(context): request = context.get('request') here = context.get('view_name') @@ -60,47 +63,63 @@ def menu(context): view_name = slugify(view_name) else: url = reverse(view_name) - docbow_menu.append({ 'view_name': view_name.replace(':', '-'), - 'url': url, 'caption': caption, 'count': count, - 'current': current}) - return { 'menus': docbow_menu } + docbow_menu.append( + { + 'view_name': view_name.replace(':', '-'), + 'url': url, + 'caption': caption, + 'count': count, + 'current': current, + } + ) + return {'menus': docbow_menu} + register.inclusion_tag('docbow/menu.html', takes_context=True)(menu) + def username(value): return models.username(value) + + register.filter(username) + def doc_real_sender(document): return document.real_sender or models.username(document.sender) + + register.filter(doc_real_sender) + def frfilesizeformat(value): - if value < 1024*1024: + if value < 1024 * 1024: value = value / 1024.0 unit = _('kB') else: - value = value / (1024*1024.0) + value = value / (1024 * 1024.0) unit = _('MB') return "%.1f %s" % (value, unit) + register.filter(frfilesizeformat) + def humantargets(value): user_human_to = value.user_human_to() group_human_to = value.group_human_to() l1 = map(lambda x: _('to %s') % x, user_human_to) if len(user_human_to) > 1: - p1 = _('%(one)s and %(two)s') % { 'one': ', '.join(l1[0:-1]), 'two': l1[-1] } + p1 = _('%(one)s and %(two)s') % {'one': ', '.join(l1[0:-1]), 'two': l1[-1]} elif user_human_to: p1 = l1[0] l2 = map(lambda x: _('to list %s') % x, group_human_to) if len(group_human_to) > 1: - p2 = _('%(one)s and %(two)s') % { 'one': ', '.join(l2[0:-1]), 'two': l2[-1] } + p2 = _('%(one)s and %(two)s') % {'one': ', '.join(l2[0:-1]), 'two': l2[-1]} elif group_human_to: p2 = l2[0] if l1 and l2: - return _('%(one)s and to lists %(two)s') % { 'one': p1, 'two': p2 } + return _('%(one)s and to lists %(two)s') % {'one': p1, 'two': p2} elif l1: return p1 elif l2: @@ -108,11 +127,14 @@ def humantargets(value): else: return '' + register.filter(humantargets) + def nonbreakinghyphen(value): return value.replace('-', '‑') + register.filter(nonbreakinghyphen) @@ -121,11 +143,13 @@ def order_by(queryset, args): args = [x.strip() for x in args.split(',')] return queryset.order_by(*args) + @register.filter_function def intersect(iterable1, iterable2): iterable2 = set(iterable2) return [it for it in iterable1 if it in iterable2] + @register.filter def is_checkbox(field): return field.field.widget.__class__.__name__.lower() == "checkboxinput" diff --git a/docbow_project/docbow/unicodecsv.py b/docbow_project/docbow/unicodecsv.py index 75c0f7a..1d74bb6 100644 --- a/docbow_project/docbow/unicodecsv.py +++ b/docbow_project/docbow/unicodecsv.py @@ -4,6 +4,7 @@ import csv from django.utils.encoding import force_str, force_text from django.utils.six import StringIO + class UnicodeWriter(object): """ Like UnicodeDictWriter, but takes lists rather than dictionaries. @@ -21,6 +22,7 @@ class UnicodeWriter(object): ]) fp.close() """ + def __init__(self, f, dialect=csv.excel, encoding="utf-8", **kwds): # Redirect output to a queue self.queue = StringIO() @@ -45,6 +47,7 @@ class UnicodeWriter(object): for row in rows: self.writerow(row) + class UnicodeDictWriter(UnicodeWriter): """ A CSV writer that produces Excel-compatibly CSV files from unicode data. @@ -67,8 +70,7 @@ class UnicodeDictWriter(UnicodeWriter): Initially derived from http://docs.python.org/lib/csv-examples.html """ - def __init__(self, f, fields, dialect=csv.excel_tab, - encoding="utf-16", **kwds): + def __init__(self, f, fields, dialect=csv.excel_tab, encoding="utf-16", **kwds): super(UnicodeDictWriter, self).__init__(f, dialect, encoding, **kwds) self.fields = fields diff --git a/docbow_project/docbow/upload_urls.py b/docbow_project/docbow/upload_urls.py index ec28e82..d4a5113 100644 --- a/docbow_project/docbow/upload_urls.py +++ b/docbow_project/docbow/upload_urls.py @@ -7,15 +7,12 @@ urlpatterns = [ url( r'^(?P[a-zA-Z0-9-]+)/(?P[0-9]+)/$', docbow_project.docbow.upload_views.upload, - name='upload' - ), - url( - r'^(?P[a-zA-Z0-9-]+)/$', - docbow_project.docbow.upload_views.upload, - name='upload' + name='upload', ), + url(r'^(?P[a-zA-Z0-9-]+)/$', docbow_project.docbow.upload_views.upload, name='upload'), url( r'^(?P[a-zA-Z0-9-]+)/(?P[^/]+)$', docbow_project.docbow.upload_views.upload_file, - name='uploaded'), + name='uploaded', + ), ] diff --git a/docbow_project/docbow/upload_views.py b/docbow_project/docbow/upload_views.py index ec135b8..53b8869 100644 --- a/docbow_project/docbow/upload_views.py +++ b/docbow_project/docbow/upload_views.py @@ -14,6 +14,7 @@ from django.utils.translation import ugettext_lazy as _ from .models import FileTypeAttachedFileKind from . import app_settings + def get_paths_for_id(upload_id): storage = DefaultStorage() path = os.path.join('upload', upload_id) @@ -21,6 +22,7 @@ def get_paths_for_id(upload_id): return [] return list(storage.listdir(path)[1]) + def get_files_for_id(upload_id): storage = DefaultStorage() path = os.path.join('upload', upload_id) @@ -30,12 +32,13 @@ def get_files_for_id(upload_id): name = os.path.basename(filepath) yield storage.open(os.path.join(path, name)) + def get_data_for_id(upload_id): for file_object in get_files_for_id(upload_id): name = os.path.basename(file_object.name) url = reverse('uploaded', args=[upload_id, name]) - yield { 'name': name, 'size': file_object.size, 'url': url, - 'delete_url': url, 'delete_type': 'DELETE' } + yield {'name': name, 'size': file_object.size, 'url': url, 'delete_url': url, 'delete_type': 'DELETE'} + def response_mimetype(request): if "application/json" in request.META['HTTP_ACCEPT']: @@ -43,13 +46,13 @@ def response_mimetype(request): else: return "text/plain" + class JSONResponse(HttpResponse): """JSON response class.""" - def __init__(self,obj='',json_opts={},mimetype="application/json",*args,**kwargs): - content = json.dumps(obj,**json_opts) - super(JSONResponse,self).__init__(content,mimetype,*args,**kwargs) - + def __init__(self, obj='', json_opts={}, mimetype="application/json", *args, **kwargs): + content = json.dumps(obj, **json_opts) + super(JSONResponse, self).__init__(content, mimetype, *args, **kwargs) @csrf_exempt @@ -75,32 +78,36 @@ def upload(request, transaction_id, file_kind=None): if uploaded_file.size > app_settings.MAX_FILE_SIZE: message = _('File is too big, limit is %(max_file_size)s bytes') message = message % {'max_file_size': app_settings.MAX_FILE_SIZE} - data.append({'name': uploaded_file.name, 'error': force_text(message) }) + data.append({'name': uploaded_file.name, 'error': force_text(message)}) continue if file_kind: if not file_kind.match_file(uploaded_file): - message = _('invalid file type, check required ' - 'file types for this field') - data.append({'name': uploaded_file.name, 'error': force_text(message) }) + message = _('invalid file type, check required ' 'file types for this field') + data.append({'name': uploaded_file.name, 'error': force_text(message)}) continue uploaded_file.open() if len(uploaded_file.name) > max_filename_length: - message = _('filename too long, only %d characters allowed') % \ - max_filename_length - data.append({'name': uploaded_file.name, 'error': force_text(message) }) + message = _('filename too long, only %d characters allowed') % max_filename_length + data.append({'name': uploaded_file.name, 'error': force_text(message)}) continue if max_files: count = len(get_paths_for_id(transaction_id)) if count >= max_files: message = _('too much file attached, delete to add a new one') - data.append({'name': uploaded_file.name, 'error': force_text(message) }) + data.append({'name': uploaded_file.name, 'error': force_text(message)}) continue path = os.path.join('upload', str(transaction_id), uploaded_file.name) filename = storage.save(path, uploaded_file) url = '%s%s' % (url, os.path.basename(filename)) - data.append({'name': uploaded_file.name, 'size': - uploaded_file.size, 'url': url, 'delete_url': url, - 'delete_type': "DELETE"}) + data.append( + { + 'name': uploaded_file.name, + 'size': uploaded_file.size, + 'url': url, + 'delete_url': url, + 'delete_type': "DELETE", + } + ) response = JSONResponse(data, {}, response_mimetype(request)) response['Content-Disposition'] = 'inline; filename=files.json' return response @@ -110,6 +117,7 @@ def upload(request, transaction_id, file_kind=None): response['Content-Disposition'] = 'inline; filename=files.json' return response + def file_response(file_object): if not isinstance(file_object, File): file_object = File(file_object) @@ -117,6 +125,7 @@ def file_response(file_object): response['Content-disposition'] = 'attachment' return response + @csrf_exempt def upload_file(request, transaction_id, filename): storage = DefaultStorage() diff --git a/docbow_project/docbow/urls.py b/docbow_project/docbow/urls.py index 8251079..0ccfe2b 100644 --- a/docbow_project/docbow/urls.py +++ b/docbow_project/docbow/urls.py @@ -7,71 +7,90 @@ import docbow_project.docbow.views urlpatterns = [ url(r'^$', docbow_project.docbow.views.homepage, name='homepage'), url(r'^profile/$', docbow_project.docbow.views.profile, name='profile'), - # inbox - url(r'^inbox/$', docbow_project.docbow.views.inbox_view, name='inbox'), url(r'^inbox/trash/$', docbow_project.docbow.views.inbox_trash_view, name='inbox-trash'), - url(r'^inbox_by_document/(?P\d+)/$', + url( + r'^inbox_by_document/(?P\d+)/$', docbow_project.docbow.views.inbox_by_document, - name='inbox-by-document-message'), - url(r'^inbox/(?P\d+)/$', - docbow_project.docbow.views.message, name='inbox-message', - kwargs={'outbox': False}), - url(r'^inbox/(?P\d+)/delete/$', + name='inbox-by-document-message', + ), + url( + r'^inbox/(?P\d+)/$', + docbow_project.docbow.views.message, + name='inbox-message', + kwargs={'outbox': False}, + ), + url( + r'^inbox/(?P\d+)/delete/$', docbow_project.docbow.views.delete, - name='inbox-message-delete'), - url(r'^inbox/(?P\d+)/restore/$', + name='inbox-message-delete', + ), + url( + r'^inbox/(?P\d+)/restore/$', docbow_project.docbow.views.restore, - name='inbox-message-restore', kwargs={'outbox': False}), - url(r'^inbox/(?P\d+)/(?P\d+)/.*$', + name='inbox-message-restore', + kwargs={'outbox': False}, + ), + url( + r'^inbox/(?P\d+)/(?P\d+)/.*$', docbow_project.docbow.views.message_attached_file, - name='inbox-message-attached-file'), - url(r'^inbox/(?P\d+)/allfiles/$', + name='inbox-message-attached-file', + ), + url( + r'^inbox/(?P\d+)/allfiles/$', docbow_project.docbow.views.message_all_files, - name='outbox-message-attached-file'), + name='outbox-message-attached-file', + ), url(r'^inbox/csv$', docbow_project.docbow.views.inbox_csv, name='inbox-csv'), url(r'^inbox/ods$', docbow_project.docbow.views.inbox_ods, name='inbox-ods'), - # outbox - url(r'^outbox/$', docbow_project.docbow.views.outbox_view, name='outbox'), url(r'^outbox/trash/$', docbow_project.docbow.views.outbox_trash_view, name='outbox-trash'), - url(r'^outbox/(?P\d+)/$', - docbow_project.docbow.views.message, name='outbox-message', - kwargs={'outbox': True}), - url(r'^outbox/(?P\d+)/delete/$', + url( + r'^outbox/(?P\d+)/$', + docbow_project.docbow.views.message, + name='outbox-message', + kwargs={'outbox': True}, + ), + url( + r'^outbox/(?P\d+)/delete/$', docbow_project.docbow.views.delete, - name='outbox-message-delete', kwargs={'outbox': True}), - url(r'^outbox/(?P\d+)/restore/$', + name='outbox-message-delete', + kwargs={'outbox': True}, + ), + url( + r'^outbox/(?P\d+)/restore/$', docbow_project.docbow.views.restore, - name='outbox-message-restore', kwargs={'outbox': True}), - url(r'^outbox/(?P\d+)/(?P\d+)/.*$', + name='outbox-message-restore', + kwargs={'outbox': True}, + ), + url( + r'^outbox/(?P\d+)/(?P\d+)/.*$', docbow_project.docbow.views.message_attached_file, - name='outbox-message-attached-file', kwargs={'outbox': True}), - url(r'^outbox/(?P\d+)/allfiles/$', + name='outbox-message-attached-file', + kwargs={'outbox': True}, + ), + url( + r'^outbox/(?P\d+)/allfiles/$', docbow_project.docbow.views.message_all_files, - name='outbox-message-attached-file', kwargs={'outbox': True}), + name='outbox-message-attached-file', + kwargs={'outbox': True}, + ), url(r'^outbox/csv$', docbow_project.docbow.views.outbox_csv, name='outbox-csv'), url(r'^outbox/ods$', docbow_project.docbow.views.outbox_ods, name='outbox-ods'), - - - url(r'^send_file/$', - docbow_project.docbow.views.send_file_selector, name='send-file-selector'), - url(r'^send_file/(?P\d+)/$', - docbow_project.docbow.views.send_file, name='send-file'), + url(r'^send_file/$', docbow_project.docbow.views.send_file_selector, name='send-file-selector'), + url(r'^send_file/(?P\d+)/$', docbow_project.docbow.views.send_file, name='send-file'), url(r'^help/$', docbow_project.docbow.views.help, name='help'), url(r'^help/(?P[a-zA-Z0-9-/\.]+)$', docbow_project.docbow.views.help, name='help'), url(r'^contact/$', docbow_project.docbow.views.contact, name='contact'), url(r'^logout/$', docbow_project.docbow.views.logout, name='logout'), url(r'^delegate/$', docbow_project.docbow.views.delegate, name='delegate'), url(r'^upload/', include('docbow_project.docbow.upload_urls')), - url(r'^su/(?P.*)/$', - docbow_project.docbow.views.su, {'redirect_url': '/'}), + url(r'^su/(?P.*)/$', docbow_project.docbow.views.su, {'redirect_url': '/'}), url(r'^mailing-lists/', docbow_project.docbow.views.mailing_lists, name='mailing-lists'), url(r'^search-inbox/', docbow_project.docbow.views.search_inbox, name='search-inbox'), url(r'^search-outbox/', docbow_project.docbow.views.search_outbox, name='search-outbox'), - ] for custom in ('docbow_project.pfwb', 'docbow_project.pw'): diff --git a/docbow_project/docbow/utils.py b/docbow_project/docbow/utils.py index 426fcc8..191b8ca 100644 --- a/docbow_project/docbow/utils.py +++ b/docbow_project/docbow/utils.py @@ -13,19 +13,22 @@ import requests from docbow_project.docbow import app_settings + def truncate_filename(file_name): '''Truncate filename (without extension) and append original extension''' base_name = os.path.basename(file_name) base, ext = os.path.splitext(base_name) - base = base[:app_settings.TRUNCATE_FILENAME] + base = base[: app_settings.TRUNCATE_FILENAME] return base + ext + def clean_ldap_user(user): '''Clean references to module in django-auth-ldap User model, allowing pickling of the instances.''' user.ldap_user = None return user + def match_mime_type(t1, t2): if t1.endswith('/*') or t2.endswith('/*'): t1 = t1.split('/')[0] @@ -80,13 +83,15 @@ def make_password_reset_url(request, user): uid = urlsafe_base64_encode(force_bytes(user.id)) token = default_token_generator.make_token(user) return request.build_absolute_uri( - reverse('auth_password_reset_confirm', - kwargs={'uidb64': uid, 'token': token})) + reverse('auth_password_reset_confirm', kwargs={'uidb64': uid, 'token': token}) + ) + def date_to_aware_datetime(date): return timezone.make_aware( - datetime.datetime(date.year, date.month, date.day), - timezone.get_current_timezone()) + datetime.datetime(date.year, date.month, date.day), timezone.get_current_timezone() + ) + def queryset_fixpoint(initial, qs_function): initial = set(initial) @@ -104,7 +109,7 @@ def a2_wscall(url, method, json=None): 'url': url, 'auth': requests.auth.HTTPBasicAuth( app_settings.settings.AUTHENTIC_USER, app_settings.settings.AUTHENTIC_PASSWORD - ) + ), } if json: kwargs['json'] = json diff --git a/docbow_project/docbow/validators.py b/docbow_project/docbow/validators.py index 99f41ee..f8acc58 100644 --- a/docbow_project/docbow/validators.py +++ b/docbow_project/docbow/validators.py @@ -5,7 +5,7 @@ from django.core.validators import RegexValidator validate_phone = RegexValidator('^\+\d+$') validate_fr_be_phone = RegexValidator('^\+(?:33[67]\d{8}|324\d{8})$') + def phone_normalize(phone_number): '''Remove usual separators from phone number.''' return re.sub('[\.\-\s]', '', phone_number) - diff --git a/docbow_project/docbow/views.py b/docbow_project/docbow/views.py index 56d64c7..d7019cd 100644 --- a/docbow_project/docbow/views.py +++ b/docbow_project/docbow/views.py @@ -13,9 +13,7 @@ import django.contrib.auth as auth from django.shortcuts import render, redirect, get_object_or_404 from django.conf import settings from django.urls import reverse -from django.http import ( - HttpResponse, HttpResponseForbidden, HttpResponseRedirect, Http404, JsonResponse -) +from django.http import HttpResponse, HttpResponseForbidden, HttpResponseRedirect, Http404, JsonResponse from django.db.models.expressions import RawSQL from django.db.models.query import Q from django.contrib.auth.models import User @@ -41,11 +39,23 @@ import watson.search as watson from docbow_project.docbow.forms import ( - FileForm, AnonymousContactForm, ContactForm, ForwardingForm, FilterForm + FileForm, + AnonymousContactForm, + ContactForm, + ForwardingForm, + FilterForm, ) from docbow_project.docbow.models import ( - Mailbox, AttachedFile, SendingLimitation, MailingList, is_guest, Document, FileType, - DeletedDocument, SeenDocument, non_guest_users + Mailbox, + AttachedFile, + SendingLimitation, + MailingList, + is_guest, + Document, + FileType, + DeletedDocument, + SeenDocument, + non_guest_users, ) from docbow_project.docbow.decorators import as_delegate from docbow_project.docbow import tables @@ -58,14 +68,14 @@ from docbow_project.docbow.utils import date_to_aware_datetime gettext_noop = lambda x: x + @login_required def homepage(request): response = redirect('inbox') server_name = request.META.get('SERVER_NAME') if server_name.count('.') >= 2: domain = '.' + '.'.join(server_name.split('.')[-2:]) - response.set_cookie('docbow-user', 'true', domain=domain, - max_age=30*86400) + response.set_cookie('docbow-user', 'true', domain=domain, max_age=30 * 86400) return response @@ -84,12 +94,13 @@ def get_document(request, mailbox_id, outbox): private_filter = Q(private=False) | Q(private=True, mailboxes__owner=request.user) if hasattr(request.user, 'delegate'): private_filter = Q(private=False) - return Document.objects \ - .exclude(deleteddocument__user=request.user, deleteddocument__soft_delete=False) \ - .filter(id=mailbox_id, mailboxes__in=mailboxes) \ - .filter(private_filter) \ - .distinct() \ + return ( + Document.objects.exclude(deleteddocument__user=request.user, deleteddocument__soft_delete=False) + .filter(id=mailbox_id, mailboxes__in=mailboxes) + .filter(private_filter) + .distinct() .get() + ) class Row(object): @@ -100,6 +111,7 @@ class Row(object): def user_mailing_list_names(user): return user.mailing_lists.values_list('name', flat=True) + def recursive_lists(lists): lists = set(lists) while True: @@ -109,32 +121,32 @@ def recursive_lists(lists): break return lists + def get_file_form_kwargs(request): user = request.user if is_guest(user): user = user.delegations_by.get().by delegators = [] else: - delegators = non_guest_users().filter( - Q(id=user.id) | - Q(delegations_to__to=user)) \ - .distinct() + delegators = non_guest_users().filter(Q(id=user.id) | Q(delegations_to__to=user)).distinct() user_lists = MailingList.objects.is_member_of(user) - own_limitations = MailingList.objects \ - .filter(is_active=True) \ - .filter(lists_limitation__mailing_list__in=user_lists) \ - .distinct() \ - .order_by('name') + own_limitations = ( + MailingList.objects.filter(is_active=True) + .filter(lists_limitation__mailing_list__in=user_lists) + .distinct() + .order_by('name') + ) if not own_limitations: return {} kwargs = {} user_lists = MailingList.objects.are_member_of([user] + list(delegators)) if SendingLimitation.objects.filter(mailing_list__in=user_lists): - lists = MailingList.objects \ - .filter(is_active=True) \ - .filter(lists_limitation__mailing_list__in=user_lists) \ - .distinct() \ - .order_by('name') + lists = ( + MailingList.objects.filter(is_active=True) + .filter(lists_limitation__mailing_list__in=user_lists) + .distinct() + .order_by('name') + ) lists = recursive_lists(lists) users = non_guest_users().filter(mailing_lists__in=lists).distinct() if lists: @@ -149,24 +161,23 @@ def get_filetype_limitation(user): user = user.delegations_by.get().by delegators = [] else: - delegators = non_guest_users().filter( - Q(id=user.id) | - Q(delegations_to__to=user)) \ - .distinct() + delegators = non_guest_users().filter(Q(id=user.id) | Q(delegations_to__to=user)).distinct() # if user has basically no limitation, do not limit him user_lists = MailingList.objects.is_member_of(user) - own_limitations = FileType.objects \ - .filter(is_active=True, filetype_limitation__mailing_list__in=user_lists) \ - .distinct() \ - .order_by('name') + own_limitations = ( + FileType.objects.filter(is_active=True, filetype_limitation__mailing_list__in=user_lists) + .distinct() + .order_by('name') + ) if not own_limitations.exists(): return FileType.objects.none() if delegators: user_lists = MailingList.objects.are_member_of([user] + list(delegators)) - return FileType.objects \ - .filter(is_active=True, filetype_limitation__mailing_list__in=user_lists) \ - .distinct() \ - .order_by('name') + return ( + FileType.objects.filter(is_active=True, filetype_limitation__mailing_list__in=user_lists) + .distinct() + .order_by('name') + ) else: return own_limitations @@ -179,18 +190,18 @@ def send_file(request, file_type_id): try: reply_to = get_document(request, request.GET['reply_to'], False) except Document.DoesNotExist: - raise Http404 + raise Http404 except KeyError: reply_to = None if hasattr(request.user, 'delegate'): delegators = User.objects.none() else: - delegators = User.objects.filter( - Q(id=request.user.id) | - Q(delegations_to__to=request.user)) \ - .filter(is_active=True) \ - .order_by('last_name', 'first_name', 'username') \ - .distinct() + delegators = ( + User.objects.filter(Q(id=request.user.id) | Q(delegations_to__to=request.user)) + .filter(is_active=True) + .order_by('last_name', 'first_name', 'username') + .distinct() + ) real_user = getattr(request.user, 'delegate', request.user) limitations = get_filetype_limitation(request.user) if limitations: @@ -199,11 +210,16 @@ def send_file(request, file_type_id): if request.method == 'POST': if 'send' not in request.POST: return redirect('outbox') - form = FileForm(request.POST, request.FILES, - default_sender=request.user, user=real_user, - delegations=delegators, reply_to=reply_to, - file_type=file_type, - **get_file_form_kwargs(request)) + form = FileForm( + request.POST, + request.FILES, + default_sender=request.user, + user=real_user, + delegations=delegators, + reply_to=reply_to, + file_type=file_type, + **get_file_form_kwargs(request), + ) try: if form.is_valid(): new_send = form.save(commit=False) @@ -221,37 +237,52 @@ def send_file(request, file_type_id): new_send.to_user.set(to_user) new_send.to_list.set(to_list) recipients_count = new_send.post() - request.record('create-document', 'sent document {document} ' - 'delivered to {recipients_count}', document=new_send, - recipients_count=recipients_count) + request.record( + 'create-document', + 'sent document {document} ' 'delivered to {recipients_count}', + document=new_send, + recipients_count=recipients_count, + ) return redirect('outbox') except Exception: import logging logging.getLogger(__name__).exception('unable to create a new document') - form._errors.setdefault(NON_FIELD_ERRORS, form.error_class()) \ - .append(_('An internal error occured, administrators ' + form._errors.setdefault(NON_FIELD_ERRORS, form.error_class()).append( + _( + 'An internal error occured, administrators ' 'have been notified; sending seems blocked at the moment. You should ' 'try agrain later. If it still does not work then, contact ' - 'your administrator.')) + 'your administrator.' + ) + ) else: - form = FileForm(default_sender=request.user, user=real_user, - delegations=delegators, reply_to=reply_to, - file_type=file_type, - **get_file_form_kwargs(request)) + form = FileForm( + default_sender=request.user, + user=real_user, + delegations=delegators, + reply_to=reply_to, + file_type=file_type, + **get_file_form_kwargs(request), + ) form_action = '' if reply_to: form_action = '?reply_to=%s' % reply_to.id return render( - request, 'docbow/send_file.html', + request, + 'docbow/send_file.html', { - 'form': form, 'view_name': 'send-file', 'reply_to': reply_to, - 'url': request.GET.get('url'), 'form_action': form_action - } + 'form': form, + 'view_name': 'send-file', + 'reply_to': reply_to, + 'url': request.GET.get('url'), + 'form_action': form_action, + }, ) + @never_cache def upload(request, attached_file): response = HttpResponse(attached_file.content.chunks(), content_type='application/octet-stream') @@ -277,10 +308,13 @@ def message_attached_file(request, mailbox_id, attached_file, outbox=False): before, otherwise return 404. ''' document = get_document_and_mark_seen(request, mailbox_id, outbox) - attached_file = get_object_or_404(AttachedFile, document=document, - pk=attached_file) - request.record('download', 'download attached file {attached_file} of document {document}', - attached_file=attached_file, document=document) + attached_file = get_object_or_404(AttachedFile, document=document, pk=attached_file) + request.record( + 'download', + 'download attached file {attached_file} of document {document}', + attached_file=attached_file, + document=document, + ) return upload(request, attached_file) @@ -323,11 +357,9 @@ def message(request, mailbox_id, outbox=False): document = get_document(request, mailbox_id, outbox) except Document.DoesNotExist: if Document.objects.filter(pk=mailbox_id): - messages.warning(request, - _('You cannot see this document as you are not the recipient')) + messages.warning(request, _('You cannot see this document as you are not the recipient')) else: - messages.warning(request, - _('Document not found')) + messages.warning(request, _('Document not found')) if outbox: return redirect('outbox') else: @@ -337,31 +369,32 @@ def message(request, mailbox_id, outbox=False): else: back_pair = (reverse('inbox'), gettext_noop('back to inbox')) back = 'outbox' if outbox else 'inbox' - ctx = { 'outbox': outbox, 'document': document, - 'view_name': back, 'back': back_pair } + ctx = {'outbox': outbox, 'document': document, 'view_name': back, 'back': back_pair} if not outbox: limitations = get_filetype_limitation(request.user) if not limitations or limitations.filter(id=document.filetype.id).exists(): if request.method == 'POST': - form = ForwardingForm(request.POST, user=request.user, - **get_file_form_kwargs(request)) + form = ForwardingForm(request.POST, user=request.user, **get_file_form_kwargs(request)) if form.is_valid(): users, lists = form_to_user_and_list(form) recipients_count, forwarded_document = document.forward( - form.cleaned_data['sender'], lists, users) - request.record('forward-document', 'forwarded document ' - '{document} as new document {new_document}', - document=forwarded_document.from_document, - new_document=forwarded_document.to_document) - msg = ungettext('Document forwarded to {recipients_count} ' - 'recipient.', 'Document forwarded to {recipients_count} ' - 'recipients.', recipients_count) \ - .format(recipients_count=recipients_count) + form.cleaned_data['sender'], lists, users + ) + request.record( + 'forward-document', + 'forwarded document ' '{document} as new document {new_document}', + document=forwarded_document.from_document, + new_document=forwarded_document.to_document, + ) + msg = ungettext( + 'Document forwarded to {recipients_count} ' 'recipient.', + 'Document forwarded to {recipients_count} ' 'recipients.', + recipients_count, + ).format(recipients_count=recipients_count) messages.info(request, msg) return redirect('inbox-message', mailbox_id=document.pk) else: - form = ForwardingForm(user=request.user, - **get_file_form_kwargs(request)) + form = ForwardingForm(user=request.user, **get_file_form_kwargs(request)) ctx['form'] = form ctx['attached_files'] = attached_files = [] for attached_file in document.attached_files.order_by('kind__position', 'kind__name', 'id'): @@ -369,11 +402,11 @@ def message(request, mailbox_id, outbox=False): attached_files[-1][1].append(attached_file) else: attached_files.append((attached_file.kind, [attached_file])) - request.record('message-view', 'looked at document {document}', - document=document) + request.record('message-view', 'looked at document {document}', document=document) ctx['related_users'] = get_related_users(request) return render(request, 'docbow/message.html', ctx) + def get_help_content(pagename): filepath = os.path.join(settings.HELP_DIR, pagename) parsed_doc = BeautifulSoup(open(filepath).read()) @@ -391,12 +424,13 @@ def get_help_content(pagename): t.name = 'h3' return force_text(page) + @login_required def help(request, pagename='index.html'): if pagename.endswith('.html'): - return render(request, 'docbow/help.html', { - 'view_name': 'help', - 'content': get_help_content(pagename) }) + return render( + request, 'docbow/help.html', {'view_name': 'help', 'content': get_help_content(pagename)} + ) else: filepath = os.path.join(settings.HELP_DIR, pagename) if not os.path.abspath(filepath).startswith(settings.HELP_DIR): @@ -405,16 +439,15 @@ def help(request, pagename='index.html'): response['Content-Type'] = 'image/png' return response -def contact(request, template='docbow/contact.html', - form_class=AnonymousContactForm): + +def contact(request, template='docbow/contact.html', form_class=AnonymousContactForm): user = request.user if user.is_authenticated: template = 'docbow/contact_user.html' form_class = ContactForm contacts = User.objects.filter(groups__name__in=settings.CONTACT_GROUPS) if not bool(contacts): - msg = N_('unable to send the contact mail because there is no ' - 'administrator group to receive them') + msg = N_('unable to send the contact mail because there is no ' 'administrator group to receive them') request.error_record('error', msg) messages.error(request, _(msg)) return redirect('inbox') @@ -422,13 +455,15 @@ def contact(request, template='docbow/contact.html', form = form_class(request.POST) if form.is_valid(): cleaned_data = form.cleaned_data - to = [ contact.email for contact in contacts ] + to = [contact.email for contact in contacts] subject = settings.CONTACT_SUBJECT_PREFIX + cleaned_data['subject'] message = cleaned_data['message'] if user.is_authenticated: reply_to = user.email - body = _('Message from %(name)s <%(email)s>') % { 'name': - user.get_full_name(), 'email': reply_to } + '\n\n%s' % message + body = ( + _('Message from %(name)s <%(email)s>') % {'name': user.get_full_name(), 'email': reply_to} + + '\n\n%s' % message + ) else: reply_to = cleaned_data['email'] body = _('Message from %(name)s <%(email)s>') % cleaned_data @@ -436,36 +471,43 @@ def contact(request, template='docbow/contact.html', body += _('\nPhone number: %s') % cleaned_data['phone_number'] body += '\n\n%s' % message try: - EmailMessage(to=to, subject=subject, body=body, - headers={'Reply-To': reply_to}).send() + EmailMessage(to=to, subject=subject, body=body, headers={'Reply-To': reply_to}).send() messages.info(request, _('Your message was sent to %d administrators') % len(to)) - request.record('contact', 'sent mail to administrators with ' - 'subject "{subject}" and message "{message}", reply should be sent to email ' - '{reply_to} or phone number "{phone}"', - subject=subject, - message=message, - reply_to=reply_to, - phone=cleaned_data.get('phone_number')) + request.record( + 'contact', + 'sent mail to administrators with ' + 'subject "{subject}" and message "{message}", reply should be sent to email ' + '{reply_to} or phone number "{phone}"', + subject=subject, + message=message, + reply_to=reply_to, + phone=cleaned_data.get('phone_number'), + ) except (smtplib.SMTPException, socket.error): import logging logging.getLogger(__name__).exception('unable to send mail to administrators') - request.error_record('error', 'unable to send mail to administrators with ' - 'subject "{subject}" and message "{message}", reply should be sent to email ' - '{reply_to} or phone number "{phone}"', - subject=subject, - message=message, - reply_to=reply_to, - phone=cleaned_data.get('phone_number')) + request.error_record( + 'error', + 'unable to send mail to administrators with ' + 'subject "{subject}" and message "{message}", reply should be sent to email ' + '{reply_to} or phone number "{phone}"', + subject=subject, + message=message, + reply_to=reply_to, + phone=cleaned_data.get('phone_number'), + ) return redirect('inbox') else: form = form_class() - return render(request, template, { 'form': form, 'view_name': 'contact' }) + return render(request, template, {'form': form, 'view_name': 'contact'}) + def logout(request): auth.logout(request) return redirect('inbox') + @login_required @never_cache def delete(request, mailbox_id, outbox=False): @@ -473,24 +515,22 @@ def delete(request, mailbox_id, outbox=False): page = request.GET.get('page', 1) back = 'outbox' if outbox else 'inbox' viewname = back + '-message-delete' - back_pair = ('%s?page=%s' % (reverse(back), page), - gettext_noop('back to %s' % back)) + back_pair = ('%s?page=%s' % (reverse(back), page), gettext_noop('back to %s' % back)) try: document = get_document(request, mailbox_id, outbox) except Document.DoesNotExist: return redirect(back_pair[0]) if request.method == 'GET': - return render(request, 'docbow/delete.html', { 'document': document, 'back': back_pair, 'view_name': viewname }) + return render( + request, 'docbow/delete.html', {'document': document, 'back': back_pair, 'view_name': viewname} + ) else: try: DeletedDocument.objects.get(user=request.user, document=document) except DeletedDocument.DoesNotExist: DeletedDocument.objects.create( - user=request.user, - document=document, - soft_delete=True, - soft_delete_date=now() + user=request.user, document=document, soft_delete=True, soft_delete_date=now() ) request.record('delete-document', 'marked document {document} as deleted', document=document) @@ -504,9 +544,7 @@ def inbox_by_document(request, document_id): @require_http_methods(["POST"]) def restore(request, doc_id, outbox=False): try: - deleted_doc = DeletedDocument.objects.get( - user=request.user, document__pk=doc_id, soft_delete=True - ) + deleted_doc = DeletedDocument.objects.get(user=request.user, document__pk=doc_id, soft_delete=True) deleted_doc.delete() except DeletedDocument.DoesNotExist: pass @@ -531,7 +569,9 @@ def su(request, username, redirect_url='/'): real_user = User.objects.get(username=real_username) pair_id = '%s,%s' % (real_user.id, su_user.id) request.session[SESSION_KEY] = pair_id - request.session[BACKEND_SESSION_KEY] = 'docbow_project.docbow.auth_backend.DelegationAuthBackend' + request.session[ + BACKEND_SESSION_KEY + ] = 'docbow_project.docbow.auth_backend.DelegationAuthBackend' request.session['has_superuser_power'] = True else: request.session[SESSION_KEY] = su_user.id @@ -541,12 +581,17 @@ def su(request, username, redirect_url='/'): else: return http.HttpResponseRedirect('/') + @login_required @never_cache def mailing_lists(request, template='docbow/mailing-lists.html'): - mailing_lists = MailingList.objects.active().filter(Q(members__isnull=False) | - Q(mailing_list_members__isnull=False)).distinct().order_by('name') - return render(request, template, { 'mailing_lists': mailing_lists }) + mailing_lists = ( + MailingList.objects.active() + .filter(Q(members__isnull=False) | Q(mailing_list_members__isnull=False)) + .distinct() + .order_by('name') + ) + return render(request, template, {'mailing_lists': mailing_lists}) def robots(request): @@ -567,8 +612,7 @@ class DateFilterMixinView(object): if 'clear' in self.request.GET: form = FilterForm(request=self.request, outbox=self.outbox) else: - form = FilterForm(data=self.request.GET, - request=self.request, outbox=self.outbox) + form = FilterForm(data=self.request.GET, request=self.request, outbox=self.outbox) self._form = form return self._form @@ -592,7 +636,9 @@ class DateFilterMixinView(object): not_after = date_to_aware_datetime(not_after) qs = qs.filter(date__lt=not_after) if search_terms: - objects_ids = watson.search(search_terms, models=(Document,)).values_list('object_id_int', flat=True) + objects_ids = watson.search(search_terms, models=(Document,)).values_list( + 'object_id_int', flat=True + ) qs = qs.filter(id__in=objects_ids) return qs @@ -605,6 +651,7 @@ class ExtraContextMixin(object): context.update(self.extra_ctx) return context + def get_related_users(request): '''Compute the list of users for which we can see the mailboxes, and cache it in the session. @@ -618,6 +665,7 @@ def get_related_users(request): request.session['related_users'] = [user.pk for user in users] return User.objects.filter(pk__in=request.session['related_users']) + class MailboxQuerysetMixin(object): def get_queryset(self): qs = super(MailboxQuerysetMixin, self).get_queryset() @@ -655,7 +703,7 @@ class MailboxView(ExtraContextMixin, MailboxQuerysetMixin, tables_views.SingleTa model = Document table_class = tables.MailboxTable table_pagination = { - 'per_page': app_settings.DOCBOW_MAILBOX_PER_PAGE, + 'per_page': app_settings.DOCBOW_MAILBOX_PER_PAGE, } def get_context_data(self): @@ -668,7 +716,7 @@ class TrashMailboxView(ExtraContextMixin, TrashMailboxQuerysetMixin, tables_view model = Document table_class = tables.MailboxTable table_pagination = { - 'per_page': app_settings.DOCBOW_MAILBOX_PER_PAGE, + 'per_page': app_settings.DOCBOW_MAILBOX_PER_PAGE, } @@ -677,7 +725,7 @@ class CSVMultipleObjectMixin(object): filename = '' def get_header(self): - return [ column['caption'] for column in self.mapping ] + return [column['caption'] for column in self.mapping] def get_cell(self, mailbox, attribute): value = operator.attrgetter(attribute)(mailbox) @@ -708,6 +756,7 @@ class ODSMultipleObjectMixin(CSVMultipleObjectMixin): def get(self, request, *args, **kwargs): from . import ods + response = HttpResponse(content_type='application/vnd.oasis.opendocument.spreadsheet') response['Content-Disposition'] = 'attachment; filename="%s"' % self.filename workbook = ods.Workbook(encoding='utf-8') @@ -720,18 +769,21 @@ class ODSMultipleObjectMixin(CSVMultipleObjectMixin): return response -class CSVMailboxView(CSVMultipleObjectMixin, ExtraContextMixin, - MailboxQuerysetMixin, MultipleObjectMixin, - tables_views.SingleTableMixin, View): +class CSVMailboxView( + CSVMultipleObjectMixin, + ExtraContextMixin, + MailboxQuerysetMixin, + MultipleObjectMixin, + tables_views.SingleTableMixin, + View, +): model = Document - @property def filename(self): return '{prefix}-{user}-{date}.csv'.format( - prefix=self.filename_prefix, - user=self.request.user, - date=datetime.date.today()) + prefix=self.filename_prefix, user=self.request.user, date=datetime.date.today() + ) def get_header(self): table = self.get_table() @@ -752,9 +804,8 @@ class ODSMailboxView(ODSMultipleObjectMixin, CSVMailboxView): @property def filename(self): return '{prefix}-{user}-{date}.ods'.format( - prefix=self.filename_prefix, - user=self.request.user, - date=datetime.date.today()) + prefix=self.filename_prefix, user=self.request.user, date=datetime.date.today() + ) class CSVInboxView(CSVMailboxView): @@ -788,6 +839,7 @@ class ODSOutboxView(ODSMailboxView, CSVOutboxView): outbox_ods = ODSOutboxView.as_view() + class DeleteDocumentsView(object): def post(self, request, *args, **kwargs): documents = None @@ -804,22 +856,21 @@ class DeleteDocumentsView(object): documents = documents.exclude(deleteddocument__user=request.user) dd = [ DeletedDocument( - user=request.user, - document=document, - soft_delete=True, - soft_delete_date=now() - ) for document in documents + user=request.user, document=document, soft_delete=True, soft_delete_date=now() + ) + for document in documents ] DeletedDocument.objects.bulk_create(dd) return HttpResponseRedirect(request.build_absolute_uri()) + class InboxView(DeleteDocumentsView, DateFilterMixinView, MailboxView): http_method_names = ['get', 'post'] outbox = False table_class = tables.InboxTable template_name = 'docbow/inbox_list.html' extra_ctx = { - 'view_name': 'inbox', + 'view_name': 'inbox', } def get_context_data(self): @@ -836,7 +887,7 @@ class InboxTrashView(TrashMailboxView): table_class = tables.InboxTrashTable template_name = 'docbow/inbox_trash_list.html' extra_ctx = { - 'view_name': 'inbox_trash', + 'view_name': 'inbox_trash', } @@ -848,7 +899,7 @@ class OutboxView(DeleteDocumentsView, DateFilterMixinView, MailboxView): table_class = tables.OutboxTable template_name = 'docbow/outbox_list.html' extra_ctx = { - 'view_name': 'outbox', + 'view_name': 'outbox', } def get_context_data(self): @@ -865,15 +916,13 @@ class OutboxTrashView(TrashMailboxView): table_class = tables.OutboxTrashTable template_name = 'docbow/outbox_trash_list.html' extra_ctx = { - 'view_name': 'outbox_trash', + 'view_name': 'outbox_trash', } outbox_trash_view = login_required(OutboxTrashView.as_view()) - - class SendFileSelectorView(ListView): template_name = 'docbow/send_file_selector.html' model = FileType @@ -892,7 +941,9 @@ send_file_selector = login_required(SendFileSelectorView.as_view()) delegate = RedirectView.as_view(url='/profile/', permanent=False) -password_change = sensitive_post_parameters()(never_cache(login_required(as_delegate(profile_views.PasswordChangeView.as_view())))) +password_change = sensitive_post_parameters()( + never_cache(login_required(as_delegate(profile_views.PasswordChangeView.as_view()))) +) profile = login_required(as_delegate(profile_views.FullProfileView.as_view())) @@ -901,16 +952,11 @@ profile = login_required(as_delegate(profile_views.FullProfileView.as_view())) def search_terms(request, outbox=True, limit_choices=10): q = request.GET.get('term', '') - documents = sql.get_documents( - Document.objects.all(), - get_related_users(request), request.user, outbox, - ) + documents = sql.get_documents(Document.objects.all(), get_related_users(request), request.user, outbox,) search = watson.search(q, models=(documents,)) choices = [] for match in search: - match_words = [ - word for word in match.description.split() if word.lower().startswith(q.lower()) - ] + match_words = [word for word in match.description.split() if word.lower().startswith(q.lower())] for word in match_words: if word in choices: continue diff --git a/docbow_project/docbow/widgets.py b/docbow_project/docbow/widgets.py index df8d092..404e2d1 100644 --- a/docbow_project/docbow/widgets.py +++ b/docbow_project/docbow/widgets.py @@ -1,8 +1,7 @@ import uuid import re -from django.forms import Textarea, MultiWidget, Select, \ - HiddenInput, FileInput, SelectMultiple +from django.forms import Textarea, MultiWidget, Select, HiddenInput, FileInput, SelectMultiple from django.utils.safestring import mark_safe from django.conf import settings from django.template.loader import render_to_string @@ -27,8 +26,7 @@ class TextInpuWithPredefinedValues(MultiWidget): ''' def __init__(self, attrs=None, choices=[]): - widget_list = (Select(attrs=attrs, choices=choices), - Textarea(attrs=attrs)) + widget_list = (Select(attrs=attrs, choices=choices), Textarea(attrs=attrs)) super(TextInpuWithPredefinedValues, self).__init__(widget_list, attrs) def decompress(self, value): @@ -42,14 +40,15 @@ class TextInpuWithPredefinedValues(MultiWidget): return select def render(self, name, value, attrs=None, renderer=None): - output = super(TextInpuWithPredefinedValues, self).render(name, value, - attrs) - return output + mark_safe(self.CLIENT_CODE % { 'name': name }) + output = super(TextInpuWithPredefinedValues, self).render(name, value, attrs) + return output + mark_safe(self.CLIENT_CODE % {'name': name}) + class MultiFileInput(FileInput): ''' FileInput field supporting the multiple attribute ''' + def __init__(self, attrs=None): super(MultiFileInput, self).__init__(attrs) self.attrs['multiple'] = 'true' @@ -60,20 +59,22 @@ class MultiFileInput(FileInput): else: return [] + class JqueryFileUploadFileInput(MultiFileInput): template_name = 'docbow/upload-multiwidget.html' class Media: - js = ('jquery-ui/js/jquery-ui-1.8.4.min.js', - 'jquery-plugin/js/jquery.tmpl.min-beta1.js', - 'jquery-plugin/js/jquery.iframe-transport.js', - 'jquery-plugin/js/jquery.fileupload.js', - 'jquery-plugin/js/jquery.fileupload-ui.js') - css = {'all': ('jquery-ui/css/jquery-ui-1.8.4.css', - 'jquery-plugin/css/jquery.fileupload-ui.css',)} + js = ( + 'jquery-ui/js/jquery-ui-1.8.4.min.js', + 'jquery-plugin/js/jquery.tmpl.min-beta1.js', + 'jquery-plugin/js/jquery.iframe-transport.js', + 'jquery-plugin/js/jquery.fileupload.js', + 'jquery-plugin/js/jquery.fileupload-ui.js', + ) + css = {'all': ('jquery-ui/css/jquery-ui-1.8.4.css', 'jquery-plugin/css/jquery.fileupload-ui.css',)} - def __init__(self, extensions=[], attached_file_kind=None, *args,**kwargs): + def __init__(self, extensions=[], attached_file_kind=None, *args, **kwargs): self.extensions = extensions self.attached_file_kind = attached_file_kind super(JqueryFileUploadFileInput, self).__init__(*args, **kwargs) @@ -88,7 +89,7 @@ class JqueryFileUploadFileInput(MultiFileInput): 'attached_file_kind': self.attached_file_kind, 'files': self.files, 'name': name, - 'STATIC_URL': settings.STATIC_URL + 'STATIC_URL': settings.STATIC_URL, } def get_context(self, name, value, attrs): @@ -111,14 +112,18 @@ class JqueryFileUploadInput(MultiWidget): upload_id = None template_name = 'docbow/multiwidget.html' - - def __init__(self, attrs=None, choices=[], max_filename_length=None, extensions=[], attached_file_kind=None): + def __init__( + self, attrs=None, choices=[], max_filename_length=None, extensions=[], attached_file_kind=None + ): self.extensions = extensions self.max_filename_length = max_filename_length self.attached_file_kind = attached_file_kind - widget_list = (HiddenInput(attrs=attrs), - JqueryFileUploadFileInput(attrs=attrs, extensions=extensions, - attached_file_kind=attached_file_kind)) + widget_list = ( + HiddenInput(attrs=attrs), + JqueryFileUploadFileInput( + attrs=attrs, extensions=extensions, attached_file_kind=attached_file_kind + ), + ) super(JqueryFileUploadInput, self).__init__(widget_list, attrs) def decompress(self, value): @@ -158,11 +163,12 @@ class JqueryFileUploadInput(MultiWidget): url += 'max_filename_length=%d' % self.max_filename_length self.widgets[1].url = url self.widgets[1].files = '/upload/%s/' % get_files_for_id(self.upload_id) - output = super(JqueryFileUploadInput, self).render(name, value, - attrs) + output = super(JqueryFileUploadInput, self).render(name, value, attrs) fileinput_id = '%s_%s' % (attrs['id'], '1') - return output + mark_safe(self.CLIENT_CODE % { - 'upload_id': self.upload_id, 'fileinput_id': fileinput_id }) + return output + mark_safe( + self.CLIENT_CODE % {'upload_id': self.upload_id, 'fileinput_id': fileinput_id} + ) + class ForcedValueWidget(SelectMultiple): def __init__(self, attrs=None, format=None, value=None, display_value=""): @@ -174,7 +180,9 @@ class ForcedValueWidget(SelectMultiple): return self.value def render(self, name, value, attrs=None, renderer=None): - return mark_safe(u'
%s
' % self.display_value) + return mark_safe( + u'
%s
' % self.display_value + ) class FilteredSelectMultiple(SelectMultiple): @@ -185,12 +193,15 @@ class FilteredSelectMultiple(SelectMultiple): Note that the resulting JavaScript assumes that the jsi18n catalog has been loaded in the page """ + class Media: - js = ("docbow/filter-widget/js/core.js", - "docbow/filter-widget/js/SelectBox.js", - "docbow/filter-widget/js/SelectFilter2.js", - "js/i18n.js") - css = { 'all': ('docbow/filter-widget/css/filter-widget.css',)} + js = ( + "docbow/filter-widget/js/core.js", + "docbow/filter-widget/js/SelectBox.js", + "docbow/filter-widget/js/SelectFilter2.js", + "js/i18n.js", + ) + css = {'all': ('docbow/filter-widget/css/filter-widget.css',)} def __init__(self, verbose_name, is_stacked, attrs=None, choices=()): self.verbose_name = verbose_name @@ -198,9 +209,11 @@ class FilteredSelectMultiple(SelectMultiple): super(FilteredSelectMultiple, self).__init__(attrs, choices) def render(self, name, value, attrs=None, *args, **kwargs): - if attrs is None: attrs = {} + if attrs is None: + attrs = {} attrs['class'] = 'selectfilter' - if self.is_stacked: attrs['class'] += 'stacked' + if self.is_stacked: + attrs['class'] += 'stacked' # disable html5 validation if 'required' in attrs: @@ -210,8 +223,10 @@ class FilteredSelectMultiple(SelectMultiple): output.append(u'\n' % \ - (name, self.verbose_name.replace('"', '\\"'), int(self.is_stacked), settings.STATIC_URL)) + output.append( + u'SelectFilter.init("id_%s", "%s", %s, "%s"); });\n' + % (name, self.verbose_name.replace('"', '\\"'), int(self.is_stacked), settings.STATIC_URL) + ) return mark_safe(u''.join(output)) diff --git a/docbow_project/humantime/templatetags/humantime.py b/docbow_project/humantime/templatetags/humantime.py index 711158a..79786d5 100644 --- a/docbow_project/humantime/templatetags/humantime.py +++ b/docbow_project/humantime/templatetags/humantime.py @@ -8,18 +8,19 @@ from .. import utils register = template.Library() + @register.filter def humandate(dt): full_dt = date(dt, 'SHORT_DATE_FORMAT') - s = u'{1}'.format( - escape(full_dt), escape(utils.datetime2human(dt))) + s = u'{1}'.format(escape(full_dt), escape(utils.datetime2human(dt))) return mark_safe(s) + @register.filter def humantime(dt): dt = localtime(dt) full_dt = date(dt, 'SHORT_DATETIME_FORMAT') s = u'{1}'.format( - escape(full_dt), escape(utils.datetime2human(dt, include_time=True))) + escape(full_dt), escape(utils.datetime2human(dt, include_time=True)) + ) return mark_safe(s) - diff --git a/docbow_project/humantime/utils.py b/docbow_project/humantime/utils.py index fcc7583..2b60e7e 100644 --- a/docbow_project/humantime/utils.py +++ b/docbow_project/humantime/utils.py @@ -4,14 +4,14 @@ from django.utils.translation import pgettext from django.utils.timezone import localtime, get_default_timezone from django.template.defaultfilters import date + def datetime2human(dt, include_time=False, days_limit=7): '''Format a datetime object for human consumption''' if isinstance(dt, datetime.datetime): dt = localtime(dt) time = dt.strftime('%H:%M') else: - dt = datetime.datetime(year=dt.year, month=dt.month, day=dt.day, - tzinfo=get_default_timezone()) + dt = datetime.datetime(year=dt.year, month=dt.month, day=dt.day, tzinfo=get_default_timezone()) dt = localtime(dt) include_time = False today = datetime.date.today() diff --git a/docbow_project/pfwb/admin.py b/docbow_project/pfwb/admin.py index 83544ed..da88cfd 100644 --- a/docbow_project/pfwb/admin.py +++ b/docbow_project/pfwb/admin.py @@ -8,7 +8,8 @@ class PloneFileTypeInlineAdmin(admin.StackedInline): model = models.PloneFileType extra = 0 + if getattr(docbow_admin.FileTypeAdmin, 'inlines'): - docbow_admin.FileTypeAdmin.inlines += [ PloneFileTypeInlineAdmin ] + docbow_admin.FileTypeAdmin.inlines += [PloneFileTypeInlineAdmin] else: - docbow_admin.FileTypeAdmin.inlines = [ PloneFileTypeInlineAdmin ] + docbow_admin.FileTypeAdmin.inlines = [PloneFileTypeInlineAdmin] diff --git a/docbow_project/pfwb/app_settings.py b/docbow_project/pfwb/app_settings.py index d1d755d..5e4549c 100644 --- a/docbow_project/pfwb/app_settings.py +++ b/docbow_project/pfwb/app_settings.py @@ -3,22 +3,23 @@ class AppSettings(object): '''Thanks django-allauth''' + __DEFAULTS = dict( # directory where ged files are stored - PFWB_GED_DIRECTORY = None, + PFWB_GED_DIRECTORY=None, # default type id for documents received by SMTP when given type does # not exist - PFWB_SENDMAIL_DEFAULT_TYPE_ID = None, + PFWB_SENDMAIL_DEFAULT_TYPE_ID=None, # default type name if default type id does not exist - PFWB_SENDMAIL_DEFAULT_TYPE_NAME = 'Divers', + PFWB_SENDMAIL_DEFAULT_TYPE_NAME='Divers', # sender email for document received from tabellio expedition by SMTP - PFWB_SENDMAIL_TABELLIO_EXPEDITION_EMAIL = 'commande.documents@pfwb.be', + PFWB_SENDMAIL_TABELLIO_EXPEDITION_EMAIL='commande.documents@pfwb.be', # user id of senders for document received from tabellio expedition by SMTP - PFWB_SENDMAIL_TABELLIO_EXPEDITION_USER_ID = None, + PFWB_SENDMAIL_TABELLIO_EXPEDITION_USER_ID=None, # sender email for document received by SMTP (generic code) - PFWB_SENDMAIL_ATTACHED_FILE_EMAIL = None, + PFWB_SENDMAIL_ATTACHED_FILE_EMAIL=None, # user id of senders for document received by SMTP (generic code) - PFWB_SENDMAIL_ATTACHED_FILE_USER_ID = None, + PFWB_SENDMAIL_ATTACHED_FILE_USER_ID=None, ) def __init__(self, prefix): @@ -27,22 +28,24 @@ class AppSettings(object): @property def settings(self): from django.conf import settings + return settings def __getattr__(self, key): if key in self.__DEFAULTS: - return getattr(self.settings, - self.prefix+key, self.__DEFAULTS[key]) + return getattr(self.settings, self.prefix + key, self.__DEFAULTS[key]) else: from django.core.exceptions import ImproperlyConfigured + try: - return getattr(self.settings, self.prefix+key) + return getattr(self.settings, self.prefix + key) except AttributeError: - raise ImproperlyConfigured('settings %s is missing' % self.prefix+key) + raise ImproperlyConfigured('settings %s is missing' % self.prefix + key) app_settings = AppSettings('DOCBOW_') app_settings.__name__ = __name__ app_settings.__file__ = __file__ import sys + sys.modules[__name__] = app_settings diff --git a/docbow_project/pfwb/management/commands/archive2.py b/docbow_project/pfwb/management/commands/archive2.py index 1dc0cdf..56cc438 100644 --- a/docbow_project/pfwb/management/commands/archive2.py +++ b/docbow_project/pfwb/management/commands/archive2.py @@ -21,11 +21,13 @@ def batch(qs, window): return after = qs[0].id while qs.filter(id__gte=after).exists(): - yield qs.filter(id__gte=after, id__lt=after+window) + yield qs.filter(id__gte=after, id__lt=after + window) after += window + window = 1000 + class Command(BaseCommand): args = ' ' help = 'Archive documents and journal' @@ -46,39 +48,43 @@ class Command(BaseCommand): json_path = os.path.join(doc_path, 'document.json') with open(json_path, 'w') as document_json: document_json.write( - serializers.serialize('json', [document], - indent=2, use_natural_foreign_keys=True)) + serializers.serialize('json', [document], indent=2, use_natural_foreign_keys=True) + ) for attached_file in document.attached_files.all(): file_path = os.path.join(doc_path, os.path.basename(attached_file.content.name)) with open(file_path, 'wb') as data_file: data_file.write(attached_file.content.read()) attached_file.content.close() - attached_file_path = os.path.join(doc_path, - 'attached_file_%s.json' % attached_file.id) + attached_file_path = os.path.join(doc_path, 'attached_file_%s.json' % attached_file.id) with open(attached_file_path, 'w') as json_file: - json_file.write(serializers.serialize('json', - [attached_file], indent=2, use_natural_foreign_keys=True)) + json_file.write( + serializers.serialize( + 'json', [attached_file], indent=2, use_natural_foreign_keys=True + ) + ) i += len(documents) - print(' - Archived %10d documents' % i, '\r',) + print( + ' - Archived %10d documents' % i, '\r', + ) sys.stdout.flush() print('') i = 0 for b in batch(qs, 1000): b.delete() i += len(documents) - print(' - Deleted %10d documents' % i, '\r',) + print( + ' - Deleted %10d documents' % i, '\r', + ) sys.stdout.flush() print('') def save_journal(self): - journals = Journal.objects \ - .filter(time__lte=self.before) \ - .order_by('id') \ - .select_related('tag', 'template') \ - # FIXME in django 1.11 - # .prefetch_related('objectdata_set__content_type', - # 'stringdata_set', 'objectdata_set__tag', - # 'stringdata_set__tag', 'objectdata_set__content_object') + journals = ( + Journal.objects.filter(time__lte=self.before).order_by('id').select_related('tag', 'template') + ) # FIXME in django 1.11 + # .prefetch_related('objectdata_set__content_type', + # 'stringdata_set', 'objectdata_set__tag', + # 'stringdata_set__tag', 'objectdata_set__content_object') if not journals.exists(): return journal_path = os.path.join(self.path, 'journal.txt') diff --git a/docbow_project/pfwb/management/commands/sendmail.py b/docbow_project/pfwb/management/commands/sendmail.py index 95c8d85..b06f21c 100644 --- a/docbow_project/pfwb/management/commands/sendmail.py +++ b/docbow_project/pfwb/management/commands/sendmail.py @@ -32,6 +32,7 @@ logger = logging.getLogger('docbow.mail_interface') EXPEDITION = 'expedition' ATTACHED_FILE = 'attached_file' + class Command(BaseCommand): args = '' help = '''Convert a mail to a document send. @@ -82,11 +83,14 @@ In case of failure the following return value is returned: def error(self, msg, exit_code=None, **kwargs): sys.stderr.write(msg.format(**kwargs) + '\n') if hasattr(self, 'message_id'): - record('warning-smtp-interface', 'message {message_id} to {all_recipients} containing {filenames} refused: ' + msg, - message_id=self.message_id, - all_recipients=self.all_recipients, - filenames=self.filenames, - **kwargs) + record( + 'warning-smtp-interface', + 'message {message_id} to {all_recipients} containing {filenames} refused: ' + msg, + message_id=self.message_id, + all_recipients=self.all_recipients, + filenames=self.filenames, + **kwargs, + ) else: record('warning-smtp-interface', 'message refused: ' + msg, **kwargs) if exit_code: @@ -111,7 +115,7 @@ In case of failure the following return value is returned: def resolve_username_for_list(self, username): if not username.startswith('liste-'): return None - return self.mailing_lists.get(username[len('liste-'):]) + return self.mailing_lists.get(username[len('liste-') :]) @atomic def handle_mail(self, mail, mail_recipients, **options): @@ -134,8 +138,9 @@ In case of failure the following return value is returned: ccs = mail.get_all('cc', []) resent_tos = mail.get_all('resent-to', []) resent_ccs = mail.get_all('resent-cc', []) - self.all_recipients = all_recipients = mail_recipients or [b for a, b in email.utils.getaddresses(tos + ccs + resent_tos + - resent_ccs)] + self.all_recipients = all_recipients = mail_recipients or [ + b for a, b in email.utils.getaddresses(tos + ccs + resent_tos + resent_ccs) + ] self.message_id = mail.get('Message-ID', None) if not self.message_id: self.error('7.7.1 Mail is missing a Message-ID', exit_code=6) @@ -153,36 +158,40 @@ In case of failure the following return value is returned: try: filetype = models.FileType.objects.get(name=subject) except models.FileType.DoesNotExist: - record('warning', 'unknown filetype ' - '{filetype}, using default filetype', - filetype=subject) + record('warning', 'unknown filetype ' '{filetype}, using default filetype', filetype=subject) else: tabellio_doc_type = mail.get('x-tabellio-doc-type') if tabellio_doc_type: try: filetype = models.FileType.objects.get( - tabelliodoctype__tabellio_doc_type=tabellio_doc_type) + tabelliodoctype__tabellio_doc_type=tabellio_doc_type + ) except models.FileType.DoesNotExist: - record('warning', 'unknown x-tabellio-doc-type ' - '{tabellio_doc_type}, using default filetype', - tabellio_doc_type=tabellio_doc_type) + record( + 'warning', + 'unknown x-tabellio-doc-type ' '{tabellio_doc_type}, using default filetype', + tabellio_doc_type=tabellio_doc_type, + ) except models.FileType.MultipleObjectsReturned: - record('warning', 'unknown x-tabellio-doc-type ' - '{tabellio_doc_type}, using default filetype', - tabellio_doc_type=tabellio_doc_type) + record( + 'warning', + 'unknown x-tabellio-doc-type ' '{tabellio_doc_type}, using default filetype', + tabellio_doc_type=tabellio_doc_type, + ) if filetype is None: try: - filetype = models.FileType.objects.get( - id=app_settings.PFWB_SENDMAIL_DEFAULT_TYPE_ID) + filetype = models.FileType.objects.get(id=app_settings.PFWB_SENDMAIL_DEFAULT_TYPE_ID) except models.FileType.DoesNotExist: filetype, created = models.FileType.objects.get_or_create( - name=app_settings.PFWB_SENDMAIL_DEFAULT_TYPE_NAME) + name=app_settings.PFWB_SENDMAIL_DEFAULT_TYPE_NAME + ) if mode == ATTACHED_FILE: for part in mail.walk(): filename = part.get_filename(None) - if part.get_content_type() == 'text/plain' and \ - ('Content-Disposition' not in part or 'inline' in part['Content-Disposition']): + if part.get_content_type() == 'text/plain' and ( + 'Content-Disposition' not in part or 'inline' in part['Content-Disposition'] + ): charset = part.get_content_charset('us-ascii') description = force_text(part.get_payload(decode=True), charset) @@ -233,8 +242,7 @@ In case of failure the following return value is returned: raise auth_models.User.DoesNotExist() recipients.extend(users_qs.all()) except auth_models.User.DoesNotExist: - msg = 'Recipient %r is not an user of the platform' \ - % email_address + msg = 'Recipient %r is not an user of the platform' % email_address content_errors.append(msg) self.filenames = [a for a, b in attachments] if not len(attachments): @@ -248,33 +256,40 @@ In case of failure the following return value is returned: user_id = app_settings.PFWB_SENDMAIL_TABELLIO_EXPEDITION_USER_ID sender = auth_models.User.objects.get(id=user_id) except auth_models.User.DoesNotExist: - content_errors.append('No user match the sender user_id %s in mode ' - '%s' % (user_id, mode)) + content_errors.append('No user match the sender user_id %s in mode ' '%s' % (user_id, mode)) if content_errors: - msg = [ '7.7.1 The email sent contains many errors:' ] + msg = ['7.7.1 The email sent contains many errors:'] for error in content_errors: msg.append(' - %s' % error) self.error('\n'.join(msg), exit_code=4) else: if mode == ATTACHED_FILE: - record('smtp-received-document', 'mode: {mode} message-id: {message_id} subject: {subject}', - mode=mode, message_id=self.message_id, subject=subject) + record( + 'smtp-received-document', + 'mode: {mode} message-id: {message_id} subject: {subject}', + mode=mode, + message_id=self.message_id, + subject=subject, + ) else: - record('smtp-received-document', 'mode: {mode} message-id: ' - '{message_id} x-tabellio-doc-url: {x_tabellio_doc_url} ' - 'x-tabellio-doc-type: {x_tabellio_doc_type}', - mode=mode, message_id=self.message_id, - subject=repr(subject), x_tabellio_doc_url=url, - x_tabellio_doc_type=tabellio_doc_type) - document = models.Document(sender=sender, - comment=description, filetype=filetype) + record( + 'smtp-received-document', + 'mode: {mode} message-id: ' + '{message_id} x-tabellio-doc-url: {x_tabellio_doc_url} ' + 'x-tabellio-doc-type: {x_tabellio_doc_type}', + mode=mode, + message_id=self.message_id, + subject=repr(subject), + x_tabellio_doc_url=url, + x_tabellio_doc_type=tabellio_doc_type, + ) + document = models.Document(sender=sender, comment=description, filetype=filetype) document.save() document.to_user.set(recipients) document.to_list.set(mailing_list_recipients) for filename, payload in attachments: content = ContentFile(payload) - attached_file = models.AttachedFile(document=document, - name=filename) + attached_file = models.AttachedFile(document=document, name=filename) attached_file.content.save(filename, content, save=False) attached_file.save() document._timestamp = time.time() diff --git a/docbow_project/pfwb/management/commands/sync-tabellio.py b/docbow_project/pfwb/management/commands/sync-tabellio.py index 2eed1c5..79234b1 100644 --- a/docbow_project/pfwb/management/commands/sync-tabellio.py +++ b/docbow_project/pfwb/management/commands/sync-tabellio.py @@ -9,9 +9,7 @@ from sqlalchemy import create_engine, engine as sqla_engine, or_ from docbow_project.docbow.models import MailingList, DocbowProfile, FileType from docbow_project.pfwb.models import TabellioDocType -from docbow_project.pfwb.tabellio import ( - DBSession, TAdresse, TCom, TComppol, TPer, TPershistoline, TTypedoc -) +from docbow_project.pfwb.tabellio import DBSession, TAdresse, TCom, TComppol, TPer, TPershistoline, TTypedoc """ The following variables should be defined somewhere in your local configurration. @@ -30,6 +28,7 @@ def get_username(last_name, first_name): username = '%s.%s' % (slugify(first_name), slugify(last_name)) return username + def get_or_create_user(last_name, first_name, email, verbose, tabellio_id): profile = None try: @@ -75,17 +74,16 @@ def get_or_create_user(last_name, first_name, email, verbose, tabellio_id): class Command(BaseCommand): - @transaction.atomic def handle(self, *args, **options): - verbose = (options.get('verbosity') > 1) + verbose = options.get('verbosity') > 1 parl_list = MailingList.objects.get(id=settings.PARLEMENTAIRES_MAILING_ID) ministres_list = MailingList.objects.get(id=settings.MINISTRES_MAILING_ID) comppols = {} - commissions_infos = {} # dict of (name, [list of pers id]) - commissions = {} # dict of list of User + commissions_infos = {} # dict of (name, [list of pers id]) + commissions = {} # dict of list of User engine = create_engine( sqla_engine.url.URL( @@ -94,7 +92,7 @@ class Command(BaseCommand): password=settings.TABELLIO_DBPASSWORD, host=settings.TABELLIO_DBHOST, port=settings.TABELLIO_DBPORT or None, - database=settings.TABELLIO_DBNAME + database=settings.TABELLIO_DBNAME, ) ) session = DBSession(bind=engine.connect()) @@ -102,10 +100,11 @@ class Command(BaseCommand): # get list of persons that are neither deputy or ministre so they can # be disabled if they exists in docbow for pers, pers_histo in session.query(TPer, TPershistoline).filter( - TPer.id == TPershistoline.pers, - TPer.prenom.isnot(None), - TPershistoline.type.in_(['P_CMPL', 'M_MINT']), - TPershistoline.fin.isnot(None)): + TPer.id == TPershistoline.pers, + TPer.prenom.isnot(None), + TPershistoline.type.in_(['P_CMPL', 'M_MINT']), + TPershistoline.fin.isnot(None), + ): last_name, first_name = pers.nom, pers.prenom @@ -132,42 +131,40 @@ class Command(BaseCommand): ministres_list.members.remove(user) ministres_list.save() - # get current deputies deputies = {} # in a first pass, get all of them - for pers, pers_histo, comppol in session.query(TPer, TPershistoline, TComppol).\ - filter( - TPer.prenom.isnot(None), - TPershistoline.description == TComppol.id, - TPer.id == TPershistoline.pers, - TPershistoline.type == 'P_CMPL', - TPershistoline.fin.is_(None) - ): + for pers, pers_histo, comppol in session.query(TPer, TPershistoline, TComppol).filter( + TPer.prenom.isnot(None), + TPershistoline.description == TComppol.id, + TPer.id == TPershistoline.pers, + TPershistoline.type == 'P_CMPL', + TPershistoline.fin.is_(None), + ): pers_id, last_name, first_name, comppol = pers.id, pers.nom, pers.prenom, comppol.abbr deputies[pers_id] = (last_name, first_name, 'noreply@pfwb.be', comppol) # in a second pass, overwrite those who have emails - for pers, pers_histo, addr, comppol in session.query(TPer, TPershistoline, TAdresse, TComppol).\ - filter( - TPer.prenom.isnot(None), - TPershistoline.description == TComppol.id, - TPer.id == TPershistoline.pers, - TPershistoline.type == 'P_CMPL', - TPershistoline.fin.is_(None), - TAdresse.email.isnot(None), - or_( - TAdresse.id == TPer.addrpriv, - TAdresse.id == TPer.addrprof1, - TAdresse.id == TPer.addrprof2 - ), - TAdresse.courriel.is_(True) - ): + for pers, pers_histo, addr, comppol in session.query(TPer, TPershistoline, TAdresse, TComppol).filter( + TPer.prenom.isnot(None), + TPershistoline.description == TComppol.id, + TPer.id == TPershistoline.pers, + TPershistoline.type == 'P_CMPL', + TPershistoline.fin.is_(None), + TAdresse.email.isnot(None), + or_(TAdresse.id == TPer.addrpriv, TAdresse.id == TPer.addrprof1, TAdresse.id == TPer.addrprof2), + TAdresse.courriel.is_(True), + ): - pers_id, last_name, first_name, email, comppol = \ - pers.id, pers.nom, pers.prenom, addr.email, comppol.abbr + pers_id, last_name, first_name, email, comppol = ( + pers.id, + pers.nom, + pers.prenom, + addr.email, + comppol.abbr, + ) deputies[pers_id] = (last_name, first_name, email, comppol) # get commissions @@ -177,15 +174,13 @@ class Command(BaseCommand): commissions[com_id] = [] for com_id in commissions.keys(): - for pers in session.query(TPer).join( - TPershistoline, TPershistoline.pers == TPer.id - ).filter( - TPershistoline.description == com_id, - TPershistoline.fin.is_(None) + for pers in ( + session.query(TPer) + .join(TPershistoline, TPershistoline.pers == TPer.id) + .filter(TPershistoline.description == com_id, TPershistoline.fin.is_(None)) ): commissions_infos[com_id][1].append(pers.id) - deputy_users = [] for pers_id, deputy in deputies.items(): last_name, first_name, email, comppol = deputy @@ -211,7 +206,8 @@ class Command(BaseCommand): # create mailing lists for political groups for comppol, members in comppols.items(): maillist, created = MailingList.objects.get_or_create( - name=u'Appartenance politique - %s' % comppol) + name=u'Appartenance politique - %s' % comppol + ) # remove members of the list that should no longer be in there for member in maillist.members.all(): if member not in members: @@ -221,13 +217,13 @@ class Command(BaseCommand): if member not in maillist.members.all(): maillist.members.add(member) - # get inactive comppol query = session.query(TComppol).filter(TComppol.st == 'S_INACTIVE') inactive_comppols = [u'Appartenance politique - %s' % comppol.abbr for comppol in query] comppol_names = [u'Appartenance politique - %s' % comppol for comppol in comppols] - for maillist in (MailingList.objects.filter(name__startswith='Appartenance politique -') - .exclude(name__in=comppol_names)): + for maillist in MailingList.objects.filter(name__startswith='Appartenance politique -').exclude( + name__in=comppol_names + ): maillist.members.clear() if verbose: print('clear', maillist) @@ -241,8 +237,7 @@ class Command(BaseCommand): # create mailing lists for commissions for com_id, members in commissions.items(): com_name = commissions_infos[com_id][0] - maillist, created = MailingList.objects.get_or_create( - name=u'Commission - %s' % com_name) + maillist, created = MailingList.objects.get_or_create(name=u'Commission - %s' % com_name) if maillist in existing_commissions: existing_commissions.remove(maillist) # remove members of the list that should no longer be in there @@ -265,33 +260,27 @@ class Command(BaseCommand): ministres = {} # like for deputies, we have a first pass to get all of them - for pers, pers_histo in session.query(TPer, TPershistoline).\ - filter( - TPer.prenom.isnot(None), - TPer.id == TPershistoline.pers, - TPershistoline.type == 'M_MINT', - TPershistoline.fin.is_(None) - ): + for pers, pers_histo in session.query(TPer, TPershistoline).filter( + TPer.prenom.isnot(None), + TPer.id == TPershistoline.pers, + TPershistoline.type == 'M_MINT', + TPershistoline.fin.is_(None), + ): pers_id, last_name, first_name = pers.id, pers.nom, pers.prenom ministres[pers_id] = (last_name, first_name, 'noreply@pfwb.be') # and a second pass to get the email addresses - for pers, pers_histo, addr, in session.query(TPer, TPershistoline, TAdresse).\ - filter( - TPer.st == 'S_MINISTRE', - TPer.prenom.isnot(None), - TPer.id == TPershistoline.pers, - TPershistoline.type == 'M_MINT', - TPershistoline.fin.is_(None), - TAdresse.email.isnot(None), - or_( - TAdresse.id == TPer.addrpriv, - TAdresse.id == TPer.addrprof1, - TAdresse.id == TPer.addrprof2 - ), - TAdresse.courriel.is_(True) - ): + for pers, pers_histo, addr, in session.query(TPer, TPershistoline, TAdresse).filter( + TPer.st == 'S_MINISTRE', + TPer.prenom.isnot(None), + TPer.id == TPershistoline.pers, + TPershistoline.type == 'M_MINT', + TPershistoline.fin.is_(None), + TAdresse.email.isnot(None), + or_(TAdresse.id == TPer.addrpriv, TAdresse.id == TPer.addrprof1, TAdresse.id == TPer.addrprof2), + TAdresse.courriel.is_(True), + ): pers_id, last_name, first_name, email = pers.id, pers.nom, pers.prenom, addr.email ministres[pers_id] = (last_name, first_name, email) diff --git a/docbow_project/pfwb/mellon_adapter.py b/docbow_project/pfwb/mellon_adapter.py index 4dd0b6b..ed2bedd 100644 --- a/docbow_project/pfwb/mellon_adapter.py +++ b/docbow_project/pfwb/mellon_adapter.py @@ -20,7 +20,6 @@ try: import mellon.models class PFWBMellonAdapter(mellon.adapters.DefaultAdapter): - def lookup_by_attributes(self, idp, saml_attributes): tabellio_id = get_tabellio_id(saml_attributes) if tabellio_id: diff --git a/docbow_project/pfwb/migrations/0001_initial.py b/docbow_project/pfwb/migrations/0001_initial.py index 510f94d..fe4bdb7 100644 --- a/docbow_project/pfwb/migrations/0001_initial.py +++ b/docbow_project/pfwb/migrations/0001_initial.py @@ -14,9 +14,17 @@ class Migration(migrations.Migration): migrations.CreateModel( name='PloneFileType', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ( + 'id', + models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True), + ), ('plone_portal_type', models.CharField(max_length=64)), - ('filetype', models.OneToOneField(verbose_name='Document type', to='docbow.FileType', on_delete=models.CASCADE)), + ( + 'filetype', + models.OneToOneField( + verbose_name='Document type', to='docbow.FileType', on_delete=models.CASCADE + ), + ), ], options={ 'db_table': 'plone_plonefiletype', @@ -28,9 +36,17 @@ class Migration(migrations.Migration): migrations.CreateModel( name='TabellioDocType', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ( + 'id', + models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True), + ), ('tabellio_doc_type', models.CharField(max_length=64, verbose_name='Tabellio doc type')), - ('filetype', models.OneToOneField(verbose_name='Document type', to='docbow.FileType', on_delete=models.CASCADE)), + ( + 'filetype', + models.OneToOneField( + verbose_name='Document type', to='docbow.FileType', on_delete=models.CASCADE + ), + ), ], options={ 'verbose_name': 'Tabellio doc type mapping', diff --git a/docbow_project/pfwb/models.py b/docbow_project/pfwb/models.py index b4815c7..12bf45a 100644 --- a/docbow_project/pfwb/models.py +++ b/docbow_project/pfwb/models.py @@ -3,8 +3,10 @@ from django.utils.translation import ugettext_lazy as _ from ..docbow.models import FileType + class PloneFileType(models.Model): '''GED file type to store in exported JSON documents''' + filetype = models.OneToOneField(FileType, verbose_name=_('Document type'), on_delete=models.CASCADE) plone_portal_type = models.CharField(max_length=64) @@ -17,6 +19,7 @@ class PloneFileType(models.Model): class TabellioDocType(models.Model): '''Mapping from Tabellio document type to Docbow filetype''' + filetype = models.OneToOneField(FileType, verbose_name=_('Document type'), on_delete=models.CASCADE) tabellio_doc_type = models.CharField(max_length=64, verbose_name=_('Tabellio doc type')) diff --git a/docbow_project/pfwb/settings.py b/docbow_project/pfwb/settings.py index 43d8761..1926966 100644 --- a/docbow_project/pfwb/settings.py +++ b/docbow_project/pfwb/settings.py @@ -5,4 +5,4 @@ DOCBOW_EDIT_EMAIL = True PORTAL_BASE_URL = 'https://form.portail.pfwb.be' -HELP_DIR = '/usr/share/doc/docbow/build-pfwb' +HELP_DIR = '/usr/share/doc/docbow/build-pfwb' diff --git a/docbow_project/pfwb/signals.py b/docbow_project/pfwb/signals.py index a00ec4f..ecb4145 100644 --- a/docbow_project/pfwb/signals.py +++ b/docbow_project/pfwb/signals.py @@ -22,12 +22,12 @@ def push_document(signal, sender, instance, **kwargs): tpl = u'{sender.first_name} {sender.last_name} ({sender.username})' sender = tpl.format(sender=document.sender) metadata = { - 'document_id': document.id, - 'plone_portal_type': plone_file_type.plone_portal_type, - 'title': force_text(document.filetype), - 'description': document.comment, - 'reception_date': document.date.isoformat().split('.')[0], - 'sender': sender, + 'document_id': document.id, + 'plone_portal_type': plone_file_type.plone_portal_type, + 'title': force_text(document.filetype), + 'description': document.comment, + 'reception_date': document.date.isoformat().split('.')[0], + 'sender': sender, } if attached_file.kind: metadata['kind'] = attached_file.kind.name diff --git a/docbow_project/pfwb/tabellio.py b/docbow_project/pfwb/tabellio.py index 8279e6b..40458d4 100644 --- a/docbow_project/pfwb/tabellio.py +++ b/docbow_project/pfwb/tabellio.py @@ -3,9 +3,7 @@ https://pypi.org/project/sqlacodegen/ """ -from sqlalchemy import ( - ARRAY, Boolean, Column, Date, DateTime, ForeignKey, Index, Integer, Text, TEXT -) +from sqlalchemy import ARRAY, Boolean, Column, Date, DateTime, ForeignKey, Index, Integer, Text, TEXT from sqlalchemy.dialects.postgresql import TSVECTOR from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import relationship, sessionmaker @@ -34,9 +32,7 @@ class TAdresse(Base): class TPer(Base): __tablename__ = 't_pers' - __table_args__ = ( - Index('i_t_pers_persobjet', 'nom', 'prenom', 'initiales', unique=True), - ) + __table_args__ = (Index('i_t_pers_persobjet', 'nom', 'prenom', 'initiales', unique=True),) id = Column(Text, primary_key=True) st = Column(Text, nullable=False) diff --git a/docbow_project/pfwb/urls.py b/docbow_project/pfwb/urls.py index 94278ec..637600f 100644 --- a/docbow_project/pfwb/urls.py +++ b/docbow_project/pfwb/urls.py @@ -1,2 +1 @@ - urlpatterns = [] diff --git a/docbow_project/pw/management/commands/sendmail.py b/docbow_project/pw/management/commands/sendmail.py index 96b2910..7e624d6 100644 --- a/docbow_project/pw/management/commands/sendmail.py +++ b/docbow_project/pw/management/commands/sendmail.py @@ -32,6 +32,7 @@ ATTACHED_FILE = 'attached_file' PRIVATE_PREFIX = 'Private - ' PRIVATE_SUFFIX = '-private' + class Command(BaseCommand): args = '' help = '''Convert a mail to a document send. @@ -83,12 +84,16 @@ In case of failure the following return value is returned: def error(self, msg, exit_code=None, **kwargs): sys.stderr.write(msg.format(**kwargs)) if hasattr(self, 'message_id'): - error_record('warning-smtp-interface', 'message {message_id} to {all_recipients} containing {filenames} with subject {subject} refused: ' + msg, - message_id=repr(self.message_id), - all_recipients=', '.join(map(repr, self.all_recipients)), - filenames=', '.join(map(repr, self.filenames)), - subject=repr(self.subject), - **kwargs) + error_record( + 'warning-smtp-interface', + 'message {message_id} to {all_recipients} containing {filenames} with subject {subject} refused: ' + + msg, + message_id=repr(self.message_id), + all_recipients=', '.join(map(repr, self.all_recipients)), + filenames=', '.join(map(repr, self.filenames)), + subject=repr(self.subject), + **kwargs, + ) else: error_record('warning-smtp-interface', 'message refused: ' + msg, **kwargs) if exit_code: @@ -113,7 +118,7 @@ In case of failure the following return value is returned: def resolve_username_for_list(self, username): if not username.startswith('liste-'): return None - return self.mailing_lists.get(username[len('liste-'):]) + return self.mailing_lists.get(username[len('liste-') :]) @atomic def handle_mail(self, mail, mail_recipients, **options): @@ -126,17 +131,15 @@ In case of failure the following return value is returned: if options.get('sender'): try: sender = auth_models.User.objects.filter( - Q(docbowprofile__is_guest=False)| - Q(docbowprofile__isnull=True)).get( - username=options['sender']) + Q(docbowprofile__is_guest=False) | Q(docbowprofile__isnull=True) + ).get(username=options['sender']) except auth_models.User.DoesNotExist: self.error('5.6.0 Unknown sender %r' % options['sender'], exit_code=8) else: try: sender = auth_models.User.objects.filter( - Q(docbowprofile__is_guest=False)| - Q(docbowprofile__isnull=True)).get( - email=from_email) + Q(docbowprofile__is_guest=False) | Q(docbowprofile__isnull=True) + ).get(email=from_email) except auth_models.User.DoesNotExist: content_errors.append('No sender user have mail %r' % from_email) except MultipleObjectsReturned: @@ -145,8 +148,9 @@ In case of failure the following return value is returned: ccs = mail.get_all('cc', []) resent_tos = mail.get_all('resent-to', []) resent_ccs = mail.get_all('resent-cc', []) - self.all_recipients = all_recipients = mail_recipients or [b for a, b in email.utils.getaddresses(tos + ccs + resent_tos + - resent_ccs)] + self.all_recipients = all_recipients = mail_recipients or [ + b for a, b in email.utils.getaddresses(tos + ccs + resent_tos + resent_ccs) + ] self.message_id = mail.get('Message-ID', None) if not self.message_id: content_errors.append('Mail is missing a Message-ID') @@ -161,7 +165,7 @@ In case of failure the following return value is returned: self.subject = subject = mail.get('Subject', '') if subject.startswith(PRIVATE_PREFIX): self.private = True - subject = subject[len(PRIVATE_PREFIX):] + subject = subject[len(PRIVATE_PREFIX) :] if not subject: content_errors.append('Mail is missing a filetype subject') else: @@ -177,8 +181,9 @@ In case of failure the following return value is returned: for part in mail.walk(): filename = part.get_filename(None) - if part.get_content_type() == 'text/plain' and \ - ('Content-Disposition' not in part or 'inline' in part['Content-Disposition']): + if part.get_content_type() == 'text/plain' and ( + 'Content-Disposition' not in part or 'inline' in part['Content-Disposition'] + ): charset = part.get_content_charset('us-ascii') for cset in (charset, 'iso-8859-15', 'utf-8'): try: @@ -200,7 +205,7 @@ In case of failure the following return value is returned: username, domain = email_address.split('@', 1) if username.endswith(PRIVATE_SUFFIX): self.private = True - username = username[:-len(PRIVATE_SUFFIX)] + username = username[: -len(PRIVATE_SUFFIX)] email_address = '%s@%s' % (username, domain) # mailing list case mailing_list = self.resolve_username_for_list(username) @@ -209,9 +214,8 @@ In case of failure the following return value is returned: continue # classic user case users = auth_models.User.objects.filter( - Q(docbowprofile__is_guest=False) | - Q(docbowprofile__isnull=True)).filter( - email=email_address) + Q(docbowprofile__is_guest=False) | Q(docbowprofile__isnull=True) + ).filter(email=email_address) if users: for user in users: recipients.append(user) @@ -220,8 +224,7 @@ In case of failure the following return value is returned: user = auth_models.User.objects.get(username=username) recipients.append(user) except auth_models.User.DoesNotExist: - msg = 'Recipient %r is not an user of the platform' \ - % username + msg = 'Recipient %r is not an user of the platform' % username content_errors.append(msg) self.filenames = [a for a, b in attachments] if not len(attachments): @@ -230,26 +233,29 @@ In case of failure the following return value is returned: content_errors.append('You must have at least one recipient in your message.') if content_errors: - msg = [ '5.6.0 The email sent contains many errors:' ] + msg = ['5.6.0 The email sent contains many errors:'] for error in content_errors: msg.append(' - %s' % error) self.error('\n'.join(msg), exit_code=4) else: - record('smtp-received-document', 'message-id: {message_id} subject: {subject} to: {all_recipients} filenames: {filenames} private: {private}', - message_id=self.message_id, subject=subject, - all_recipients=', '.join(map(repr, self.all_recipients)), - filenames=', '.join(map(repr, self.filenames)), - private=self.private) - document = models.Document(sender=sender, - comment=description, filetype=filetype, - private=self.private) + record( + 'smtp-received-document', + 'message-id: {message_id} subject: {subject} to: {all_recipients} filenames: {filenames} private: {private}', + message_id=self.message_id, + subject=subject, + all_recipients=', '.join(map(repr, self.all_recipients)), + filenames=', '.join(map(repr, self.filenames)), + private=self.private, + ) + document = models.Document( + sender=sender, comment=description, filetype=filetype, private=self.private + ) document.save() document.to_user.set(recipients) document.to_list.set(mailing_list_recipients) for filename, payload in attachments: content = ContentFile(payload) - attached_file = models.AttachedFile(document=document, - name=filename) + attached_file = models.AttachedFile(document=document, name=filename) attached_file.content.save(filename, content, save=False) attached_file.save() document._timestamp = time.time() diff --git a/docbow_project/pw/models.py b/docbow_project/pw/models.py index 784fed1..c0a39a8 100644 --- a/docbow_project/pw/models.py +++ b/docbow_project/pw/models.py @@ -22,8 +22,7 @@ except: A2User.objects.using('authentic').filter(username=username).delete() @receiver(pre_save, sender=User) - def user_pre_save(sender, instance, raw, using, *args, - **kwargs): + def user_pre_save(sender, instance, raw, using, *args, **kwargs): '''Create new instance on authentic side''' if using != 'default': return diff --git a/docbow_project/pw/settings.py b/docbow_project/pw/settings.py index dff32ce..2ce5a05 100644 --- a/docbow_project/pw/settings.py +++ b/docbow_project/pw/settings.py @@ -17,28 +17,26 @@ if 'USE_SAML' in os.environ: INSTALLED_APPS += ('mellon',) LOGIN_URL = 'mellon_login' LOGOUT_URL = 'mellon_logout' - AUTHENTICATION_BACKENDS = ( - 'mellon.backends.SAMLBackend', - ) + AUTHENTICATION_BACKENDS = ('mellon.backends.SAMLBackend',) MELLON_IDENTITY_PROVIDERS = { - 'METADATA': '/etc/docbow/idp-metadata.xml', - 'GROUP_ATTRIBUTE': 'role', + 'METADATA': '/etc/docbow/idp-metadata.xml', + 'GROUP_ATTRIBUTE': 'role', } MELLON_ATTRIBUTE_MAPPING = { - 'first_name': '{attributes[sn][0]}', - 'last_name': '{attributes[gn][0]}', - 'email': '{attributes[mail][0]}', + 'first_name': '{attributes[sn][0]}', + 'last_name': '{attributes[gn][0]}', + 'email': '{attributes[mail][0]}', } MELLON_SUPERUSER_MAPPING = { - 'role': ['Superutilisateur'], + 'role': ['Superutilisateur'], } MELLON_USERNAME_TEMPLATE = '{attributes[name_id_content]}' if 'AUTHENTIC_DATABASE_ENGINE' in os.environ: DATABASES['authentic'] = { - 'ENGINE': os.environ.get('AUTHENTIC_DATABASE_ENGINE'), - 'NAME': os.environ.get('AUTHENTIC_DATABASE_NAME'), - 'USER': os.environ.get('AUTHENTIC_DATABASE_USER'), + 'ENGINE': os.environ.get('AUTHENTIC_DATABASE_ENGINE'), + 'NAME': os.environ.get('AUTHENTIC_DATABASE_NAME'), + 'USER': os.environ.get('AUTHENTIC_DATABASE_USER'), } JOURNAL_DB_FOR_ERROR_ALIAS = 'journal' @@ -52,7 +50,7 @@ if PLATFORM == 'test': DEFAULT_FROM_EMAIL = 'gestionnaire@pes-pw.dev.entrouvert.org' CONTACT_SUBJECT_PREFIX = 'Contact depuis pes-pw.dev.entrouvert.org: ' -HELP_DIR = '/usr/share/doc/docbow/build-pw' +HELP_DIR = '/usr/share/doc/docbow/build-pw' try: from local_settings import * diff --git a/docbow_project/settings.py b/docbow_project/settings.py index 7f62b1b..a35e5cd 100644 --- a/docbow_project/settings.py +++ b/docbow_project/settings.py @@ -4,6 +4,7 @@ import os import logging.handlers from django.core.exceptions import ImproperlyConfigured + gettext_noop = lambda s: s BASE_DIR = os.path.dirname(__file__) @@ -30,10 +31,10 @@ ROOT_URLCONF = 'docbow_project.urls' STATICFILES_DIRS = ('/var/lib/%s/static/' % PROJECT_NAME,) FILE_PER_PAGE = 30 DO_NOT_SEND_GROUPS = ( - u'Administrateurs des groupes', - u'Administrateurs des types de document', - u'Administrateurs des utilisateurs', - u"Utilisateurs de l'application" + u'Administrateurs des groupes', + u'Administrateurs des types de document', + u'Administrateurs des utilisateurs', + u"Utilisateurs de l'application", ) CONTACT_GROUPS = (u'Contact « Administrateur du système »',) LOCALE_PATHS = (os.path.join(BASE_DIR, 'locale'),) @@ -43,8 +44,8 @@ ALLOWED_HOSTS = ['*'] LOGIN_REDIRECT_URL = '/inbox' MESSAGE_STORAGE = 'django.contrib.messages.storage.fallback.FallbackStorage' AUTHENTICATION_BACKENDS = ( - 'docbow_project.docbow.auth_backend.DelegationAuthBackend', - 'django.contrib.auth.backends.ModelBackend' + 'docbow_project.docbow.auth_backend.DelegationAuthBackend', + 'django.contrib.auth.backends.ModelBackend', ) INTERNAL_IPS = ('127.0.0.1', '82.234.244.169') ADMINS = () @@ -68,7 +69,7 @@ DOCBOW_PFWB_SENDMAIL_TABELLIO_EXPEDITION_USER_ID = None DOCBOW_PFWB_SENDMAIL_ATTACHED_FILE_EMAIL = 'dontknow@pfwb.be' DOCBOW_PFWB_SENDMAIL_ATTACHED_FILE_USER_ID = None DATE_INPUT_FORMATS = ('%d/%m/%Y', '%Y-%m-%d') -DOCBOW_MAX_FILE_SIZE = 10*1024*1024 +DOCBOW_MAX_FILE_SIZE = 10 * 1024 * 1024 DOCBOW_PRIVATE_DOCUMENTS = False DOCBOW_BASE_URL = 'http://localhost:8000' PLATFORM = 'prod' @@ -82,28 +83,17 @@ TEST_RUNNER = 'django.test.runner.DiscoverRunner' MANAGERS = ADMINS -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'NAME': 'docbow', - } -} +DATABASES = {'default': {'ENGINE': 'django.db.backends.postgresql_psycopg2', 'NAME': 'docbow',}} # Hey Entr'ouvert is in France !! -LANGUAGES = ( - ('fr', gettext_noop('French')), -) +LANGUAGES = (('fr', gettext_noop('French')),) TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [ - '/var/lib/%s/templates' % PROJECT_NAME, - os.path.join(BASE_DIR, 'docbow', 'templates') - - ], + 'DIRS': ['/var/lib/%s/templates' % PROJECT_NAME, os.path.join(BASE_DIR, 'docbow', 'templates')], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ @@ -122,7 +112,6 @@ TEMPLATES = [ ] - ATOMIC_REQUESTS = True MIDDLEWARE = ( @@ -162,11 +151,10 @@ LOGGING = { 'version': 1, 'disable_existing_loggers': True, 'formatters': { - 'syslog': { - 'format': PROJECT_NAME + '(pid=%(process)d) %(levelname)s %(name)s: %(message)s', - }, + 'syslog': {'format': PROJECT_NAME + '(pid=%(process)d) %(levelname)s %(name)s: %(message)s',}, 'syslog_debug': { - 'format': PROJECT_NAME + '(pid=%(process)d) %(levelname)s %(asctime)s t_%(thread)s %(name)s: %(message)s', + 'format': PROJECT_NAME + + '(pid=%(process)d) %(levelname)s %(asctime)s t_%(thread)s %(name)s: %(message)s', }, }, 'handlers': { @@ -184,26 +172,12 @@ LOGGING = { 'filters': [], 'include_html': True, }, - 'console': { - 'class': 'logging.StreamHandler', - 'formatter': 'syslog_debug', - 'level': 'DEBUG', - }, + 'console': {'class': 'logging.StreamHandler', 'formatter': 'syslog_debug', 'level': 'DEBUG',}, }, 'loggers': { - 'django.db': { - 'handlers': [ ], - 'level': 'INFO', - 'propagate': True, - }, - 'django.request': { - 'handlers': [ ], - 'propagate': True, - }, - '': { - 'handlers': [ 'syslog' ], - 'level': 'INFO', - }, + 'django.db': {'handlers': [], 'level': 'INFO', 'propagate': True,}, + 'django.request': {'handlers': [], 'propagate': True,}, + '': {'handlers': ['syslog'], 'level': 'INFO',}, }, } @@ -212,7 +186,7 @@ if DEBUG: LOGGING['loggers']['']['handlers'].append('console') -HELP_DIR = '/usr/share/doc/docbow/help' +HELP_DIR = '/usr/share/doc/docbow/help' for logger in LOGGING['loggers'].values(): if 'syslog' in logger['handlers']: @@ -224,13 +198,17 @@ if DEBUG and not globals()['SECRET_KEY']: if USE_DEBUG_TOOLBAR: try: import debug_toolbar + INSTALLED_APPS += ('debug_toolbar',) except ImportError: print("Debug toolbar missing, not loaded") # syntax checks for admin in ADMINS: - assert len(admin) == 2, 'ADMINS setting must be a colon separated list of name and emails separated by a semi-colon: %s' % ADMINS + assert len(admin) == 2, ( + 'ADMINS setting must be a colon separated list of name and emails separated by a semi-colon: %s' + % ADMINS + ) assert '@' in admin[1], 'ADMINS setting pairs second value must be emails: %s' % ADMINS diff --git a/docbow_project/wsgi.py b/docbow_project/wsgi.py index b2804a8..4a1f89b 100644 --- a/docbow_project/wsgi.py +++ b/docbow_project/wsgi.py @@ -18,4 +18,5 @@ import os os.environ.setdefault("DJANGO_SETTINGS_MODULE", "docbow_project.settings") from django.core.wsgi import get_wsgi_application + application = get_wsgi_application() diff --git a/mellon_settings.py b/mellon_settings.py index 31bd7fb..d4403d1 100644 --- a/mellon_settings.py +++ b/mellon_settings.py @@ -7,18 +7,13 @@ INSTALLED_APPS += ('mellon',) AUTHENTICATION_BACKENDS = ( - 'docbow_project.docbow.auth_backend.DocbowMellonAuthBackend' - 'django.contrib.auth.backends.ModelBackend' + 'docbow_project.docbow.auth_backend.DocbowMellonAuthBackend' 'django.contrib.auth.backends.ModelBackend' ) LOGIN_URL = 'mellon_login' LOGOUT_URL = 'mellon_logout' -MELLON_IDENTITY_PROVIDERS = [ - { - 'METADATA': '/path/to/idp.xml' - } -] +MELLON_IDENTITY_PROVIDERS = [{'METADATA': '/path/to/idp.xml'}] MELLON_PUBLIC_KEYS = ['/path/to/saml.crt'] MELLON_PRIVATE_KEY = '/path/to/saml.key' diff --git a/setup.py b/setup.py index 46c5d2f..2cb1df1 100755 --- a/setup.py +++ b/setup.py @@ -34,7 +34,9 @@ def get_version(): if os.path.exists('.git'): p = subprocess.Popen( ['git', 'describe', '--dirty=.dirty', '--match=v*'], - stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) result = p.communicate()[0] if p.returncode == 0: result = result.decode('ascii').strip()[1:] # strip spaces/newlines and initial v @@ -45,9 +47,7 @@ def get_version(): version = result return version else: - return '0.0.post%s' % len( - subprocess.check_output( - ['git', 'rev-list', 'HEAD']).splitlines()) + return '0.0.post%s' % len(subprocess.check_output(['git', 'rev-list', 'HEAD']).splitlines()) return '0.0' @@ -64,6 +64,7 @@ class compile_translations(Command): def run(self): try: from django.core.management import call_command + for path, dirs, files in os.walk('docbow_project'): if 'locale' not in dirs: continue @@ -85,34 +86,37 @@ class install_lib(_install_lib): _install_lib.run(self) -setup(name='docbow', - version=get_version(), - license='AGPL 3.0', - description='Document box for the Wallon Parliament', - url='https://dev.entrouvert.org/projects/docbow-pub/', - author="Entr'ouvert", - author_email='info@entrouvert.com', - maintainer='Benjamin Dauvergne', - maintainer_email='bdauvergne@entrouvert.com', - include_package_data=True, - scripts=('manage.py',), - packages=find_packages(), - install_requires=[ - 'django>=1.11, <2', - 'six<1.11.0', - 'typing', # For M2Crypto.util - 'django-debug-toolbar<0.9.0', - 'gunicorn', - 'django_journal>=2.0.0', - 'django-picklefield<2.0.0', - 'django-tables2', - 'requests', - 'python-magic', - 'django-watson<1.5.4', - 'sqlalchemy', - ], - cmdclass={ - 'build': build, - 'install_lib': install_lib, - 'compile_translations': compile_translations, - 'sdist': eo_sdist}) +setup( + name='docbow', + version=get_version(), + license='AGPL 3.0', + description='Document box for the Wallon Parliament', + url='https://dev.entrouvert.org/projects/docbow-pub/', + author="Entr'ouvert", + author_email='info@entrouvert.com', + maintainer='Benjamin Dauvergne', + maintainer_email='bdauvergne@entrouvert.com', + include_package_data=True, + scripts=('manage.py',), + packages=find_packages(), + install_requires=[ + 'django>=1.11, <2', + 'six<1.11.0', + 'typing', # For M2Crypto.util + 'django-debug-toolbar<0.9.0', + 'gunicorn', + 'django_journal>=2.0.0', + 'django-picklefield<2.0.0', + 'django-tables2', + 'requests', + 'python-magic', + 'django-watson<1.5.4', + 'sqlalchemy', + ], + cmdclass={ + 'build': build, + 'install_lib': install_lib, + 'compile_translations': compile_translations, + 'sdist': eo_sdist, + }, +) diff --git a/tests/conftest.py b/tests/conftest.py index 6116a7d..048bb54 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -22,22 +22,16 @@ def filetypes(db): def users(db): result = [] for i in range(10): - result.append( - User.objects.create( - username='user-%s' % i, - email='user-%s@example.com' % i)) + result.append(User.objects.create(username='user-%s' % i, email='user-%s@example.com' % i)) result[-1].set_password('password') result[-1].save() - DocbowProfile.objects.create( - user=result[-1], - personal_email='personal-email-user-%s@example.com' % i) + DocbowProfile.objects.create(user=result[-1], personal_email='personal-email-user-%s@example.com' % i) return result @pytest.fixture def admin(db): - user = User.objects.create( - username='admin', email='admin@localhost', is_staff=True, is_superuser=True) + user = User.objects.create(username='admin', email='admin@localhost', is_staff=True, is_superuser=True) user.set_password('password') user.save() return user @@ -45,7 +39,6 @@ def admin(db): @pytest.fixture def app(django_app): - def login(username='user-1', password='password'): login_page = django_app.get('/accounts/login/') login_form = login_page.forms[0] diff --git a/tests/main/test_commands.py b/tests/main/test_commands.py index 7a8de9c..981d4f8 100644 --- a/tests/main/test_commands.py +++ b/tests/main/test_commands.py @@ -45,14 +45,15 @@ def test_forward_docs_date_filter(app, users, settings, filetypes): assert Document.objects.filter(sender=sender).count() == 3 management.call_command( - 'forward-docs', '%s' % recipient.pk, '%s' % new_user.pk, + 'forward-docs', + '%s' % recipient.pk, + '%s' % new_user.pk, startdate=(localtime(now()) - timedelta(days=20)), - enddate=(localtime(now()) + timedelta(days=20)) + enddate=(localtime(now()) + timedelta(days=20)), ) assert Document.objects.filter(sender=sender).count() == 4 - doc = Document.objects.filter(sender=sender).exclude(pk__in=[doc1.pk, doc2.pk, doc3.pk])\ - .first() + doc = Document.objects.filter(sender=sender).exclude(pk__in=[doc1.pk, doc2.pk, doc3.pk]).first() assert_can_see_doc(app, doc, new_user) diff --git a/tests/main/test_main.py b/tests/main/test_main.py index d4631f0..fa7f652 100644 --- a/tests/main/test_main.py +++ b/tests/main/test_main.py @@ -22,8 +22,19 @@ import pytest from docbow_project.docbow import app_settings, notification from docbow_project.docbow.models import ( - Document, AttachedFile, Mailbox, Notification, DocbowProfile, MailingList, generate_filename, - FileType, FileTypeAttachedFileKind, Delegation, all_emails, is_guest, NotificationPreference + Document, + AttachedFile, + Mailbox, + Notification, + DocbowProfile, + MailingList, + generate_filename, + FileType, + FileTypeAttachedFileKind, + Delegation, + all_emails, + is_guest, + NotificationPreference, ) from docbow_project.docbow.notification import MailNotifier from docbow_project.docbow.upload_views import file_response @@ -40,15 +51,10 @@ def create_users(num_user): """ result = [] for i in range(num_user): - result.append( - User.objects.create( - username='user-%s' % i, - email='user-%s@example.com' % i)) + result.append(User.objects.create(username='user-%s' % i, email='user-%s@example.com' % i)) result[-1].set_password('password') result[-1].save() - DocbowProfile.objects.create( - user=result[-1], - personal_email='personal-email-user-%s@example.com' % i) + DocbowProfile.objects.create(user=result[-1], personal_email='personal-email-user-%s@example.com' % i) return result @@ -59,8 +65,7 @@ def users_fixture(): @pytest.fixture def admin(): - admin = User.objects.create( - username='admin', email='admin@localhost', is_superuser=True, is_staff=True) + admin = User.objects.create(username='admin', email='admin@localhost', is_superuser=True, is_staff=True) admin.set_password('admin') admin.save() return admin @@ -83,6 +88,7 @@ def filetype_fixtures(): class MailingListTreeTestCase(TestCase): '''Test mailing lists member resolution in a classical tree setup.''' + def setUp(self): self.users = [] self.mls = [] @@ -90,11 +96,10 @@ class MailingListTreeTestCase(TestCase): self.users.append(User.objects.create(username='%s' % i)) sublist = [] for i in range(19, -1, -1): - self.mls.insert(0, MailingList.objects.create( - name='%s' % i)) + self.mls.insert(0, MailingList.objects.create(name='%s' % i)) self.mls[0].members.set([self.users[i]]) self.mls[0].mailing_list_members.set(sublist) - sublist = [ self.mls[0] ] + sublist = [self.mls[0]] def test_mailing_list_recursive(self): """ @@ -102,7 +107,7 @@ class MailingListTreeTestCase(TestCase): """ for i in range(19, -1, -1): members = self.mls[i].recursive_members() - self.assertEqual(len(members), 20-i) + self.assertEqual(len(members), 20 - i) def test_mailing_list_recursive_with_origin(self): """ @@ -110,13 +115,14 @@ class MailingListTreeTestCase(TestCase): """ for i in range(19, -1, -1): mwo = self.mls[i].recursive_members_with_origin() - self.assertEqual(len(mwo), 20-i) + self.assertEqual(len(mwo), 20 - i) for j in range(i, 20): - self.assertEqual(mwo[self.users[j]], set(self.mls[i:j+1])) + self.assertEqual(mwo[self.users[j]], set(self.mls[i : j + 1])) class MailingListCycle(TestCase): '''Test mailing lists member resolution in a cyclic setup.''' + def setUp(self): self.users = [] self.mls = [] @@ -124,11 +130,10 @@ class MailingListCycle(TestCase): self.users.append(User.objects.create(username='%s' % i)) sublist = [] for i in range(19, -1, -1): - self.mls.insert(0, MailingList.objects.create( - name='%s' % i)) + self.mls.insert(0, MailingList.objects.create(name='%s' % i)) self.mls[0].members.set([self.users[i]]) self.mls[0].mailing_list_members.set(sublist) - sublist = [ self.mls[0] ] + sublist = [self.mls[0]] self.mls[19].mailing_list_members.set([self.mls[0]]) def test_mailing_list_recursive(self): @@ -152,31 +157,29 @@ class MailingListCycle(TestCase): @override_settings(MEDIA_ROOT=MEDIA_ROOT) class BaseTestCase(TestCase): - COUNT = 10 + COUNT = 10 - def setUp(self): - self.setUpUsers() - self.setUpDocuments() + def setUp(self): + self.setUpUsers() + self.setUpDocuments() - def setUpUsers(self): - self.users = create_users(self.COUNT) - self.filetypes = create_filetypes(self.COUNT) + def setUpUsers(self): + self.users = create_users(self.COUNT) + self.filetypes = create_filetypes(self.COUNT) + + def setUpDocuments(self): + self.documents = [] + for i in range(self.COUNT): + self.documents.append( + Document.objects.create(sender=self.users[(i + 2) % self.COUNT], filetype=self.filetypes[i]) + ) + self.documents[-1].to_user.set([self.users[i % self.COUNT], self.users[(i + 1) % self.COUNT]]) + for j in range(2): + attached_file = AttachedFile(name='file%s' % j, document=self.documents[-1], kind=None) + attached_file.content.save('file%s.pdf' % j, ContentFile('coucou')) + attached_file.save() + self.documents[-1].post() - def setUpDocuments(self): - self.documents = [] - for i in range(self.COUNT): - self.documents.append( - Document.objects.create(sender=self.users[(i+2) % self.COUNT], - filetype=self.filetypes[i])) - self.documents[-1].to_user.set([self.users[i % self.COUNT], - self.users[(i+1) % self.COUNT]]) - for j in range(2): - attached_file = AttachedFile(name='file%s' % j, - document=self.documents[-1], - kind=None) - attached_file.content.save('file%s.pdf' % j, ContentFile('coucou')) - attached_file.save() - self.documents[-1].post() class BasicTestCase(BaseTestCase): def test_notification_mail(self): @@ -194,138 +197,134 @@ class BasicTestCase(BaseTestCase): match = MAIL_LINK_RE.search(message.body) self.assertIsNotNone(match) + class UtilsTestCase(BaseTestCase): - def setUp(self): - super(UtilsTestCase, self).setUp() - self.user1, self.user2 = self.users[:2] - self.user2.docbowprofile.is_guest = True - self.user2.docbowprofile.save() + def setUp(self): + super(UtilsTestCase, self).setUp() + self.user1, self.user2 = self.users[:2] + self.user2.docbowprofile.is_guest = True + self.user2.docbowprofile.save() - def test_generate_filename(self): - assert generate_filename(None, 'xxx.pdf') != generate_filename(None, 'xxx.pdf') + def test_generate_filename(self): + assert generate_filename(None, 'xxx.pdf') != generate_filename(None, 'xxx.pdf') - def test_all_emails(self): - assert set(all_emails(self.user1)) == \ - set((self.user1.email, self.user1.docbowprofile.personal_email)) + def test_all_emails(self): + assert set(all_emails(self.user1)) == set((self.user1.email, self.user1.docbowprofile.personal_email)) - def test_document_manager(self): - with self.assertNumQueries(1): - # With django 1.8, there is no more - # eager loading of related AttachedFile objects - # so only one SQL query here - list(Document.objects.all()) + def test_document_manager(self): + with self.assertNumQueries(1): + # With django 1.8, there is no more + # eager loading of related AttachedFile objects + # so only one SQL query here + list(Document.objects.all()) - def test_document_accessors(self): - document = self.documents[0] - filenames = set([filename.strip() for filename in document.filenames().split(',')]) - self.assertEqual(filenames, set(['file0.pdf', 'file1.pdf'])) - self.assertEqual(set(document.user_human_to()), - set(['user-0', 'user-1'])) - self.assertEqual(document.group_human_to(), []) - self.assertEqual(set(document.human_to()), - set(['user-0', 'user-1'])) - self.assertIsNotNone(document.filename_links()) - assert set(document.to()) == set((self.user1, self.user2)) - assert dict(document.to_with_origin()) == \ - {self.user1: set([ '--direct--' ]), self.user2: set(['--direct--'])} - self.assertEqual(sorted(map(lambda x: x.pk, document.delivered_to())), - sorted([ self.user1.pk, self.user2.pk])) + def test_document_accessors(self): + document = self.documents[0] + filenames = set([filename.strip() for filename in document.filenames().split(',')]) + self.assertEqual(filenames, set(['file0.pdf', 'file1.pdf'])) + self.assertEqual(set(document.user_human_to()), set(['user-0', 'user-1'])) + self.assertEqual(document.group_human_to(), []) + self.assertEqual(set(document.human_to()), set(['user-0', 'user-1'])) + self.assertIsNotNone(document.filename_links()) + assert set(document.to()) == set((self.user1, self.user2)) + assert dict(document.to_with_origin()) == { + self.user1: set(['--direct--']), + self.user2: set(['--direct--']), + } + self.assertEqual( + sorted(map(lambda x: x.pk, document.delivered_to())), sorted([self.user1.pk, self.user2.pk]) + ) - def test_is_guest(self): - self.assertTrue(is_guest(self.user2)) + def test_is_guest(self): + self.assertTrue(is_guest(self.user2)) class DelegatesTestCase(BaseTestCase): - def setUp(self): - self.setUpUsers() - self.delegate = delegate = User(username='delegate') - delegate.set_password('delegate') - delegate.save() - Delegation.objects.create(by=self.users[0], to=delegate) - Delegation.objects.create(by=self.users[1], to=delegate) - self.guest0 = User(username='user-0-1') - self.guest0.set_password('guest') - self.guest0.save() - DocbowProfile.objects.create( - user=self.guest0, is_guest=True) - Delegation.objects.create( - by=self.users[0], to=self.guest0) - self.guest1 = User(username='user-1-1') - self.guest1.set_password('guest') - self.guest1.save() - DocbowProfile.objects.create( - user=self.guest1, is_guest=True) - Delegation.objects.create( - by=self.users[1], to=self.guest1) + def setUp(self): + self.setUpUsers() + self.delegate = delegate = User(username='delegate') + delegate.set_password('delegate') + delegate.save() + Delegation.objects.create(by=self.users[0], to=delegate) + Delegation.objects.create(by=self.users[1], to=delegate) + self.guest0 = User(username='user-0-1') + self.guest0.set_password('guest') + self.guest0.save() + DocbowProfile.objects.create(user=self.guest0, is_guest=True) + Delegation.objects.create(by=self.users[0], to=self.guest0) + self.guest1 = User(username='user-1-1') + self.guest1.set_password('guest') + self.guest1.save() + DocbowProfile.objects.create(user=self.guest1, is_guest=True) + Delegation.objects.create(by=self.users[1], to=self.guest1) - def test_inbox_by_document(self): - document = Document.objects.create( - sender=self.users[0], filetype=self.filetypes[0]) - document.to_user.set([self.users[1]]) - attached_file = AttachedFile(name='file-private-flag.pdf', document=document, kind=None) - attached_file.content.save('file-private-flag.pdf', ContentFile('coucou')) - attached_file.save() - document.post() - c = Client() - c.login(username='delegate', password='delegate') - response = c.get('/inbox/%s/' % document.pk) - self.assertEqual(response.status_code, 200) + def test_inbox_by_document(self): + document = Document.objects.create(sender=self.users[0], filetype=self.filetypes[0]) + document.to_user.set([self.users[1]]) + attached_file = AttachedFile(name='file-private-flag.pdf', document=document, kind=None) + attached_file.content.save('file-private-flag.pdf', ContentFile('coucou')) + attached_file.save() + document.post() + c = Client() + c.login(username='delegate', password='delegate') + response = c.get('/inbox/%s/' % document.pk) + self.assertEqual(response.status_code, 200) - def test_private_flag(self): - document = Document.objects.create(sender=self.users[0], filetype=self.filetypes[0], private=True) - document.to_user.set([self.users[1]]) - attached_file = AttachedFile(name='file-private-flag.pdf', document=document, kind=None) - attached_file.content.save('file-private-flag.pdf', ContentFile('coucou')) - attached_file.save() - document.post() - self.assertEqual(Mailbox.objects.count(), 2) + def test_private_flag(self): + document = Document.objects.create(sender=self.users[0], filetype=self.filetypes[0], private=True) + document.to_user.set([self.users[1]]) + attached_file = AttachedFile(name='file-private-flag.pdf', document=document, kind=None) + attached_file.content.save('file-private-flag.pdf', ContentFile('coucou')) + attached_file.save() + document.post() + self.assertEqual(Mailbox.objects.count(), 2) - # check user-1 sees document in its outbox - c = Client() - c.login(username='user-0', password='password') + # check user-1 sees document in its outbox + c = Client() + c.login(username='user-0', password='password') - response = c.get('/outbox/') - self.assertIn('/outbox/%s/' % document.pk, force_text(response.content)) - response = c.get('/outbox/%s/' % document.pk) - self.assertEqual(response.status_code, 200) + response = c.get('/outbox/') + self.assertIn('/outbox/%s/' % document.pk, force_text(response.content)) + response = c.get('/outbox/%s/' % document.pk) + self.assertEqual(response.status_code, 200) - response = c.get('/inbox/') - self.assertNotIn('/inbox/%s/' % document.pk, force_text(response.content)) - response = c.get('/inbox/%s/' % document.pk) - self.assertEqual(response.status_code, 302) + response = c.get('/inbox/') + self.assertNotIn('/inbox/%s/' % document.pk, force_text(response.content)) + response = c.get('/inbox/%s/' % document.pk) + self.assertEqual(response.status_code, 302) - # check user-1 sees document in its inbox - c = Client() - c.login(username='user-1', password='password') + # check user-1 sees document in its inbox + c = Client() + c.login(username='user-1', password='password') - response = c.get('/inbox/') - self.assertIn('/inbox/%s/' % document.pk, force_text(response.content)) - response = c.get('/inbox/%s/' % document.pk) - self.assertEqual(response.status_code, 200) + response = c.get('/inbox/') + self.assertIn('/inbox/%s/' % document.pk, force_text(response.content)) + response = c.get('/inbox/%s/' % document.pk) + self.assertEqual(response.status_code, 200) - response = c.get('/outbox/%s/' % document.pk) - self.assertEqual(response.status_code, 302) - response = c.get('/outbox/') - self.assertNotIn('/outbox/%s/' % document.pk, force_text(response.content)) + response = c.get('/outbox/%s/' % document.pk) + self.assertEqual(response.status_code, 302) + response = c.get('/outbox/') + self.assertNotIn('/outbox/%s/' % document.pk, force_text(response.content)) - # check delegate sees nothing - c = Client() - c.login(username='delegate', password='delegate') + # check delegate sees nothing + c = Client() + c.login(username='delegate', password='delegate') - response = c.get('/inbox/') - self.assertNotIn('/inbox/%s/' % document.pk, force_text(response.content)) - response = c.get('/inbox/%s/' % document.pk) - self.assertEqual(response.status_code, 302) + response = c.get('/inbox/') + self.assertNotIn('/inbox/%s/' % document.pk, force_text(response.content)) + response = c.get('/inbox/%s/' % document.pk) + self.assertEqual(response.status_code, 302) - response = c.get('/outbox/') - self.assertNotIn('/outbox/%s/' % document.pk, force_text(response.content)) - response = c.get('/outbox/%s/' % document.pk) - self.assertEqual(response.status_code, 302) + response = c.get('/outbox/') + self.assertNotIn('/outbox/%s/' % document.pk, force_text(response.content)) + response = c.get('/outbox/%s/' % document.pk) + self.assertEqual(response.status_code, 302) - # check notifications - with self.assertRaises(Notification.DoesNotExist): - Notification.objects.get(user=self.delegate, document=document) - Notification.objects.get(user=self.users[1], document=document) + # check notifications + with self.assertRaises(Notification.DoesNotExist): + Notification.objects.get(user=self.delegate, document=document) + Notification.objects.get(user=self.users[1], document=document) class DummyNotifier(notification.BaseNotifier): @@ -346,15 +345,11 @@ class NotificationTestCase(BaseTestCase): def test_notification_preferences(self): NotificationPreference.objects.create( - user=self.users[0], - kind=DummyNotifier.key, - filetype=self.filetypes[0], - value=False) + user=self.users[0], kind=DummyNotifier.key, filetype=self.filetypes[0], value=False + ) NotificationPreference.objects.create( - user=self.users[1], - kind=DummyNotifier.key, - filetype=self.filetypes[1], - value=False) + user=self.users[1], kind=DummyNotifier.key, filetype=self.filetypes[1], value=False + ) DummyNotifier.notifications = [] with mock.patch('docbow_project.docbow.notification.get_notifiers') as MockClass: @@ -365,17 +360,20 @@ class NotificationTestCase(BaseTestCase): for notif in DummyNotifier.notifications: with self.assertRaises(NotificationPreference.DoesNotExist): NotificationPreference.objects.get( - user=notif.user, - filetype=notif.document.filetype, - kind=DummyNotifier.key, - value=False) + user=notif.user, filetype=notif.document.filetype, kind=DummyNotifier.key, value=False + ) @override_settings(MEDIA_ROOT=MEDIA_ROOT) class NotificationToDelegatesTestCase(TestCase): - def send_document(self, sender, user_recipients=[], list_recipients=[], - filetype_name='dummy filetype', - names_and_contents=(('dummy.pdf', 'dummy content'),)): + def send_document( + self, + sender, + user_recipients=[], + list_recipients=[], + filetype_name='dummy filetype', + names_and_contents=(('dummy.pdf', 'dummy content'),), + ): filetype, created = FileType.objects.get_or_create(name=filetype_name) document = Document.objects.create(sender=sender, filetype=filetype) @@ -383,8 +381,7 @@ class NotificationToDelegatesTestCase(TestCase): document.to_list.set(list_recipients) attached_files = [] for name, content in names_and_contents: - attached_file = AttachedFile(name=name, - document=document, kind=None) + attached_file = AttachedFile(name=name, document=document, kind=None) attached_file.content.save(name, ContentFile(content)) attached_file.save() attached_files.append(attached_file) @@ -405,8 +402,9 @@ class NotificationToDelegatesTestCase(TestCase): notification.process_notifications() self.assertEqual(len(DummyNotifier.notifications), 2) - self.assertEqual(set([notif.user for notif in - DummyNotifier.notifications]), set([recipient, delegate])) + self.assertEqual( + set([notif.user for notif in DummyNotifier.notifications]), set([recipient, delegate]) + ) delegate.is_active = False delegate.save() @@ -418,7 +416,6 @@ class NotificationToDelegatesTestCase(TestCase): class AddListTestCase(BaseTestCase): - def test_add_mailing_list(self): management.call_command('add-list', 'some-ml-name') ml_list = MailingList.objects.all() @@ -427,17 +424,22 @@ class AddListTestCase(BaseTestCase): @pytest.mark.django_db -def test_send_file( - users_fixture, filetype_fixtures, settings, - tmpdir, monkeypatch): +def test_send_file(users_fixture, filetype_fixtures, settings, tmpdir, monkeypatch): settings.MEDIA_ROOT = tmpdir.strpath monkeypatch.chdir(tmpdir) filetosend = tmpdir.join('filetosend.txt') filetosend.write("file content") management.call_command( - 'sendfile', '--sender', 'user-1', '--to-user', 'user-2', - '--filetype', 'filetype-1', '--description', 'yes we can', - 'filetosend.txt' + 'sendfile', + '--sender', + 'user-1', + '--to-user', + 'user-2', + '--filetype', + 'filetype-1', + '--description', + 'yes we can', + 'filetosend.txt', ) docs = Document.objects.all() assert len(docs) == 1 @@ -468,12 +470,13 @@ def test_signals(users_fixture, settings, monkeypatch, tmpdir): return {'user': 'test-user', 'ip': '0.0.0.0'} import docbow_project.docbow.signals + user0 = users_fixture[0] - monkeypatch.setattr( - docbow_project.docbow.signals, 'middleware', MockMiddleware()) + monkeypatch.setattr(docbow_project.docbow.signals, 'middleware', MockMiddleware()) # We just need no exception raised docbow_project.docbow.signals.modified_data( - sender=User, instance=user0, created=False, raw=False, using='default') + sender=User, instance=user0, created=False, raw=False, using='default' + ) def test_file_response(tmpdir): @@ -500,7 +503,8 @@ def test_file_match_mime_types(tmpdir): def test_filetype_attached_file_kind(db, tmpdir): ft = FileType.objects.create(name='some file type') fta = FileTypeAttachedFileKind.objects.create( - name='text-plain', file_type=ft, mime_types='text/plain', position=1) + name='text-plain', file_type=ft, mime_types='text/plain', position=1 + ) text_file = tmpdir.join('aaa.txt') with text_file.open('w') as f: @@ -520,6 +524,7 @@ def test_password_reset(users_fixture, settings, monkeypatch, client): client.login(username='user-1@example.com', password='password') import django.contrib.auth.forms + email_message = mock.Mock() email_message_factory = mock.Mock(return_value=email_message) @@ -558,9 +563,7 @@ def test_password_reset_confirm_post(users_fixture, client): redirect_url = '/accounts/password/reset/confirm/%s/set-password/' % uid assert response['Location'].endswith(redirect_url) - response = client.post( - redirect_url, - {'new_password1': 'newpass', 'new_password2': 'newpass'}) + response = client.post(redirect_url, {'new_password1': 'newpass', 'new_password2': 'newpass'}) assert response.status_code == 302 assert response['Location'].endswith('/accounts/password/reset/complete/') @@ -588,13 +591,12 @@ def test_delegate_login(client): delegate = User.objects.create(username='recipient-1') delegate.set_password('password') delegate.save() - DocbowProfile.objects.create( - user=delegate, is_guest=True) + DocbowProfile.objects.create(user=delegate, is_guest=True) Delegation.objects.create(by=recipient, to=delegate) response = client.post( - '/accounts/login/?next=/inbox/', - {'username': 'recipient-1', 'password': 'password'}) + '/accounts/login/?next=/inbox/', {'username': 'recipient-1', 'password': 'password'} + ) assert response.status_code == 302 assert response['Location'].endswith('inbox/') @@ -607,12 +609,11 @@ def test_delegate_login_hyphen_in_user_name(client): delegate = User.objects.create(username='recipient-tricky-1') delegate.set_password('password') delegate.save() - DocbowProfile.objects.create( - user=delegate, is_guest=True) + DocbowProfile.objects.create(user=delegate, is_guest=True) Delegation.objects.create(by=recipient, to=delegate) response = client.post( - '/accounts/login/?next=/inbox/', - {'username': 'recipient-tricky-1', 'password': 'password'}) + '/accounts/login/?next=/inbox/', {'username': 'recipient-tricky-1', 'password': 'password'} + ) assert response.status_code == 302 assert response['Location'].endswith('inbox/') diff --git a/tests/main/test_views.py b/tests/main/test_views.py index 49df81a..16048b7 100644 --- a/tests/main/test_views.py +++ b/tests/main/test_views.py @@ -9,7 +9,12 @@ import pytest from webtest import Upload from docbow_project.docbow.models import ( - AttachedFile, DeletedDocument, Document, FileType, MailingList, FileTypeAttachedFileKind + AttachedFile, + DeletedDocument, + Document, + FileType, + MailingList, + FileTypeAttachedFileKind, ) from utils import assert_can_see_doc, assert_cannot_see_doc, send_file @@ -102,8 +107,7 @@ def test_sendfile_selector(app, filetypes, users): def test_sendfile(app, filetypes, users, settings): settings.MEDIA_ROOT = MEDIA_ROOT sender = User.objects.get(username='user-1') - recipient1, recipient2 = \ - User.objects.get(username='user-2'), User.objects.get(username='user-3') + recipient1, recipient2 = User.objects.get(username='user-2'), User.objects.get(username='user-3') app.login() ft = FileType.objects.first() resp = app.get('/send_file/%s/' % ft.pk) @@ -127,8 +131,7 @@ def test_sendfile_mailing_list(app, filetypes, users, settings): settings.MEDIA_ROOT = MEDIA_ROOT sender = User.objects.get(username='user-1') ml = MailingList.objects.create(name='test') - recipient1, recipient2 = \ - User.objects.get(username='user-2'), User.objects.get(username='user-3') + recipient1, recipient2 = User.objects.get(username='user-2'), User.objects.get(username='user-3') ml.members.add(recipient1, recipient2) ft = FileType.objects.first() @@ -364,8 +367,9 @@ def test_upload_file(app, users, settings): app.login(username='user-1') upload_id = str(uuid.uuid4()) resp = app.post( - '/upload/%s/' % upload_id, headers={'Accept': 'application/json'}, - upload_files=[('file', 'readme.rst', b'data')] + '/upload/%s/' % upload_id, + headers={'Accept': 'application/json'}, + upload_files=[('file', 'readme.rst', b'data')], ) assert resp.status_code == 200 json = resp.json diff --git a/tests/pfwb/test_pfwb.py b/tests/pfwb/test_pfwb.py index 088fb1d..f36c90e 100644 --- a/tests/pfwb/test_pfwb.py +++ b/tests/pfwb/test_pfwb.py @@ -18,6 +18,7 @@ import mock MEDIA_ROOT = tempfile.mkdtemp() + @contextmanager def captured_output(): new_out, new_err = StringIO(), StringIO() @@ -28,6 +29,7 @@ def captured_output(): finally: sys.stdout, sys.stderr = old_out, old_err + class stderr_output(object): def __init__(self, output): self.output = output @@ -39,8 +41,10 @@ class stderr_output(object): ret = func(testcase, *args, **kwargs) testcase.assertEqual(self.output, err.getvalue()) return ret + return f + class stdout_output(object): def __init__(self, output): self.output = output @@ -52,16 +56,17 @@ class stdout_output(object): ret = func(testcase, *args, **kwargs) testcase.assertEqual(self.output, out.getvalue()) return ret + return f + from django.test import TestCase from django.test.utils import override_settings from django.core import management from django.contrib.auth.models import User from docbow_project.pfwb.models import TabellioDocType, PloneFileType -from docbow_project.docbow.models import (FileType, MailingList, Document, - AttachedFile) +from docbow_project.docbow.models import FileType, MailingList, Document, AttachedFile from django_journal.models import Journal @@ -71,7 +76,6 @@ RECIPIENT_LIST_EMAIL = 'liste-ma-liste@example.com' class MockUrlib(object): - def urlopen(self, url): return self @@ -92,28 +96,27 @@ mockurllib = MockUrlib() class SendMailTestCase(TestCase): def setUp(self): self.pjd_filetype = FileType.objects.create(name='PJD', id=2) - self.tabellio_doc_type = TabellioDocType.objects.create(filetype=self.pjd_filetype, - tabellio_doc_type='PJD') + self.tabellio_doc_type = TabellioDocType.objects.create( + filetype=self.pjd_filetype, tabellio_doc_type='PJD' + ) self.expedition_user = User.objects.create(username='expedition', id=1) self.to_user = User.objects.create(username='recipient', email=RECIPIENT_EMAIL, id=2) self.to_list = MailingList.objects.create(name='ma liste') self.to_list.members.add(self.to_user) - def send_tabellio_doc2(self, content, expedition_email, recipient_email, doc_type, - subject): - content = content % { 'expedition_email': expedition_email, - 'recipient_email': recipient_email, - 'doc_type': doc_type, - 'subject': subject - } + def send_tabellio_doc2(self, content, expedition_email, recipient_email, doc_type, subject): + content = content % { + 'expedition_email': expedition_email, + 'recipient_email': recipient_email, + 'doc_type': doc_type, + 'subject': subject, + } with tempfile.NamedTemporaryFile() as f: f.write(force_bytes(content)) f.flush() - management.call_command('sendmail', recipient_email, file=f.name, - sender=expedition_email) + management.call_command('sendmail', recipient_email, file=f.name, sender=expedition_email) - def send_tabellio_doc(self, expedition_email, recipient_email, doc_type, - subject): + def send_tabellio_doc(self, expedition_email, recipient_email, doc_type, subject): content = '''\ Message-ID: <232323232@example.com> From: %(expedition_email)s @@ -129,8 +132,7 @@ Status: RO Coucou ''' - self.send_tabellio_doc2(content, expedition_email, recipient_email, - doc_type, subject) + self.send_tabellio_doc2(content, expedition_email, recipient_email, doc_type, subject) @stderr_output('7.7.1 No sender\n7.7.1 No sender\n') def test_fail_sender(self): @@ -142,7 +144,8 @@ Coucou @stderr_output('7.7.1 Mail is missing a Message-ID\n') def test_fail_on_missing_message_id(self): with self.assertRaises(SystemExit): - self.send_tabellio_doc2('''From: %(expedition_email)s + self.send_tabellio_doc2( + '''From: %(expedition_email)s To: %(recipient_email)s Subject: %(subject)s MIME-Version: 1.0 @@ -153,13 +156,19 @@ X-Tabellio-Doc-Type: %(doc_type)s X-Tabellio-Doc-URL: https://buildmedia.readthedocs.org/media/pdf/django/2.2.x/django.pdf Status: RO -Coucou''', EXPEDITION_EMAIL, RECIPIENT_EMAIL, 'PJD', 'Mouais: monfichier.pdf') +Coucou''', + EXPEDITION_EMAIL, + RECIPIENT_EMAIL, + 'PJD', + 'Mouais: monfichier.pdf', + ) @stderr_output('7.7.1 Mail is missing a subject\n') def test_fail_on_missing_subject(self): with mock.patch('docbow_project.pfwb.management.commands.sendmail.urlopen', mockurllib.urlopen): with self.assertRaises(SystemExit): - self.send_tabellio_doc2('''From: %(expedition_email)s + self.send_tabellio_doc2( + '''From: %(expedition_email)s Message-ID: <232323232@example.com> To: %(recipient_email)s MIME-Version: 1.0 @@ -170,7 +179,12 @@ X-Tabellio-Doc-Type: %(doc_type)s X-Tabellio-Doc-URL: https://buildmedia.readthedocs.org/media/pdf/django/2.2.x/django.pdf Status: RO -Coucou''', EXPEDITION_EMAIL, RECIPIENT_EMAIL, 'PJD', 'Mouais: monfichier.pdf') +Coucou''', + EXPEDITION_EMAIL, + RECIPIENT_EMAIL, + 'PJD', + 'Mouais: monfichier.pdf', + ) @stderr_output('7.7.1 Filename cannot be extracted from the subject\n') def test_fail_on_missing_colon_in_subject(self): @@ -178,7 +192,9 @@ Coucou''', EXPEDITION_EMAIL, RECIPIENT_EMAIL, 'PJD', 'Mouais: monfichier.pdf') with self.assertRaises(SystemExit): self.send_tabellio_doc(EXPEDITION_EMAIL, RECIPIENT_EMAIL, 'PJD', 'Mouais') - @stderr_output("7.7.1 The email sent contains many errors:\n - Recipient 'xxx@xx.com' is not an user of the platform\n") + @stderr_output( + "7.7.1 The email sent contains many errors:\n - Recipient 'xxx@xx.com' is not an user of the platform\n" + ) def test_fail_on_unknown_user(self): with mock.patch('docbow_project.pfwb.management.commands.sendmail.urlopen', mockurllib.urlopen): with self.assertRaises(SystemExit): @@ -189,10 +205,12 @@ Coucou''', EXPEDITION_EMAIL, RECIPIENT_EMAIL, 'PJD', 'Mouais: monfichier.pdf') with mock.patch('docbow_project.pfwb.management.commands.sendmail.urlopen', mockurllib.urlopen): self.assertEqual(FileType.objects.filter(name='Default').count(), 0) self.send_tabellio_doc(EXPEDITION_EMAIL, RECIPIENT_EMAIL, 'XXX', 'Mouais: monfichier.pdf') - self.assertEqual(Journal.objects.order_by('id')[0].message, 'unknown x-tabellio-doc-type XXX, using default filetype') + self.assertEqual( + Journal.objects.order_by('id')[0].message, + 'unknown x-tabellio-doc-type XXX, using default filetype', + ) self.assertEqual(FileType.objects.filter(name='Default').count(), 1) - @stderr_output('') def test_expedition_mode(self): """ @@ -214,7 +232,6 @@ Coucou''', EXPEDITION_EMAIL, RECIPIENT_EMAIL, 'PJD', 'Mouais: monfichier.pdf') assert Document.objects.get().to_user.count() == 1 assert Document.objects.get().to_list.count() == 0 - @stderr_output('') def test_expedition_mode_to_list(self): """ @@ -227,6 +244,7 @@ Coucou''', EXPEDITION_EMAIL, RECIPIENT_EMAIL, 'PJD', 'Mouais: monfichier.pdf') assert Document.objects.get().to_list.count() == 1 assert Document.objects.get().to_list.all()[0] == self.to_list + @override_settings(DOCBOW_PFWB_SENDMAIL_DEFAULT_TYPE_ID=1) @override_settings(DOCBOW_PFWB_SENDMAIL_DEFAULT_TYPE_NAME='Default') @override_settings(DOCBOW_PFWB_SENDMAIL_ATTACHED_FILE_EMAIL=EXPEDITION_EMAIL) @@ -236,8 +254,9 @@ Coucou''', EXPEDITION_EMAIL, RECIPIENT_EMAIL, 'PJD', 'Mouais: monfichier.pdf') class SendMailAttachedFileTestCase(TestCase): def setUp(self): self.pjd_filetype = FileType.objects.create(name='PJD', id=2) - self.tabellio_doc_type = TabellioDocType.objects.create(filetype=self.pjd_filetype, - tabellio_doc_type='PJD') + self.tabellio_doc_type = TabellioDocType.objects.create( + filetype=self.pjd_filetype, tabellio_doc_type='PJD' + ) self.expedition_user = User.objects.create(username='expedition', id=1) self.to_user = User.objects.create(username='recipient', email=RECIPIENT_EMAIL, id=2) self.to_list = MailingList.objects.create(name='ma liste') @@ -269,15 +288,17 @@ class SendMailAttachedFileTestCase(TestCase): @stderr_output('') def test_attached_file1(self): with tempfile.NamedTemporaryFile() as f: - f.write(self.build_message( - self.pjd_filetype, - EXPEDITION_EMAIL, - RECIPIENT_EMAIL, - 'coucou', - (('attached-file', 'content'),))) + f.write( + self.build_message( + self.pjd_filetype, + EXPEDITION_EMAIL, + RECIPIENT_EMAIL, + 'coucou', + (('attached-file', 'content'),), + ) + ) f.flush() - management.call_command('sendmail', RECIPIENT_EMAIL, file=f.name, - sender=EXPEDITION_EMAIL) + management.call_command('sendmail', RECIPIENT_EMAIL, file=f.name, sender=EXPEDITION_EMAIL) self.assertEqual(Document.objects.count(), 1) document = Document.objects.get() assert document.attached_files.count() == 1 @@ -288,6 +309,7 @@ class SendMailAttachedFileTestCase(TestCase): assert document.to_list.count() == 0 assert document.to_user.get() == self.to_user + @override_settings(MEDIA_ROOT=MEDIA_ROOT) class PushDocumentTestCase(TestCase): def setUp(self): @@ -295,6 +317,7 @@ class PushDocumentTestCase(TestCase): def tearDown(self): import shutil + shutil.rmtree(self.ged_dir) def test_push_document1(self): @@ -306,25 +329,23 @@ class PushDocumentTestCase(TestCase): FROM_USERNAME = 'from_user' FROM_FIRST_NAME = 'from_first_name' FROM_LAST_NAME = 'from_last_name' - self.from_user = User.objects.create(username=FROM_USERNAME, - first_name=FROM_FIRST_NAME, last_name=FROM_LAST_NAME) + self.from_user = User.objects.create( + username=FROM_USERNAME, first_name=FROM_FIRST_NAME, last_name=FROM_LAST_NAME + ) self.to_user = User.objects.create(username='to_user') self.filetype = FileType.objects.create(name='filetype') - self.plone_filetype = PloneFileType.objects.create(filetype=self.filetype, - plone_portal_type='plone-portal-type') + self.plone_filetype = PloneFileType.objects.create( + filetype=self.filetype, plone_portal_type='plone-portal-type' + ) DESCRIPTION = 'description' - self.document = Document.objects.create(sender=self.from_user, - filetype=self.filetype, - comment=DESCRIPTION) - self.attached_file = AttachedFile(name='attached-file', - document=self.document, kind=None) + self.document = Document.objects.create( + sender=self.from_user, filetype=self.filetype, comment=DESCRIPTION + ) + self.attached_file = AttachedFile(name='attached-file', document=self.document, kind=None) CONTENT = 'content' - self.attached_file.content.save('attached-file', - ContentFile(CONTENT)) - pattern1 = '{0}-*-{1}.json'.format(self.document.id, - self.attached_file.name) - pattern2 = '{0}-*-{1}'.format(self.document.id, - self.attached_file.name) + self.attached_file.content.save('attached-file', ContentFile(CONTENT)) + pattern1 = '{0}-*-{1}.json'.format(self.document.id, self.attached_file.name) + pattern2 = '{0}-*-{1}'.format(self.document.id, self.attached_file.name) files1 = glob(os.path.join(self.ged_dir, pattern1)) files2 = glob(os.path.join(self.ged_dir, pattern2)) assert len(files1) == 1 @@ -332,18 +353,18 @@ class PushDocumentTestCase(TestCase): with open(files2[0]) as f: assert f.read() == CONTENT import json + with open(files1[0]) as f: json_content = json.loads(f.read()) self.assertIsNotNone(json_content) assert json_content['document_id'] == self.document.id - assert json_content['plone_portal_type'] == \ - self.plone_filetype.plone_portal_type - assert json_content['title'] == \ - force_text(self.filetype) + assert json_content['plone_portal_type'] == self.plone_filetype.plone_portal_type + assert json_content['title'] == force_text(self.filetype) assert json_content['description'] == DESCRIPTION - assert json_content['sender'] == \ - u'{0} {1} ({2})'.format(FROM_FIRST_NAME, - FROM_LAST_NAME, FROM_USERNAME) + assert json_content['sender'] == u'{0} {1} ({2})'.format( + FROM_FIRST_NAME, FROM_LAST_NAME, FROM_USERNAME + ) + @override_settings(MEDIA_ROOT=MEDIA_ROOT) @override_settings(DOCBOW_PFWB_GED_DIRECTORY=None) @@ -355,21 +376,21 @@ class ArchiveTestCase(TestCase): FROM_USERNAME = 'from_user' FROM_FIRST_NAME = 'from_first_name' FROM_LAST_NAME = 'from_last_name' - self.from_user = User.objects.create(username=FROM_USERNAME, - first_name=FROM_FIRST_NAME, last_name=FROM_LAST_NAME) + self.from_user = User.objects.create( + username=FROM_USERNAME, first_name=FROM_FIRST_NAME, last_name=FROM_LAST_NAME + ) self.to_user = User.objects.create(username='to_user') self.filetype = FileType.objects.create(name='filetype') - self.plone_filetype = PloneFileType.objects.create(filetype=self.filetype, - plone_portal_type='plone-portal-type') + self.plone_filetype = PloneFileType.objects.create( + filetype=self.filetype, plone_portal_type='plone-portal-type' + ) DESCRIPTION = 'description' - self.document = Document.objects.create(sender=self.from_user, - filetype=self.filetype, - comment=DESCRIPTION) - self.attached_file = AttachedFile(name='attached-file', - document=self.document, kind=None) + self.document = Document.objects.create( + sender=self.from_user, filetype=self.filetype, comment=DESCRIPTION + ) + self.attached_file = AttachedFile(name='attached-file', document=self.document, kind=None) CONTENT = 'content' - self.attached_file.content.save('attached-file', - ContentFile(CONTENT)) + self.attached_file.content.save('attached-file', ContentFile(CONTENT)) self.document.post() def tearDown(self): @@ -386,16 +407,18 @@ class ArchiveTestCase(TestCase): management.call_command('archive2', self.archive_dir, 0) l = glob.glob(os.path.join(self.archive_dir, '*')) assert len(l) == 1 - self.assertTrue(l[0].split('T')[0], - datetime.datetime.today().isoformat()) + self.assertTrue(l[0].split('T')[0], datetime.datetime.today().isoformat()) archive_dir = os.path.join(self.archive_dir, l[0]) self.assertTrue(os.path.exists(os.path.join(archive_dir, 'doc'))) - self.assertTrue(os.path.exists(os.path.join(archive_dir, 'doc', - str(self.document.id)))) - self.assertTrue(os.path.exists(os.path.join(archive_dir, 'doc', - str(self.document.id), 'document.json'))) - self.assertTrue(os.path.exists(os.path.join(archive_dir, 'doc', - str(self.document.id), 'attached_file_%s.json' % - self.attached_file.id))) - self.assertTrue(os.path.exists(os.path.join(archive_dir, - 'journal.txt'))) + self.assertTrue(os.path.exists(os.path.join(archive_dir, 'doc', str(self.document.id)))) + self.assertTrue( + os.path.exists(os.path.join(archive_dir, 'doc', str(self.document.id), 'document.json')) + ) + self.assertTrue( + os.path.exists( + os.path.join( + archive_dir, 'doc', str(self.document.id), 'attached_file_%s.json' % self.attached_file.id + ) + ) + ) + self.assertTrue(os.path.exists(os.path.join(archive_dir, 'journal.txt'))) diff --git a/tests/pfwb/test_sync_tabellio.py b/tests/pfwb/test_sync_tabellio.py index 98d75e7..540a694 100644 --- a/tests/pfwb/test_sync_tabellio.py +++ b/tests/pfwb/test_sync_tabellio.py @@ -11,7 +11,15 @@ from sqlalchemy_utils import database_exists, create_database, drop_database from docbow_project.docbow.models import DocbowProfile, FileType, MailingList from docbow_project.pfwb.models import TabellioDocType from docbow_project.pfwb.tabellio import ( - Base, DBSession, TAdresse, TCom, TComppol, TPer, TPershistoline, TTypehistoper, TTypedoc + Base, + DBSession, + TAdresse, + TCom, + TComppol, + TPer, + TPershistoline, + TTypehistoper, + TTypedoc, ) @@ -75,8 +83,13 @@ def create_pers(session, first_name, last_name, adresse, st='XX'): def create_pershisto(session, pers, typehisto, desc_obj, fin=None): new_id = get_new_id(session, TPershistoline) res = TPershistoline( - id=new_id, pers=pers.id, type=typehisto.id, description=desc_obj.id, st='XX', - debut=datetime.now(), fin=fin + id=new_id, + pers=pers.id, + type=typehisto.id, + description=desc_obj.id, + st='XX', + debut=datetime.now(), + fin=fin, ) session.add(res) session.commit() @@ -274,9 +287,7 @@ def test_create_commission(session, db, ministres_list, parl_list): foo = MailingList.objects.get(name='Commission - foo') bar = MailingList.objects.get(name='Commission - bar') - user = User.objects.get( - first_name='John', last_name='Doe', email='john@doe.be', username='john.doe' - ) + user = User.objects.get(first_name='John', last_name='Doe', email='john@doe.be', username='john.doe') assert foo.members.count() == 1 assert user in foo.members.all() assert bar.members.count() == 0 @@ -301,12 +312,8 @@ def test_diable_commission(session, db, ministres_list, parl_list): def test_update_commission(session, db, ministres_list, parl_list): commission = MailingList.objects.create(name='Commission - foo') - user = User.objects.create( - first_name='John', last_name='Doe', email='john@doe.be', username='john.doe' - ) - old_user = User.objects.create( - first_name='Old', last_name='Man', email='old@man.be', username='old.man' - ) + user = User.objects.create(first_name='John', last_name='Doe', email='john@doe.be', username='john.doe') + old_user = User.objects.create(first_name='Old', last_name='Man', email='old@man.be', username='old.man') commission.members.add(user) commission.members.add(old_user) assert commission.members.count() == 2 @@ -342,9 +349,7 @@ def test_create_political(session, db, ministres_list, parl_list): def test_desactivate_innactive_political(session, db, ministres_list, parl_list): ml = MailingList.objects.create(name='Appartenance politique - xx') - old_user = User.objects.create( - first_name='Old', last_name='Man', email='old@man.be', username='old.man' - ) + old_user = User.objects.create(first_name='Old', last_name='Man', email='old@man.be', username='old.man') ml.members.add(old_user) assert ml.is_active assert ml.members.count() == 1 @@ -367,9 +372,7 @@ def test_desactivate_innactive_political(session, db, ministres_list, parl_list) def test_remove_member_from_political(session, db, ministres_list, parl_list): ml = MailingList.objects.create(name='Appartenance politique - green') - old_user = User.objects.create( - first_name='Old', last_name='Man', email='old@man.be', username='old.man' - ) + old_user = User.objects.create(first_name='Old', last_name='Man', email='old@man.be', username='old.man') ml.members.add(old_user) assert ml.is_active assert ml.members.count() == 1 @@ -395,11 +398,13 @@ def test_document_types(session, db, ministres_list, parl_list): TabellioDocType.objects.create(filetype=ft, tabellio_doc_type='doc-x') assert TabellioDocType.objects.count() == 1 - session.add_all([ - TTypedoc(id='doc-0', descr='foo', st='st', fno=True, fnodoc=True, listnum=True), - TTypedoc(id='doc-1', descr='bar', st='st', fno=True, fnodoc=True, listnum=True), - TTypedoc(id='doc-x', descr='new', st='st', fno=True, fnodoc=True, listnum=True) - ]) + session.add_all( + [ + TTypedoc(id='doc-0', descr='foo', st='st', fno=True, fnodoc=True, listnum=True), + TTypedoc(id='doc-1', descr='bar', st='st', fno=True, fnodoc=True, listnum=True), + TTypedoc(id='doc-x', descr='new', st='st', fno=True, fnodoc=True, listnum=True), + ] + ) session.commit() management.call_command('sync-tabellio', '--verbosity', '2') diff --git a/tests/pfwbmellon/settings.py b/tests/pfwbmellon/settings.py index f4b3255..10ca37c 100644 --- a/tests/pfwbmellon/settings.py +++ b/tests/pfwbmellon/settings.py @@ -9,8 +9,6 @@ MELLON_ADAPTER = ('docbow_project.pfwb.mellon_adapter.PFWBMellonAdapter',) DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'TEST': { - 'NAME': 'docbow-test-%s' % os.environ.get("BRANCH_NAME", "").replace('/', '-')[:63], - }, + 'TEST': {'NAME': 'docbow-test-%s' % os.environ.get("BRANCH_NAME", "").replace('/', '-')[:63],}, } } diff --git a/tests/pw/test_pw.py b/tests/pw/test_pw.py index 9446592..b89c11a 100644 --- a/tests/pw/test_pw.py +++ b/tests/pw/test_pw.py @@ -16,6 +16,7 @@ from django.utils.six import StringIO MEDIA_ROOT = tempfile.mkdtemp() + @contextmanager def captured_output(): new_out, new_err = StringIO(), StringIO() @@ -26,6 +27,7 @@ def captured_output(): finally: sys.stdout, sys.stderr = old_out, old_err + class stderr_output(object): def __init__(self, output): self.output = output @@ -37,8 +39,10 @@ class stderr_output(object): ret = func(testcase, *args, **kwargs) testcase.assertEqual(self.output, err.getvalue()) return ret + return f + class stdout_output(object): def __init__(self, output): self.output = output @@ -50,19 +54,22 @@ class stdout_output(object): ret = func(testcase, *args, **kwargs) testcase.assertEqual(self.output, out.getvalue()) return ret + return f + from django.test import TestCase from django.test.utils import override_settings from django.core import management from django.contrib.auth.models import User -from docbow_project.docbow.models import (FileType, MailingList, Document) +from docbow_project.docbow.models import FileType, MailingList, Document EXPEDITION_EMAIL = 'expedition@example.com' RECIPIENT_EMAIL = 'recipient@example.com' PRIVATE_RECIPIENT_EMAIL = 'recipient-private@example.com' + class MessageFile(object): def __init__(self, filetype, to_addr, from_addr, content, attached_files, headers={}): self.filetype = filetype @@ -116,10 +123,16 @@ class SendMailAttachedFileTestCase(TestCase): @stderr_output('') def test_attached_file_with_forced_sender(self): - with MessageFile(self.pjd_filetype, 'whatthefuck@example.com', 'whatthefuck-too@example.com', - 'coucou', (('attached-file', 'content'),)) as f: - management.call_command('sendmail', RECIPIENT_EMAIL, file=f.name, - sender=self.expedition_user.username) + with MessageFile( + self.pjd_filetype, + 'whatthefuck@example.com', + 'whatthefuck-too@example.com', + 'coucou', + (('attached-file', 'content'),), + ) as f: + management.call_command( + 'sendmail', RECIPIENT_EMAIL, file=f.name, sender=self.expedition_user.username + ) self.assertEqual(Document.objects.count(), 1) document = Document.objects.get() assert document.comment == 'coucou' @@ -132,10 +145,17 @@ class SendMailAttachedFileTestCase(TestCase): @stderr_output('') def test_attached_file_with_forced_sender_and_private_header(self): - with MessageFile(self.pjd_filetype, 'whatthefuck@example.com', 'whatthefuck-too@example.com', - 'coucou', (('attached-file', 'content'),), headers={'Private': '1'}) as f: - management.call_command('sendmail', RECIPIENT_EMAIL, file=f.name, - sender=self.expedition_user.username) + with MessageFile( + self.pjd_filetype, + 'whatthefuck@example.com', + 'whatthefuck-too@example.com', + 'coucou', + (('attached-file', 'content'),), + headers={'Private': '1'}, + ) as f: + management.call_command( + 'sendmail', RECIPIENT_EMAIL, file=f.name, sender=self.expedition_user.username + ) self.assertEqual(Document.objects.count(), 1) document = Document.objects.get() assert document.private is True @@ -149,10 +169,16 @@ class SendMailAttachedFileTestCase(TestCase): @stderr_output('') def test_attached_file_with_forced_sender_and_private_email(self): - with MessageFile(self.pjd_filetype, 'whatthefuck@example.com', 'whatthefuck-too@example.com', - 'coucou', (('attached-file', 'content'),)) as f: - management.call_command('sendmail', PRIVATE_RECIPIENT_EMAIL, file=f.name, - sender=self.expedition_user.username) + with MessageFile( + self.pjd_filetype, + 'whatthefuck@example.com', + 'whatthefuck-too@example.com', + 'coucou', + (('attached-file', 'content'),), + ) as f: + management.call_command( + 'sendmail', PRIVATE_RECIPIENT_EMAIL, file=f.name, sender=self.expedition_user.username + ) self.assertEqual(Document.objects.count(), 1) document = Document.objects.get() assert document.private is True @@ -171,7 +197,9 @@ def test_email_wrong_encoding(db, settings): User.objects.create(username='recipient', email=RECIPIENT_EMAIL, id=2) FileType.objects.create(name='QE-Question', id=2) management.call_command( - 'sendmail', RECIPIENT_EMAIL, file='tests/data/email-encoded-iso-8859-15-but-says-utf-8', - sender=expedition_user.username + 'sendmail', + RECIPIENT_EMAIL, + file='tests/data/email-encoded-iso-8859-15-but-says-utf-8', + sender=expedition_user.username, ) assert Document.objects.count() == 1 diff --git a/tests/settings.py b/tests/settings.py index 4446f73..b242205 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -7,9 +7,7 @@ DOCBOW_PRIVATE_DOCUMENTS = True DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'TEST': { - 'NAME': 'docbow-test-%s' % os.environ.get("BRANCH_NAME", "").replace('/', '-')[:63], - }, + 'TEST': {'NAME': 'docbow-test-%s' % os.environ.get("BRANCH_NAME", "").replace('/', '-')[:63],}, } } diff --git a/tests/sso/settings.py b/tests/sso/settings.py index 7d17791..3d98477 100644 --- a/tests/sso/settings.py +++ b/tests/sso/settings.py @@ -7,8 +7,6 @@ INSTALLED_APPS += ('mellon',) DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'TEST': { - 'NAME': 'docbow-test-%s' % os.environ.get("BRANCH_NAME", "").replace('/', '-')[:63], - }, + 'TEST': {'NAME': 'docbow-test-%s' % os.environ.get("BRANCH_NAME", "").replace('/', '-')[:63],}, } } diff --git a/tests/sso/test_profile.py b/tests/sso/test_profile.py index 11d77f5..4f6d7e7 100644 --- a/tests/sso/test_profile.py +++ b/tests/sso/test_profile.py @@ -30,7 +30,6 @@ def user(): class MockResp(object): - def __init__(self, json=None, excp=None): self._json = json self._excp = excp @@ -47,6 +46,7 @@ class MockResp(object): def test_create_delegate_sso(a2settings, app, monkeypatch, users): a2settings.AUTHENTIC_ROLE = 'roleuuid' import docbow_project.docbow.utils + mock_resp1 = MockResp(json={'uuid': '1234'}) mock_resp2 = MockResp() mock_post = mock.Mock(side_effect=[mock_resp1, mock_resp2]) @@ -89,8 +89,8 @@ def test_create_delegate_sso(a2settings, app, monkeypatch, users): @pytest.mark.django_db def test_create_delegate_a2_failed(a2settings, app, monkeypatch, users): import docbow_project.docbow.utils - mock_resp = MockResp( - json={'errors': {'some': ['a2 error']}}, excp=requests.exceptions.RequestException()) + + mock_resp = MockResp(json={'errors': {'some': ['a2 error']}}, excp=requests.exceptions.RequestException()) mock_post = mock.Mock(return_value=mock_resp) monkeypatch.setattr(docbow_project.docbow.utils.requests, 'post', mock_post) @@ -110,6 +110,7 @@ def test_create_delegate_a2_failed(a2settings, app, monkeypatch, users): @pytest.mark.django_db def test_delete_delegate_sso(a2settings, client, monkeypatch, user): import docbow_project.docbow.profile_views + mock_resp = MockResp(json={}) mock_delete = mock.Mock(return_value=mock_resp) monkeypatch.setattr(docbow_project.docbow.profile_views.requests, 'delete', mock_delete) @@ -124,12 +125,7 @@ def test_delete_delegate_sso(a2settings, client, monkeypatch, user): mellon.models.UserSAMLIdentifier.objects.create(name_id='1234', issuer=issuer, user=delegate) client.login(username='user', password='password') - client.post( - '/profile/', - { - 'delegate-delete-john.doe-1.x': True, - } - ) + client.post('/profile/', {'delegate-delete-john.doe-1.x': True,}) assert not user.delegations_to.count() call_args = mock_delete.call_args[1] assert call_args['url'] == 'https://a2-url/api/users/1234'