misc: reformat using black (#49290)

This commit is contained in:
Emmanuel Cazenave 2020-12-09 15:22:51 +01:00
parent 1e425f5f29
commit baebc42524
80 changed files with 2531 additions and 1871 deletions

View File

@ -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')

View File

@ -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'<a href="{0}" class="external-link">{1}</a>'.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

View File

@ -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

View File

@ -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

View File

@ -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<uidb64>[0-9A-Za-z_\-]+)/(?P<token>.+)/$',
name='auth_password_reset',
),
url(
r'^password/reset/confirm/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>.+)/$',
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'),
]

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
)

View File

@ -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)

View File

@ -15,6 +15,7 @@ def get_object(model, ref):
else:
return model.objects.get(name=ref)
class Command(BaseCommand):
args = '<username>'
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)

View File

@ -14,6 +14,7 @@ from django.conf import settings
from ... import models
from ....log import models as log_models
class Command(BaseCommand):
args = '<directory>'
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()

View File

@ -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):

View File

@ -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

View File

@ -6,6 +6,7 @@ from django.db import transaction
from ... import models
class Command(BaseCommand):
args = '<directory>'
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)

View File

@ -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()

View File

@ -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:

View File

@ -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:

View File

@ -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)

View File

@ -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'):

View File

@ -3,6 +3,7 @@ from django.db import transaction
from ... import notification
class Command(BaseCommand):
args = '<directory>'
help = 'Send notifications'

View File

@ -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()

View File

@ -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

View File

@ -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',),
),
]

View File

@ -12,7 +12,5 @@ class Migration(migrations.Migration):
]
operations = [
migrations.DeleteModel(
name='DeletedMailbox',
),
migrations.DeleteModel(name='DeletedMailbox',),
]

View File

@ -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,
),
),
]

View File

@ -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),
),
]

View File

@ -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 + '&nbsp;: ' + 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

View File

@ -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),
)

View File

@ -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', '''<?xml version="1.0" encoding="UTF-8"?>
z.writestr(
'META-INF/manifest.xml',
'''<?xml version="1.0" encoding="UTF-8"?>
<manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0">
<manifest:file-entry manifest:full-path="/" manifest:media-type="application/vnd.oasis.opendocument.spreadsheet"/>
<manifest:file-entry manifest:full-path="styles.xml" manifest:media-type="text/xml"/>
<manifest:file-entry manifest:full-path="content.xml" manifest:media-type="text/xml"/>
<manifest:file-entry manifest:full-path="META-INF/manifest.xml" manifest:media-type="text/xml"/>
<manifest:file-entry manifest:full-path="mimetype" manifest:media-type="text/plain"/>
</manifest:manifest>''')
z.writestr('styles.xml', '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
</manifest:manifest>''',
)
z.writestr(
'styles.xml',
'''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<office:document-styles xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0">
</office:document-styles>''')
</office:document-styles>''',
)
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)

View File

@ -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():

View File

@ -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'))

View File

@ -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)

View File

@ -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
)

View File

@ -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

View File

@ -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('<input type="checkbox" name="select-all" class="js-select-all"/>')
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(
'<input type="checkbox", name="select" class="js-select" value="{{ record.id }}" class="js-select"/>',
orderable=False, verbose_name=SELECT_ALL)
delete = tables.TemplateColumn(template_name='docbow/outbox_delete_column.html',
orderable=False,
verbose_name=' ')
'<input type="checkbox", name="select" class="js-select" value="{{ record.id }}" class="js-select"/>',
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 %}
<span>{{ recipient_user|username }}</span>{% 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(
'<input type="checkbox" class="js-select" name="select" value="{{ record.id }}"/>',
verbose_name=SELECT_ALL, orderable=False)
delete = tables.TemplateColumn(template_name='docbow/inbox_delete_column.html',
orderable=False, verbose_name=' ')
'<input type="checkbox" class="js-select" name="select" value="{{ record.id }}"/>',
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')

View File

@ -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('-', '&#8209;')
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"

View File

@ -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

View File

@ -7,15 +7,12 @@ urlpatterns = [
url(
r'^(?P<transaction_id>[a-zA-Z0-9-]+)/(?P<file_kind>[0-9]+)/$',
docbow_project.docbow.upload_views.upload,
name='upload'
),
url(
r'^(?P<transaction_id>[a-zA-Z0-9-]+)/$',
docbow_project.docbow.upload_views.upload,
name='upload'
name='upload',
),
url(r'^(?P<transaction_id>[a-zA-Z0-9-]+)/$', docbow_project.docbow.upload_views.upload, name='upload'),
url(
r'^(?P<transaction_id>[a-zA-Z0-9-]+)/(?P<filename>[^/]+)$',
docbow_project.docbow.upload_views.upload_file,
name='uploaded'),
name='uploaded',
),
]

View File

@ -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()

View File

