misc: reformat using black (#49290)
This commit is contained in:
parent
1e425f5f29
commit
baebc42524
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'),
|
||||
]
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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'):
|
||||
|
|
|
@ -3,6 +3,7 @@ from django.db import transaction
|
|||
|
||||
from ... import notification
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
args = '<directory>'
|
||||
help = 'Send notifications'
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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',),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -12,7 +12,5 @@ class Migration(migrations.Migration):
|
|||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name='DeletedMailbox',
|
||||
),
|
||||
migrations.DeleteModel(name='DeletedMailbox',),
|
||||
]
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -7,9 +7,22 @@ import re
|
|||
from collections import defaultdict
|
||||
import time
|
||||
|
||||
from django.db.models import (Model, ForeignKey, DateTimeField, CharField,
|
||||
FileField, ManyToManyField, TextField, Manager, BooleanField,
|
||||
OneToOneField, Q, EmailField, PositiveSmallIntegerField, CASCADE)
|
||||
from django.db.models import (
|
||||
Model,
|
||||
ForeignKey,
|
||||
DateTimeField,
|
||||
CharField,
|
||||
FileField,
|
||||
ManyToManyField,
|
||||
TextField,
|
||||
Manager,
|
||||
BooleanField,
|
||||
OneToOneField,
|
||||
Q,
|
||||
EmailField,
|
||||
PositiveSmallIntegerField,
|
||||
CASCADE,
|
||||
)
|
||||
from django.contrib.auth.models import User, Group
|
||||
from django.conf import settings
|
||||
from django.template.defaultfilters import slugify
|
||||
|
@ -33,19 +46,20 @@ DOCBOW_APP = _('Docbow_App')
|
|||
DOCBOW_APP2 = _('Docbow_app')
|
||||
|
||||
# fixup User ordering
|
||||
User._meta.ordering = [ 'username' ]
|
||||
User._meta.ordering = ['username']
|
||||
|
||||
|
||||
def ellipsize(text, length=50):
|
||||
text = html.unescape(strip_tags(text))
|
||||
if len(text) < length:
|
||||
return text
|
||||
return text[:(length-10)] + '...'
|
||||
return text[: (length - 10)] + '...'
|
||||
|
||||
|
||||
class GetByNameManager(Manager):
|
||||
'''Manager providing a get_by_natural_key() method to retrieve object by
|
||||
their name. The name field MUST be unique.'''
|
||||
|
||||
def get_by_natural_key(self, name):
|
||||
return self.get(name=name)
|
||||
|
||||
|
@ -53,6 +67,7 @@ class GetByNameManager(Manager):
|
|||
class ContentManager(Manager):
|
||||
'''Manager providing a get_by_natural_key() method to retrieve object by
|
||||
their description. The description field MUST be unique.'''
|
||||
|
||||
def get_by_natural_key(self, description):
|
||||
return self.get(description=description)
|
||||
|
||||
|
@ -60,6 +75,7 @@ class ContentManager(Manager):
|
|||
class NameNaturalKey(object):
|
||||
'''Model mixin to export the name of a model as a natural key. The name
|
||||
field MUST be unique.'''
|
||||
|
||||
def natural_key(self):
|
||||
return (self.name,)
|
||||
|
||||
|
@ -69,6 +85,7 @@ class FileType(NameNaturalKey, Model):
|
|||
'''
|
||||
A type of file that can be sent inside the application.
|
||||
'''
|
||||
|
||||
objects = GetByNameManager()
|
||||
|
||||
name = CharField(max_length=128, unique=True)
|
||||
|
@ -90,22 +107,25 @@ class FileTypeAttachedFileKindManager(Manager):
|
|||
|
||||
@python_2_unicode_compatible
|
||||
class FileTypeAttachedFileKind(Model):
|
||||
MIME_TYPES_RE = re.compile(r'^\s*(?:(?:text|image|audio|application|video)'
|
||||
r'/(?:\*|[a-z-]+)(?:\s+(?:text|image|audio|application|video)'
|
||||
r'/(?:\*|[a-z-.]+))*\s*)?$')
|
||||
MIME_TYPES_RE = re.compile(
|
||||
r'^\s*(?:(?:text|image|audio|application|video)'
|
||||
r'/(?:\*|[a-z-]+)(?:\s+(?:text|image|audio|application|video)'
|
||||
r'/(?:\*|[a-z-.]+))*\s*)?$'
|
||||
)
|
||||
|
||||
objects = FileTypeAttachedFileKindManager()
|
||||
|
||||
name = CharField(max_length=128, verbose_name=_('name'))
|
||||
file_type = ForeignKey('FileType', verbose_name=_('document type'), on_delete=CASCADE)
|
||||
mime_types = TextField(verbose_name=_('mime types'),
|
||||
help_text=('mime types separated by spaces, wildcards are allowed'),
|
||||
blank=True)
|
||||
cardinality = PositiveSmallIntegerField(default=0,
|
||||
verbose_name=_('cardinality'),
|
||||
help_text=_('zero is a special value setting no limitation'))
|
||||
minimum = PositiveSmallIntegerField(default=0,
|
||||
verbose_name=_('minimum number of files'))
|
||||
mime_types = TextField(
|
||||
verbose_name=_('mime types'),
|
||||
help_text=('mime types separated by spaces, wildcards are allowed'),
|
||||
blank=True,
|
||||
)
|
||||
cardinality = PositiveSmallIntegerField(
|
||||
default=0, verbose_name=_('cardinality'), help_text=_('zero is a special value setting no limitation')
|
||||
)
|
||||
minimum = PositiveSmallIntegerField(default=0, verbose_name=_('minimum number of files'))
|
||||
position = PositiveSmallIntegerField(verbose_name=_('position'))
|
||||
|
||||
class Meta:
|
||||
|
@ -116,7 +136,9 @@ class FileTypeAttachedFileKind(Model):
|
|||
|
||||
def clean(self):
|
||||
if self.cardinality != 0 and not self.minimum <= self.cardinality:
|
||||
raise ValidationError(_('minimum must be inferior to maximum number of files if maximum is not zero'))
|
||||
raise ValidationError(
|
||||
_('minimum must be inferior to maximum number of files if maximum is not zero')
|
||||
)
|
||||
if self.mime_types:
|
||||
if not self.MIME_TYPES_RE.match(self.mime_types):
|
||||
raise ValidationError(_('invalid mime types list'))
|
||||
|
@ -125,8 +147,7 @@ class FileTypeAttachedFileKind(Model):
|
|||
return list(filter(None, re.split(r'\s+', self.mime_types.strip())))
|
||||
|
||||
def match_file(self, file_like):
|
||||
return file_match_mime_types(
|
||||
file_like, self.get_mime_types(), app_settings.MIME_BUFFER_SIZE)
|
||||
return file_match_mime_types(file_like, self.get_mime_types(), app_settings.MIME_BUFFER_SIZE)
|
||||
|
||||
def natural_key(self):
|
||||
return (self.name, self.file_type.name)
|
||||
|
@ -138,6 +159,7 @@ class FileTypeAttachedFileKind(Model):
|
|||
@python_2_unicode_compatible
|
||||
class Content(Model):
|
||||
'''Predefined content type'''
|
||||
|
||||
objects = ContentManager()
|
||||
|
||||
description = CharField(max_length=128, unique=True)
|
||||
|
@ -186,12 +208,11 @@ FORWARD_PERMISSION = 'FORWARD_DOCUMENT'
|
|||
|
||||
|
||||
class DocumentManager(Manager):
|
||||
|
||||
def get_query_set(self):
|
||||
'''Prefetch as much as possible.'''
|
||||
return super(DocumentManager, self).get_query_set() \
|
||||
.select_related() \
|
||||
.prefetch_related('attached_files')
|
||||
return (
|
||||
super(DocumentManager, self).get_query_set().select_related().prefetch_related('attached_files')
|
||||
)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
|
@ -206,45 +227,44 @@ class Document(Model):
|
|||
ordering = ['-date']
|
||||
verbose_name = _('Document')
|
||||
verbose_name_plural = _('Documents')
|
||||
permissions = (
|
||||
(FORWARD_PERMISSION, _("Can forward documents")),
|
||||
)
|
||||
permissions = ((FORWARD_PERMISSION, _("Can forward documents")),)
|
||||
base_manager_name = 'objects'
|
||||
|
||||
|
||||
sender = ForeignKey(User, verbose_name=_('Sender'), on_delete=CASCADE,
|
||||
related_name='documents_sent')
|
||||
sender = ForeignKey(User, verbose_name=_('Sender'), on_delete=CASCADE, related_name='documents_sent')
|
||||
real_sender = CharField(max_length=64, blank=True, verbose_name=_('Real sender'))
|
||||
date = DateTimeField(default=now,
|
||||
verbose_name=_("Date d'envoi"))
|
||||
to_user = ManyToManyField(User, related_name='directly_received_documents',
|
||||
blank=True, verbose_name=_('Users to send to'))
|
||||
to_list = ManyToManyField('MailingList', blank=True,
|
||||
verbose_name=_('Groups to send to'))
|
||||
filetype = ForeignKey(FileType, verbose_name=_('Document type'), on_delete=CASCADE,
|
||||
limit_choices_to={'is_active': True})
|
||||
date = DateTimeField(default=now, verbose_name=_("Date d'envoi"))
|
||||
to_user = ManyToManyField(
|
||||
User, related_name='directly_received_documents', blank=True, verbose_name=_('Users to send to')
|
||||
)
|
||||
to_list = ManyToManyField('MailingList', blank=True, verbose_name=_('Groups to send to'))
|
||||
filetype = ForeignKey(
|
||||
FileType, verbose_name=_('Document type'), on_delete=CASCADE, limit_choices_to={'is_active': True}
|
||||
)
|
||||
comment = TextField(blank=True, verbose_name=_('Comments'))
|
||||
_timestamp = TextField(blank=True)
|
||||
reply_to = ForeignKey('self', verbose_name=_('Reply to'), on_delete=CASCADE,
|
||||
blank=True, null=True, related_name='replies')
|
||||
private = BooleanField(default=False, verbose_name=_('Private'),
|
||||
help_text=_('delegates cannot see this document'))
|
||||
reply_to = ForeignKey(
|
||||
'self', verbose_name=_('Reply to'), on_delete=CASCADE, blank=True, null=True, related_name='replies'
|
||||
)
|
||||
private = BooleanField(
|
||||
default=False, verbose_name=_('Private'), help_text=_('delegates cannot see this document')
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
'''Return a displayable representation of the document sending.'''
|
||||
ctx = {
|
||||
'id': self.id,
|
||||
'date': self.date.date().isoformat(),
|
||||
'filetype': self.filetype,
|
||||
'comment': self.comment,
|
||||
'sender': username(self.sender),
|
||||
}
|
||||
'id': self.id,
|
||||
'date': self.date.date().isoformat(),
|
||||
'filetype': self.filetype,
|
||||
'comment': self.comment,
|
||||
'sender': username(self.sender),
|
||||
}
|
||||
return _('document {id} sent on {date} of type {filetype} about {comment} by {sender}').format(**ctx)
|
||||
|
||||
def filenames(self):
|
||||
'''Returns a display string for the list of attached files'''
|
||||
files = [ attached_file for attached_file in self.attached_files.all() ]
|
||||
files = [attached_file for attached_file in self.attached_files.all()]
|
||||
return ', '.join([f.filename() for f in files])
|
||||
|
||||
filenames.short_description = _('Attached files')
|
||||
|
||||
def filename_links(self):
|
||||
|
@ -263,6 +283,7 @@ class Document(Model):
|
|||
links[-1] = kind.name + ' : ' + links[-1]
|
||||
last_kind_name = kind.name
|
||||
return mark_safe(', '.join(links))
|
||||
|
||||
filename_links.short_description = _('Attached files')
|
||||
|
||||
def user_human_to(self):
|
||||
|
@ -275,14 +296,14 @@ class Document(Model):
|
|||
|
||||
def human_to(self):
|
||||
'''Return a sorted list of display names for all recipients.'''
|
||||
return sorted(list(map(username, self.to_user.all())) + \
|
||||
list(map(force_text, self.to_list.all())))
|
||||
return sorted(list(map(username, self.to_user.all())) + list(map(force_text, self.to_list.all())))
|
||||
|
||||
def recipients(self):
|
||||
'''Return a comma separated sorted list of display names for all
|
||||
recipients.
|
||||
'''
|
||||
return ', '.join(self.human_to())
|
||||
|
||||
recipients.short_description = _('Recipients')
|
||||
|
||||
def to(self):
|
||||
|
@ -315,8 +336,12 @@ class Document(Model):
|
|||
self._timestamp = time.time()
|
||||
self.date = dt.datetime.fromtimestamp(self._timestamp, utc)
|
||||
self.save()
|
||||
django_journal.record('timestamp', 'timestamped document {document} result is {timestamp}',
|
||||
document=self, timestamp=self._timestamp)
|
||||
django_journal.record(
|
||||
'timestamp',
|
||||
'timestamped document {document} result is {timestamp}',
|
||||
document=self,
|
||||
timestamp=self._timestamp,
|
||||
)
|
||||
return self._timestamp
|
||||
|
||||
def post(self, forward=True):
|
||||
|
@ -329,30 +354,49 @@ class Document(Model):
|
|||
self.timestamp(to=to)
|
||||
# Record recipient lists
|
||||
for mailing_list in self.to_list.all():
|
||||
django_journal.record('delivery', 'deliver document {document} to members of list {mailing_list}',
|
||||
document=self, mailing_list=mailing_list)
|
||||
django_journal.record(
|
||||
'delivery',
|
||||
'deliver document {document} to members of list {mailing_list}',
|
||||
document=self,
|
||||
mailing_list=mailing_list,
|
||||
)
|
||||
for user in to:
|
||||
Mailbox.objects.get_or_create(owner=user, document=self)
|
||||
if '--direct--' not in to_with_origins[user] or \
|
||||
len(to_with_origins[user]) > 1:
|
||||
if '--direct--' not in to_with_origins[user] or len(to_with_origins[user]) > 1:
|
||||
lists = u', '.join(m.name for m in to_with_origins[user] if m != '--direct--')
|
||||
if '--direct--' in to_with_origins[user]:
|
||||
django_journal.record('delivery', 'deliver document '
|
||||
'{document} in mailbox of user {recipient} '
|
||||
'member of {lists} and as a direct recipient',
|
||||
document=self, recipient=user, lists=lists)
|
||||
django_journal.record(
|
||||
'delivery',
|
||||
'deliver document '
|
||||
'{document} in mailbox of user {recipient} '
|
||||
'member of {lists} and as a direct recipient',
|
||||
document=self,
|
||||
recipient=user,
|
||||
lists=lists,
|
||||
)
|
||||
else:
|
||||
django_journal.record('delivery', 'deliver document '
|
||||
'{document} in mailbox of user {recipient} '
|
||||
'member of {lists}',
|
||||
document=self, recipient=user, lists=lists)
|
||||
django_journal.record(
|
||||
'delivery',
|
||||
'deliver document ' '{document} in mailbox of user {recipient} ' 'member of {lists}',
|
||||
document=self,
|
||||
recipient=user,
|
||||
lists=lists,
|
||||
)
|
||||
else:
|
||||
django_journal.record('delivery', 'deliver document {document} in mailbox of user {recipient} as a direct recipient',
|
||||
document=self, recipient=user)
|
||||
django_journal.record(
|
||||
'delivery',
|
||||
'deliver document {document} in mailbox of user {recipient} as a direct recipient',
|
||||
document=self,
|
||||
recipient=user,
|
||||
)
|
||||
# Deliver to ouput mailbox of the sender
|
||||
Mailbox.objects.get_or_create(owner=self.sender, outbox=True, document=self)
|
||||
django_journal.record('delivery', 'deliver document {document} in output mailbox of user {recipient}',
|
||||
document=self, recipient=self.sender)
|
||||
django_journal.record(
|
||||
'delivery',
|
||||
'deliver document {document} in output mailbox of user {recipient}',
|
||||
document=self,
|
||||
recipient=self.sender,
|
||||
)
|
||||
# Push notifications
|
||||
Notification.objects.notify(document=self, users=to)
|
||||
if forward:
|
||||
|
@ -368,25 +412,27 @@ class Document(Model):
|
|||
automatic - whether this forwarding is the result of an
|
||||
automatic treatment
|
||||
'''
|
||||
document = Document.objects.create(sender=new_sender,
|
||||
filetype=self.filetype,
|
||||
comment=self.comment)
|
||||
document = Document.objects.create(sender=new_sender, filetype=self.filetype, comment=self.comment)
|
||||
document.to_user.set(users)
|
||||
document.to_list.set(lists)
|
||||
attached_files = [ AttachedFile(name=attached_file.name,
|
||||
content=attached_file.content,
|
||||
document=document) for attached_file in self.attached_files.all()]
|
||||
attached_files = [
|
||||
AttachedFile(name=attached_file.name, content=attached_file.content, document=document)
|
||||
for attached_file in self.attached_files.all()
|
||||
]
|
||||
AttachedFile.objects.bulk_create(attached_files)
|
||||
recipients_count = document.post(forward=False)
|
||||
return recipients_count, DocumentForwarded.objects.create(from_document=self,
|
||||
to_document=document, automatic=automatic)
|
||||
return (
|
||||
recipients_count,
|
||||
DocumentForwarded.objects.create(from_document=self, to_document=document, automatic=automatic),
|
||||
)
|
||||
|
||||
def sender_display(self):
|
||||
return username(self.sender)
|
||||
|
||||
def url(self):
|
||||
return urlparse.urljoin(app_settings.BASE_URL,
|
||||
reverse('inbox-message', kwargs=dict(mailbox_id=self.id)))
|
||||
return urlparse.urljoin(
|
||||
app_settings.BASE_URL, reverse('inbox-message', kwargs=dict(mailbox_id=self.id))
|
||||
)
|
||||
|
||||
def has_zip_download(self):
|
||||
return self.attached_files.count() > 1 and settings.ZIP_DOWNLOAD
|
||||
|
@ -394,6 +440,7 @@ class Document(Model):
|
|||
|
||||
class SeenDocument(Model):
|
||||
'''Mark a document as seen'''
|
||||
|
||||
document = ForeignKey('Document', on_delete=CASCADE)
|
||||
user = ForeignKey('auth.User', on_delete=CASCADE)
|
||||
|
||||
|
@ -402,8 +449,10 @@ class SeenDocument(Model):
|
|||
verbose_name_plural = _('seen documents')
|
||||
ordering = ('-document',)
|
||||
|
||||
|
||||
class DeletedDocument(Model):
|
||||
'''Mark a document as deleted'''
|
||||
|
||||
document = ForeignKey('Document', on_delete=CASCADE)
|
||||
user = ForeignKey('auth.User', on_delete=CASCADE)
|
||||
soft_delete = BooleanField(default=False)
|
||||
|
@ -421,6 +470,7 @@ class DocumentForwarded(Model):
|
|||
|
||||
First use will be to mark message as having been forwarded.
|
||||
'''
|
||||
|
||||
from_document = ForeignKey(Document, related_name='document_forwarded_to', on_delete=CASCADE)
|
||||
to_document = ForeignKey(Document, related_name='document_forwarded_from', on_delete=CASCADE)
|
||||
date = DateTimeField(auto_now_add=True)
|
||||
|
@ -429,14 +479,12 @@ class DocumentForwarded(Model):
|
|||
def __str__(self):
|
||||
if self.automatic:
|
||||
return _(u'forwarded document {from_document} as {to_document} on {date} automatically').format(
|
||||
from_document=self.from_document,
|
||||
to_document=self.to_document,
|
||||
date=self.date)
|
||||
from_document=self.from_document, to_document=self.to_document, date=self.date
|
||||
)
|
||||
else:
|
||||
return _(u'forwarded document {from_document} as {to_document} on {date}').format(
|
||||
from_document=self.from_document,
|
||||
to_document=self.to_document,
|
||||
date=self.date)
|
||||
from_document=self.from_document, to_document=self.to_document, date=self.date
|
||||
)
|
||||
|
||||
|
||||
def list_to_csv(l, mapping_func=None):
|
||||
|
@ -456,17 +504,29 @@ class AutomaticForwarding(Model):
|
|||
Choice of sender and filetype to transfer mail automatically to a list
|
||||
of recipients.
|
||||
'''
|
||||
filetypes = ManyToManyField(FileType, related_name='forwarding_rules',
|
||||
verbose_name=_('filetype'),
|
||||
limit_choices_to={'is_active': True})
|
||||
originaly_to_user = ManyToManyField(User, related_name='as_original_recipient_forwarding_rules',
|
||||
blank=True, verbose_name=_('Original recipients'),
|
||||
help_text=_('At least one recipient must match for the rule to '
|
||||
'apply.'))
|
||||
forward_to_user = ManyToManyField(User, related_name='as_recipient_forwarding_rules',
|
||||
blank=True, verbose_name=_('Users to forward to'))
|
||||
forward_to_list = ManyToManyField('MailingList', blank=True,
|
||||
verbose_name=_('Groups to forward to'), related_name='as_recipient_forwarding_rules')
|
||||
|
||||
filetypes = ManyToManyField(
|
||||
FileType,
|
||||
related_name='forwarding_rules',
|
||||
verbose_name=_('filetype'),
|
||||
limit_choices_to={'is_active': True},
|
||||
)
|
||||
originaly_to_user = ManyToManyField(
|
||||
User,
|
||||
related_name='as_original_recipient_forwarding_rules',
|
||||
blank=True,
|
||||
verbose_name=_('Original recipients'),
|
||||
help_text=_('At least one recipient must match for the rule to ' 'apply.'),
|
||||
)
|
||||
forward_to_user = ManyToManyField(
|
||||
User, related_name='as_recipient_forwarding_rules', blank=True, verbose_name=_('Users to forward to')
|
||||
)
|
||||
forward_to_list = ManyToManyField(
|
||||
'MailingList',
|
||||
blank=True,
|
||||
verbose_name=_('Groups to forward to'),
|
||||
related_name='as_recipient_forwarding_rules',
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Automatic forwarding rule')
|
||||
|
@ -475,10 +535,10 @@ class AutomaticForwarding(Model):
|
|||
def __str__(self):
|
||||
'''Return a display string for the forwarding rule.'''
|
||||
ctx = {
|
||||
'filetypes': list_to_csv(self.filetypes.all()),
|
||||
'originaly_to_user': list_to_csv(map(username, self.originaly_to_user.all())),
|
||||
'to': list_to_csv(map(username, self.forward_to_user.all())
|
||||
+ list(self.forward_to_list.all())) }
|
||||
'filetypes': list_to_csv(self.filetypes.all()),
|
||||
'originaly_to_user': list_to_csv(map(username, self.originaly_to_user.all())),
|
||||
'to': list_to_csv(map(username, self.forward_to_user.all()) + list(self.forward_to_list.all())),
|
||||
}
|
||||
assert self.filetypes.all() or self.originaly_to_user.all()
|
||||
if self.filetypes.all() and self.originaly_to_user.all():
|
||||
tpl = _('Forward documents of type {filetypes} and to {originaly_to_user} automatically to {to}')
|
||||
|
@ -491,30 +551,44 @@ class AutomaticForwarding(Model):
|
|||
@classmethod
|
||||
def try_forwarding(self, document):
|
||||
'''Try applying matching rules to the document'''
|
||||
for rule in self.objects.filter(Q(filetypes=document.filetype,
|
||||
originaly_to_user__in=document.to())|
|
||||
Q(filetypes=document.filetype, originaly_to_user__isnull=True)):
|
||||
recipients_count, document_forwarded = document.forward(document.sender,
|
||||
users=rule.forward_to_user.all(),
|
||||
lists=rule.forward_to_list.all(),
|
||||
automatic=True)
|
||||
django_journal.record('automatic-forward', 'document {document} '
|
||||
'automatically forwarded to {recipients_count} new recipients '
|
||||
'as document {new_document}', document=document_forwarded.from_document,
|
||||
new_document=document_forwarded.to_document,
|
||||
recipients_count=recipients_count,
|
||||
document_forwarded=document_forwarded)
|
||||
for rule in self.objects.filter(
|
||||
Q(filetypes=document.filetype, originaly_to_user__in=document.to())
|
||||
| Q(filetypes=document.filetype, originaly_to_user__isnull=True)
|
||||
):
|
||||
recipients_count, document_forwarded = document.forward(
|
||||
document.sender,
|
||||
users=rule.forward_to_user.all(),
|
||||
lists=rule.forward_to_list.all(),
|
||||
automatic=True,
|
||||
)
|
||||
django_journal.record(
|
||||
'automatic-forward',
|
||||
'document {document} '
|
||||
'automatically forwarded to {recipients_count} new recipients '
|
||||
'as document {new_document}',
|
||||
document=document_forwarded.from_document,
|
||||
new_document=document_forwarded.to_document,
|
||||
recipients_count=recipients_count,
|
||||
document_forwarded=document_forwarded,
|
||||
)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class AttachedFile(Model):
|
||||
'''Uploaded file attached to a Document'''
|
||||
|
||||
name = CharField(max_length=300, verbose_name=_('Name'))
|
||||
content = FileField(max_length=300, upload_to=generate_filename, verbose_name=_('File'))
|
||||
document = ForeignKey(Document, verbose_name=_('Attached to'), on_delete=CASCADE,
|
||||
related_name='attached_files')
|
||||
kind = ForeignKey('FileTypeAttachedFileKind', blank=True, null=True, on_delete=CASCADE,
|
||||
verbose_name=_('attached file kind'))
|
||||
document = ForeignKey(
|
||||
Document, verbose_name=_('Attached to'), on_delete=CASCADE, related_name='attached_files'
|
||||
)
|
||||
kind = ForeignKey(
|
||||
'FileTypeAttachedFileKind',
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=CASCADE,
|
||||
verbose_name=_('attached file kind'),
|
||||
)
|
||||
|
||||
def filename(self):
|
||||
'''Extract the original filename from generated unique filename for
|
||||
|
@ -544,6 +618,7 @@ class AttachedFile(Model):
|
|||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
def is_guest(user):
|
||||
try:
|
||||
return user.docbowprofile.is_guest
|
||||
|
@ -557,19 +632,26 @@ class Delegation(Model):
|
|||
Delegate account, managable by user themselves.
|
||||
'''
|
||||
|
||||
by = ForeignKey(User, related_name='delegations_to', on_delete=CASCADE,
|
||||
verbose_name=pgettext_lazy('delegation from', "From"))
|
||||
to = ForeignKey(User, related_name='delegations_by', on_delete=CASCADE,
|
||||
verbose_name=pgettext_lazy('delegation to', "To"))
|
||||
by = ForeignKey(
|
||||
User,
|
||||
related_name='delegations_to',
|
||||
on_delete=CASCADE,
|
||||
verbose_name=pgettext_lazy('delegation from', "From"),
|
||||
)
|
||||
to = ForeignKey(
|
||||
User,
|
||||
related_name='delegations_by',
|
||||
on_delete=CASCADE,
|
||||
verbose_name=pgettext_lazy('delegation to', "To"),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = [ 'by' ]
|
||||
ordering = ['by']
|
||||
verbose_name = _('Account delegation')
|
||||
verbose_name_plural = _('Account delegations')
|
||||
db_table = 'auth_delegation'
|
||||
unique_together = (('by', 'to'),)
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return u'delegation from {0}:{0.id} to {1}:{1.id}'.format(self.by, self.to)
|
||||
|
||||
|
@ -590,7 +672,7 @@ class MailingListManager(GetByNameManager):
|
|||
|
||||
def are_member_of(self, users):
|
||||
lists = set(MailingList.objects.filter(is_active=True).filter(members__in=users))
|
||||
while True: # accumulate lists until it grows no more
|
||||
while True: # accumulate lists until it grows no more
|
||||
old_count = len(lists)
|
||||
lists |= set(MailingList.objects.filter(is_active=True).filter(mailing_list_members__in=lists))
|
||||
if old_count == len(lists):
|
||||
|
@ -603,18 +685,16 @@ class MailingList(NameNaturalKey, Model):
|
|||
'''A list of recipients.'''
|
||||
|
||||
name = CharField(max_length=400, verbose_name=_('Name'))
|
||||
members = ManyToManyField(User, verbose_name=_('Members'), blank=True,
|
||||
related_name='mailing_lists')
|
||||
mailing_list_members = ManyToManyField('MailingList',
|
||||
verbose_name=_('Mailing lists members'),
|
||||
blank=True, related_name='members_lists')
|
||||
is_active = BooleanField(verbose_name=_('is active'), blank=True,
|
||||
default=True)
|
||||
members = ManyToManyField(User, verbose_name=_('Members'), blank=True, related_name='mailing_lists')
|
||||
mailing_list_members = ManyToManyField(
|
||||
'MailingList', verbose_name=_('Mailing lists members'), blank=True, related_name='members_lists'
|
||||
)
|
||||
is_active = BooleanField(verbose_name=_('is active'), blank=True, default=True)
|
||||
|
||||
objects = MailingListManager()
|
||||
|
||||
class Meta:
|
||||
ordering = [ 'name' ]
|
||||
ordering = ['name']
|
||||
verbose_name = _('Mailing list')
|
||||
verbose_name_plural = _('Mailing lists')
|
||||
|
||||
|
@ -635,15 +715,14 @@ class MailingList(NameNaturalKey, Model):
|
|||
'''Traverse this list and all its recursive sublist and accumulate
|
||||
members and their origin.'''
|
||||
if sublist_traversed is None:
|
||||
sublist_traversed = defaultdict(lambda:0)
|
||||
sublist_traversed = defaultdict(lambda: 0)
|
||||
sublist_traversed[self] += 1
|
||||
members = defaultdict(lambda:set())
|
||||
members = defaultdict(lambda: set())
|
||||
for member in self.members.filter(is_active=True):
|
||||
members[member].add(self)
|
||||
for sublist in self.mailing_list_members.all():
|
||||
if sublist_traversed[sublist] < 3:
|
||||
sub_members = sublist.recursive_members_with_origin(
|
||||
sublist_traversed).items()
|
||||
sub_members = sublist.recursive_members_with_origin(sublist_traversed).items()
|
||||
for member, list_set in sub_members:
|
||||
members[member] |= list_set
|
||||
members[member].add(self)
|
||||
|
@ -656,24 +735,22 @@ class MailingList(NameNaturalKey, Model):
|
|||
@python_2_unicode_compatible
|
||||
class Mailbox(Model):
|
||||
'''List of document received by a user'''
|
||||
owner = ForeignKey(User, verbose_name=_('Mailbox owner'), on_delete=CASCADE,
|
||||
related_name='documents')
|
||||
document = ForeignKey(Document, verbose_name=('Document'), on_delete=CASCADE,
|
||||
related_name='mailboxes')
|
||||
outbox = BooleanField(verbose_name=_('Outbox message'), blank=True,
|
||||
default=False, db_index=True)
|
||||
|
||||
owner = ForeignKey(User, verbose_name=_('Mailbox owner'), on_delete=CASCADE, related_name='documents')
|
||||
document = ForeignKey(Document, verbose_name=('Document'), on_delete=CASCADE, related_name='mailboxes')
|
||||
outbox = BooleanField(verbose_name=_('Outbox message'), blank=True, default=False, db_index=True)
|
||||
date = DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
ordering = [ '-date' ]
|
||||
ordering = ['-date']
|
||||
verbose_name = _('Mailbox')
|
||||
verbose_name_plural = _('Mailboxes')
|
||||
|
||||
def __str__(self):
|
||||
return _(u'mailbox entry {id} of user {user}:{user.id} created on '
|
||||
u'{date} for {document}').format(id=self.id, user=self.owner,
|
||||
date=self.date,
|
||||
document=self.document)
|
||||
return _(u'mailbox entry {id} of user {user}:{user.id} created on ' u'{date} for {document}').format(
|
||||
id=self.id, user=self.owner, date=self.date, document=self.document
|
||||
)
|
||||
|
||||
|
||||
class DocbowUser(User):
|
||||
class Meta:
|
||||
|
@ -705,14 +782,19 @@ class Outbox(Mailbox):
|
|||
|
||||
@python_2_unicode_compatible
|
||||
class SendingLimitation(Model):
|
||||
mailing_list = OneToOneField(MailingList, unique=True, on_delete=CASCADE,
|
||||
verbose_name=MailingList._meta.verbose_name)
|
||||
filetypes = ManyToManyField(FileType, blank=True,
|
||||
related_name='filetype_limitation',
|
||||
verbose_name=_('Limitation des types de fichier'),
|
||||
limit_choices_to={'is_active': True})
|
||||
lists = ManyToManyField(MailingList, related_name='lists_limitation',
|
||||
verbose_name=_('Limitation des destinataires'))
|
||||
mailing_list = OneToOneField(
|
||||
MailingList, unique=True, on_delete=CASCADE, verbose_name=MailingList._meta.verbose_name
|
||||
)
|
||||
filetypes = ManyToManyField(
|
||||
FileType,
|
||||
blank=True,
|
||||
related_name='filetype_limitation',
|
||||
verbose_name=_('Limitation des types de fichier'),
|
||||
limit_choices_to={'is_active': True},
|
||||
)
|
||||
lists = ManyToManyField(
|
||||
MailingList, related_name='lists_limitation', verbose_name=_('Limitation des destinataires')
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Limitation par liste de destinataires')
|
||||
|
@ -720,48 +802,57 @@ class SendingLimitation(Model):
|
|||
verbose_name_plural = verbose_name
|
||||
|
||||
def __str__(self):
|
||||
return _(u'sending limitation for list {mailing_list} to filetypes'
|
||||
u'{filetypes} and lists {lists}').format(
|
||||
mailing_list=self.mailing_list,
|
||||
filetypes=list_to_csv(self.filetypes.all()),
|
||||
lists=list_to_csv(self.lists.all()))
|
||||
return _(
|
||||
u'sending limitation for list {mailing_list} to filetypes' u'{filetypes} and lists {lists}'
|
||||
).format(
|
||||
mailing_list=self.mailing_list,
|
||||
filetypes=list_to_csv(self.filetypes.all()),
|
||||
lists=list_to_csv(self.lists.all()),
|
||||
)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class DocbowProfile(Model):
|
||||
'''Hold extra user attributes'''
|
||||
|
||||
user = OneToOneField(User, unique=True, on_delete=CASCADE)
|
||||
is_guest = BooleanField(verbose_name=_('Guest user'), blank=True,
|
||||
default=False)
|
||||
mobile_phone = CharField(max_length=32, verbose_name=_('Mobile phone'),
|
||||
blank=True, validators=[validate_phone])
|
||||
personal_email = EmailField(_('personal email address'), blank=True,
|
||||
help_text=_('if you provide a personal email address, notifications '
|
||||
'of new documents will also be sent to this address.'))
|
||||
is_guest = BooleanField(verbose_name=_('Guest user'), blank=True, default=False)
|
||||
mobile_phone = CharField(
|
||||
max_length=32, verbose_name=_('Mobile phone'), blank=True, validators=[validate_phone]
|
||||
)
|
||||
personal_email = EmailField(
|
||||
_('personal email address'),
|
||||
blank=True,
|
||||
help_text=_(
|
||||
'if you provide a personal email address, notifications '
|
||||
'of new documents will also be sent to this address.'
|
||||
),
|
||||
)
|
||||
accept_notifications = BooleanField(
|
||||
verbose_name=_('Accept to be notified'), blank=True, default=True,
|
||||
help_text=_("If unchecked you will not received notifications "
|
||||
"anymore, by email or SMS."))
|
||||
verbose_name=_('Accept to be notified'),
|
||||
blank=True,
|
||||
default=True,
|
||||
help_text=_("If unchecked you will not received notifications " "anymore, by email or SMS."),
|
||||
)
|
||||
external_id = CharField(max_length=256, verbose_name=_('External identifer'), blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return _(u'docbow profile of {user}:{user.id} with mobile phone '
|
||||
u'{mobile_phone} and personal email {personal_email}').format(
|
||||
user=self.user,
|
||||
mobile_phone=self.mobile_phone,
|
||||
personal_email=self.personal_email)
|
||||
return _(
|
||||
u'docbow profile of {user}:{user.id} with mobile phone '
|
||||
u'{mobile_phone} and personal email {personal_email}'
|
||||
).format(user=self.user, mobile_phone=self.mobile_phone, personal_email=self.personal_email)
|
||||
|
||||
|
||||
class NotificationManager(Manager):
|
||||
def notify(self, document=None, users=None, kind='new-document', ctx=None):
|
||||
'''Build notifications in bulk'''
|
||||
users = set(users)
|
||||
users = set(users)
|
||||
if document and not document.private:
|
||||
users |= set(User.objects.filter(delegations_by__by__in=users, is_active=True))
|
||||
users = User.objects.filter(pk__in=[u.pk for u in users]) \
|
||||
.exclude(docbowprofile__accept_notifications=False)
|
||||
notifications = [Notification(user=user, document=document, kind=kind, ctx=ctx)
|
||||
for user in users]
|
||||
users = User.objects.filter(pk__in=[u.pk for u in users]).exclude(
|
||||
docbowprofile__accept_notifications=False
|
||||
)
|
||||
notifications = [Notification(user=user, document=document, kind=kind, ctx=ctx) for user in users]
|
||||
self.bulk_create(notifications)
|
||||
|
||||
|
||||
|
@ -778,6 +869,7 @@ class Notification(Model):
|
|||
was successful if this is empty.
|
||||
ctx - a pickled object
|
||||
'''
|
||||
|
||||
objects = NotificationManager()
|
||||
|
||||
create_dt = DateTimeField(auto_now_add=True)
|
||||
|
@ -797,6 +889,7 @@ class Notification(Model):
|
|||
|
||||
class NotificationPreference(Model):
|
||||
'''Store preferences of users toward notification methods'''
|
||||
|
||||
user = ForeignKey(User, verbose_name=_('user'), on_delete=CASCADE)
|
||||
filetype = ForeignKey(FileType, verbose_name=_('file type'), on_delete=CASCADE)
|
||||
kind = CharField(max_length=8, verbose_name=_('kind'))
|
||||
|
@ -807,15 +900,18 @@ class NotificationPreference(Model):
|
|||
verbose_name = _('notification preference')
|
||||
verbose_name_plural = _('notification preferences')
|
||||
|
||||
|
||||
def guest_users():
|
||||
return User.objects.filter(docbowprofile__is_guest=True).filter(is_active=True)
|
||||
|
||||
|
||||
def non_guest_users():
|
||||
return User.objects.exclude(docbowprofile__is_guest=True).filter(is_active=True)
|
||||
|
||||
|
||||
import watson.search as watson
|
||||
|
||||
|
||||
class DocumentAdapter(watson.SearchAdapter):
|
||||
def gather_strings(self, obj):
|
||||
yield obj.comment
|
||||
|
|
|
@ -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),
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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'))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -8,6 +8,7 @@ from .. import views, models, app_settings, sql
|
|||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
def paginator(context):
|
||||
"""
|
||||
To be used in conjunction with the object_list generic view.
|
||||
|
@ -20,7 +21,7 @@ def paginator(context):
|
|||
page = context['page']
|
||||
hits = page.paginator.count
|
||||
pn = page.number
|
||||
link_to_last_page = (page.paginator.num_pages > 2) and (pn < (page.paginator.num_pages-1))
|
||||
link_to_last_page = (page.paginator.num_pages > 2) and (pn < (page.paginator.num_pages - 1))
|
||||
return {
|
||||
'hits': hits,
|
||||
'page_number': page.number,
|
||||
|
@ -28,14 +29,16 @@ def paginator(context):
|
|||
'last_page': page.paginator.num_pages,
|
||||
'first': page.start_index(),
|
||||
'last': page.end_index(),
|
||||
'previous': page.number-1,
|
||||
'next': page.number+1,
|
||||
'previous': page.number - 1,
|
||||
'next': page.number + 1,
|
||||
'has_next': page.has_next(),
|
||||
'has_previous': page.has_previous(),
|
||||
}
|
||||
|
||||
|
||||
register.inclusion_tag('docbow/paginator.html', takes_context=True)(paginator)
|
||||
|
||||
|
||||
def menu(context):
|
||||
request = context.get('request')
|
||||
here = context.get('view_name')
|
||||
|
@ -60,47 +63,63 @@ def menu(context):
|
|||
view_name = slugify(view_name)
|
||||
else:
|
||||
url = reverse(view_name)
|
||||
docbow_menu.append({ 'view_name': view_name.replace(':', '-'),
|
||||
'url': url, 'caption': caption, 'count': count,
|
||||
'current': current})
|
||||
return { 'menus': docbow_menu }
|
||||
docbow_menu.append(
|
||||
{
|
||||
'view_name': view_name.replace(':', '-'),
|
||||
'url': url,
|
||||
'caption': caption,
|
||||
'count': count,
|
||||
'current': current,
|
||||
}
|
||||
)
|
||||
return {'menus': docbow_menu}
|
||||
|
||||
|
||||
register.inclusion_tag('docbow/menu.html', takes_context=True)(menu)
|
||||
|
||||
|
||||
def username(value):
|
||||
return models.username(value)
|
||||
|
||||
|
||||
register.filter(username)
|
||||
|
||||
|
||||
def doc_real_sender(document):
|
||||
return document.real_sender or models.username(document.sender)
|
||||
|
||||
|
||||
register.filter(doc_real_sender)
|
||||
|
||||
|
||||
def frfilesizeformat(value):
|
||||
if value < 1024*1024:
|
||||
if value < 1024 * 1024:
|
||||
value = value / 1024.0
|
||||
unit = _('kB')
|
||||
else:
|
||||
value = value / (1024*1024.0)
|
||||
value = value / (1024 * 1024.0)
|
||||
unit = _('MB')
|
||||
return "%.1f %s" % (value, unit)
|
||||
|
||||
|
||||
register.filter(frfilesizeformat)
|
||||
|
||||
|
||||
def humantargets(value):
|
||||
user_human_to = value.user_human_to()
|
||||
group_human_to = value.group_human_to()
|
||||
l1 = map(lambda x: _('to %s') % x, user_human_to)
|
||||
if len(user_human_to) > 1:
|
||||
p1 = _('%(one)s and %(two)s') % { 'one': ', '.join(l1[0:-1]), 'two': l1[-1] }
|
||||
p1 = _('%(one)s and %(two)s') % {'one': ', '.join(l1[0:-1]), 'two': l1[-1]}
|
||||
elif user_human_to:
|
||||
p1 = l1[0]
|
||||
l2 = map(lambda x: _('to list %s') % x, group_human_to)
|
||||
if len(group_human_to) > 1:
|
||||
p2 = _('%(one)s and %(two)s') % { 'one': ', '.join(l2[0:-1]), 'two': l2[-1] }
|
||||
p2 = _('%(one)s and %(two)s') % {'one': ', '.join(l2[0:-1]), 'two': l2[-1]}
|
||||
elif group_human_to:
|
||||
p2 = l2[0]
|
||||
if l1 and l2:
|
||||
return _('%(one)s and to lists %(two)s') % { 'one': p1, 'two': p2 }
|
||||
return _('%(one)s and to lists %(two)s') % {'one': p1, 'two': p2}
|
||||
elif l1:
|
||||
return p1
|
||||
elif l2:
|
||||
|
@ -108,11 +127,14 @@ def humantargets(value):
|
|||
else:
|
||||
return ''
|
||||
|
||||
|
||||
register.filter(humantargets)
|
||||
|
||||
|
||||
def nonbreakinghyphen(value):
|
||||
return value.replace('-', '‑')
|
||||
|
||||
|
||||
register.filter(nonbreakinghyphen)
|
||||
|
||||
|
||||
|
@ -121,11 +143,13 @@ def order_by(queryset, args):
|
|||
args = [x.strip() for x in args.split(',')]
|
||||
return queryset.order_by(*args)
|
||||
|
||||
|
||||
@register.filter_function
|
||||
def intersect(iterable1, iterable2):
|
||||
iterable2 = set(iterable2)
|
||||
return [it for it in iterable1 if it in iterable2]
|
||||
|
||||
|
||||
@register.filter
|
||||
def is_checkbox(field):
|
||||
return field.field.widget.__class__.__name__.lower() == "checkboxinput"
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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',
|
||||
),
|
||||
]
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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'):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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'))
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
|
||||
urlpatterns = []
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 *
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
74
setup.py
74
setup.py
|
@ -34,7 +34,9 @@ def get_version():
|
|||
if os.path.exists('.git'):
|
||||
p = subprocess.Popen(
|
||||
['git', 'describe', '--dirty=.dirty', '--match=v*'],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
result = p.communicate()[0]
|
||||
if p.returncode == 0:
|
||||
result = result.decode('ascii').strip()[1:] # strip spaces/newlines and initial v
|
||||
|
@ -45,9 +47,7 @@ def get_version():
|
|||
version = result
|
||||
return version
|
||||
else:
|
||||
return '0.0.post%s' % len(
|
||||
subprocess.check_output(
|
||||
['git', 'rev-list', 'HEAD']).splitlines())
|
||||
return '0.0.post%s' % len(subprocess.check_output(['git', 'rev-list', 'HEAD']).splitlines())
|
||||
return '0.0'
|
||||
|
||||
|
||||
|
@ -64,6 +64,7 @@ class compile_translations(Command):
|
|||
def run(self):
|
||||
try:
|
||||
from django.core.management import call_command
|
||||
|
||||
for path, dirs, files in os.walk('docbow_project'):
|
||||
if 'locale' not in dirs:
|
||||
continue
|
||||
|
@ -85,34 +86,37 @@ class install_lib(_install_lib):
|
|||
_install_lib.run(self)
|
||||
|
||||
|
||||
setup(name='docbow',
|
||||
version=get_version(),
|
||||
license='AGPL 3.0',
|
||||
description='Document box for the Wallon Parliament',
|
||||
url='https://dev.entrouvert.org/projects/docbow-pub/',
|
||||
author="Entr'ouvert",
|
||||
author_email='info@entrouvert.com',
|
||||
maintainer='Benjamin Dauvergne',
|
||||
maintainer_email='bdauvergne@entrouvert.com',
|
||||
include_package_data=True,
|
||||
scripts=('manage.py',),
|
||||
packages=find_packages(),
|
||||
install_requires=[
|
||||
'django>=1.11, <2',
|
||||
'six<1.11.0',
|
||||
'typing', # For M2Crypto.util
|
||||
'django-debug-toolbar<0.9.0',
|
||||
'gunicorn',
|
||||
'django_journal>=2.0.0',
|
||||
'django-picklefield<2.0.0',
|
||||
'django-tables2',
|
||||
'requests',
|
||||
'python-magic',
|
||||
'django-watson<1.5.4',
|
||||
'sqlalchemy',
|
||||
],
|
||||
cmdclass={
|
||||
'build': build,
|
||||
'install_lib': install_lib,
|
||||
'compile_translations': compile_translations,
|
||||
'sdist': eo_sdist})
|
||||
setup(
|
||||
name='docbow',
|
||||
version=get_version(),
|
||||
license='AGPL 3.0',
|
||||
description='Document box for the Wallon Parliament',
|
||||
url='https://dev.entrouvert.org/projects/docbow-pub/',
|
||||
author="Entr'ouvert",
|
||||
author_email='info@entrouvert.com',
|
||||
maintainer='Benjamin Dauvergne',
|
||||
maintainer_email='bdauvergne@entrouvert.com',
|
||||
include_package_data=True,
|
||||
scripts=('manage.py',),
|
||||
packages=find_packages(),
|
||||
install_requires=[
|
||||
'django>=1.11, <2',
|
||||
'six<1.11.0',
|
||||
'typing', # For M2Crypto.util
|
||||
'django-debug-toolbar<0.9.0',
|
||||
'gunicorn',
|
||||
'django_journal>=2.0.0',
|
||||
'django-picklefield<2.0.0',
|
||||
'django-tables2',
|
||||
'requests',
|
||||
'python-magic',
|
||||
'django-watson<1.5.4',
|
||||
'sqlalchemy',
|
||||
],
|
||||
cmdclass={
|
||||
'build': build,
|
||||
'install_lib': install_lib,
|
||||
'compile_translations': compile_translations,
|
||||
'sdist': eo_sdist,
|
||||
},
|
||||
)
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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/')
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')))
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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],},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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],},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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],},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
|
Loading…
Reference in New Issue