@ -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<document_id>\d+)/$',
url(
r'^inbox_by_document/(?P<document_id>\d+)/$',
docbow_project.docbow.views.inbox_by_document,
name='inbox-by-document-message'),
url(r'^inbox/(?P<mailbox_id>\d+)/$',
docbow_project.docbow.views.message, name='inbox-message',
kwargs={'outbox': False}),
url(r'^inbox/(?P<mailbox_id>\d+)/delete/$',
name='inbox-by-document-message',
),
url(
r'^inbox/(?P<mailbox_id>\d+)/$',
docbow_project.docbow.views.message,
name='inbox-message',
kwargs={'outbox': False},
),
url(
r'^inbox/(?P<mailbox_id>\d+)/delete/$',
docbow_project.docbow.views.delete,
name='inbox-message-delete'),
url(r'^inbox/(?P<doc_id>\d+)/restore/$',
name='inbox-message-delete',
),
url(
r'^inbox/(?P<doc_id>\d+)/restore/$',
docbow_project.docbow.views.restore,
name='inbox-message-restore', kwargs={'outbox': False}),
url(r'^inbox/(?P<mailbox_id>\d+)/(?P<attached_file>\d+)/.*$',
name='inbox-message-restore',
kwargs={'outbox': False},
),
url(
r'^inbox/(?P<mailbox_id>\d+)/(?P<attached_file>\d+)/.*$',
docbow_project.docbow.views.message_attached_file,
name='inbox-message-attached-file'),
url(r'^inbox/(?P<mailbox_id>\d+)/allfiles/$',
name='inbox-message-attached-file',
),
url(
r'^inbox/(?P<mailbox_id>\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<mailbox_id>\d+)/$',
docbow_project.docbow.views.message, name='outbox-message',
kwargs={'outbox': True}),
url(r'^outbox/(?P<mailbox_id>\d+)/delete/$',
url(
r'^outbox/(?P<mailbox_id>\d+)/$',
docbow_project.docbow.views.message,
name='outbox-message',
kwargs={'outbox': True},
),
url(
r'^outbox/(?P<mailbox_id>\d+)/delete/$',
docbow_project.docbow.views.delete,
name='outbox-message-delete', kwargs={'outbox': True}),
url(r'^outbox/(?P<doc_id>\d+)/restore/$',
name='outbox-message-delete',
kwargs={'outbox': True},
),
url(
r'^outbox/(?P<doc_id>\d+)/restore/$',
docbow_project.docbow.views.restore,
name='outbox-message-restore', kwargs={'outbox': True}),
url(r'^outbox/(?P<mailbox_id>\d+)/(?P<attached_file>\d+)/.*$',
name='outbox-message-restore',
kwargs={'outbox': True},
),
url(
r'^outbox/(?P<mailbox_id>\d+)/(?P<attached_file>\d+)/.*$',
docbow_project.docbow.views.message_attached_file,
name='outbox-message-attached-file', kwargs={'outbox': True}),
url(r'^outbox/(?P<mailbox_id>\d+)/allfiles/$',
name='outbox-message-attached-file',
kwargs={'outbox': True},
),
url(
r'^outbox/(?P<mailbox_id>\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<file_type_id>\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<file_type_id>\d+)/$', docbow_project.docbow.views.send_file, name='send-file'),
url(r'^help/$', docbow_project.docbow.views.help, name='help'),
url(r'^help/(?P<pagename>[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<username>.*)/$',
docbow_project.docbow.views.su, {'redirect_url': '/'}),
url(r'^su/(?P<username>.*)/$', 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'):

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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):
</script>'''
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'<div class="selector"><span class="display-value">%s</span></div>' % self.display_value)
return mark_safe(
u'<div class="selector"><span class="display-value">%s</span></div>' % 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'<script type="text/javascript">addEvent(window, "load", function(e) {')
# TODO: "id_" is hard-coded here. This should instead use the correct
# API to determine the ID dynamically.
output.append(u'SelectFilter.init("id_%s", "%s", %s, "%s"); });</script>\n' % \
(name, self.verbose_name.replace('"', '\\"'), int(self.is_stacked), settings.STATIC_URL))
output.append(
u'SelectFilter.init("id_%s", "%s", %s, "%s"); });</script>\n'
% (name, self.verbose_name.replace('"', '\\"'), int(self.is_stacked), settings.STATIC_URL)
)
return mark_safe(u''.join(output))

View File

@ -8,18 +8,19 @@ from .. import utils
register = template.Library()
@register.filter
def humandate(dt):
full_dt = date(dt, 'SHORT_DATE_FORMAT')
s = u'<span title="{0}">{1}</span>'.format(
escape(full_dt), escape(utils.datetime2human(dt)))
s = u'<span title="{0}">{1}</span>'.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'<span title="{0}">{1}</span>'.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)

View File

@ -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()

View File

@ -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]

View File

@ -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

View File

@ -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 = '<directory> <days of retention>'
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')

View File

@ -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()

View File

@ -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)

View File

@ -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:

View File

@ -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',

View File

@ -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'))

View File

@ -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'

View File

@ -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

View File

@ -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)

View File

@ -1,2 +1 @@
urlpatterns = []

View File

@ -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()

View File

@ -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

View File

@ -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 *

View File

@ -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

View File

@ -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()

View File

@ -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'

View File

@ -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,
},
)

View File

@ -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]

View File

@ -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)

View File

@ -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/')

View File

@ -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

View File

@ -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')))

View File

@ -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')

View File

@ -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],},
}
}

View File

@ -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

View File

@ -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],},
}
}

View File

@ -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],},
}
}

View File

@ -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'