docbow/docbow_project/docbow/views.py

998 lines
33 KiB
Python

import datetime
import operator
import os.path
import smtplib
import tempfile
import zipfile
from itertools import chain
import django.contrib.auth as auth
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.core.files.base import File
from django.core.mail import EmailMessage
from django.db.models.query import Q
from django.forms.forms import NON_FIELD_ERRORS
from django.http import Http404, HttpResponse, HttpResponseForbidden, HttpResponseRedirect, JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.utils.encoding import force_str
from django.utils.timezone import now
from django.utils.translation import gettext as _
from django.utils.translation import gettext_noop as N_
from django.utils.translation import ngettext
from django.views.decorators.cache import never_cache
from django.views.decorators.debug import sensitive_post_parameters
from django.views.decorators.http import require_http_methods
from django.views.generic.base import RedirectView, View
from django.views.generic.list import ListView, MultipleObjectMixin
try:
from BeautifulSoup import BeautifulSoup
except ImportError:
from bs4 import BeautifulSoup
import django_tables2.views as tables_views
import watson.search as watson
from docbow_project.docbow import app_settings, profile_views, sql, tables, unicodecsv
from docbow_project.docbow.decorators import as_delegate
from docbow_project.docbow.forms import (
AnonymousContactForm,
ContactForm,
FileForm,
FilterForm,
ForwardingForm,
)
from docbow_project.docbow.models import (
AttachedFile,
DeletedDocument,
Document,
FileType,
Mailbox,
MailingList,
SeenDocument,
SendingLimitation,
is_guest,
non_guest_users,
username,
)
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)
return response
def get_base_document_query(request, qs, outbox):
related_users = get_related_users(request)
mailboxes = Mailbox.objects.filter(owner__in=related_users, outbox=outbox)
private_filter = Q(private=False) | Q(private=True, mailboxes__owner=request.user)
if hasattr(request.user, 'delegate'):
private_filter = Q(private=False)
return qs.filter(mailboxes__in=mailboxes).filter(private_filter)
def get_document(request, mailbox_id, outbox):
related_users = get_related_users(request)
mailboxes = Mailbox.objects.filter(owner__in=related_users, outbox=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()
.get()
)
class Row:
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
def user_mailing_list_names(user):
return user.mailing_lists.values_list('name', flat=True)
def recursive_lists(lists):
lists = set(lists)
while True:
old_count = len(lists)
lists |= set(MailingList.objects.filter(is_active=True).filter(members_lists__in=lists))
if len(lists) == old_count:
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()
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')
)
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 = recursive_lists(lists)
users = non_guest_users().filter(mailing_lists__in=lists).distinct()
if lists:
kwargs['list_qs'] = lists
kwargs['user_qs'] = users
return kwargs
def get_filetype_limitation(user):
# find delegation relations
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()
# 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')
)
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')
)
else:
return own_limitations
def get_table_kwargs():
res = {}
if not settings.EXTRA_SENDERS:
res['exclude'] = ('extra_senders',)
return res
@login_required
@never_cache
@watson.update_index()
def send_file(request, file_type_id):
file_type = get_object_or_404(FileType, id=file_type_id)
try:
reply_to = get_document(request, request.GET['reply_to'], False)
except Document.DoesNotExist:
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()
)
real_user = getattr(request.user, 'delegate', request.user)
limitations = get_filetype_limitation(request.user)
if limitations:
if not limitations.filter(id=file_type.id).exists():
return redirect('send-file-selector')
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),
)
try:
if form.is_valid():
new_send = form.save(commit=False)
to_user = []
to_list = []
for recipient in form.cleaned_data['recipients']:
if recipient.startswith('list-'):
i = recipient.split('-')[1]
to_list.append(int(i))
elif recipient.startswith('user-'):
i = recipient.split('-')[1]
to_user.append(int(i))
new_send.save()
form.save_m2m()
form.save_attachments()
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,
)
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 '
'have been notified; sending seems blocked at the moment. You should '
'try agrain later. If it still does not work then, contact '
'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_action = ''
if reply_to:
form_action = '?reply_to=%s' % reply_to.id
return render(
request,
'docbow/send_file.html',
{
'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')
response['Content-disposition'] = 'attachment'
return response
def get_document_and_mark_seen(request, mailbox_id, outbox):
try:
document = get_document(request, mailbox_id, outbox)
except Document.DoesNotExist:
raise Http404
if not SeenDocument.objects.filter(document=document, user=request.user).exists():
SeenDocument.objects.create(document=document, user=request.user)
return document
@login_required
@never_cache
def message_attached_file(request, mailbox_id, attached_file, outbox=False):
"""
Download attached files, verify that the user has access to the document
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,
)
return upload(request, attached_file)
@login_required
@never_cache
def message_all_files(request, mailbox_id, outbox=False):
document = get_document_and_mark_seen(request, mailbox_id, outbox)
if not document.has_zip_download():
return redirect('inbox')
temp = tempfile.TemporaryFile()
with zipfile.ZipFile(temp, mode='w') as zf:
for file_ in document.attached_files.all():
zf.write(file_.content.path, file_.name)
temp.seek(0)
response = HttpResponse(File(temp), content_type='application/octet-stream')
response['Content-disposition'] = 'attachment; filename="documents.zip"'
request.record('download', 'download zip archive of {document}', document=document)
return response
def form_to_user_and_list(form):
to_user = []
to_list = []
for recipient in form.cleaned_data['recipients']:
if recipient.startswith('list-'):
i = recipient.split('-')[1]
to_list.append(int(i))
elif recipient.startswith('user-'):
i = recipient.split('-')[1]
to_user.append(int(i))
return to_user, to_list
@login_required
@never_cache
def message(request, mailbox_id, outbox=False):
try:
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'))
else:
messages.warning(request, _('Document not found'))
if outbox:
return redirect('outbox')
else:
return redirect('inbox')
if outbox:
back_pair = (reverse('outbox'), gettext_noop('back to outbox'))
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}
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))
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 = ngettext(
'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))
ctx['form'] = form
ctx['attached_files'] = attached_files = []
for attached_file in document.attached_files.order_by('kind__position', 'kind__name', 'id'):
if attached_files and attached_files[-1][0] == attached_file.kind:
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)
ctx['related_users'] = get_related_users(request)
extra_senders = ''
if document.extra_senders.exists():
extra_senders = ', '.join([username(sender) for sender in document.extra_senders.all()])
ctx['extra_senders'] = extra_senders
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())
try:
page = parsed_doc.findAll(True, role='main')[0]
except IndexError: # newer yelp-tools
page = parsed_doc.findAll('main')[0]
for t in page.findAll('h4'):
t.name = 'h6'
for t in page.findAll('h3'):
t.name = 'h5'
for t in page.findAll('h2'):
t.name = 'h4'
for t in page.findAll('h1'):
t.name = 'h3'
return force_str(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)}
)
else:
filepath = os.path.join(settings.HELP_DIR, pagename)
if not os.path.abspath(filepath).startswith(settings.HELP_DIR):
return HttpResponseForbidden()
response = HttpResponse(content=open(filepath, 'rb'))
response['Content-Type'] = 'image/png'
return response
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')
request.error_record('error', msg)
messages.error(request, _(msg))
return redirect('inbox')
if request.method == 'POST':
form = form_class(request.POST)
if form.is_valid():
cleaned_data = form.cleaned_data
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
)
else:
reply_to = cleaned_data['email']
body = _('Message from %(name)s <%(email)s>') % cleaned_data
if cleaned_data.get('phone_number'):
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()
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'),
)
except (smtplib.SMTPException, OSError):
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'),
)
return redirect('inbox')
else:
form = form_class()
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):
'''Remove a document from the inbox'''
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))
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}
)
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()
)
request.record('delete-document', 'marked document {document} as deleted', document=document)
return redirect(back_pair[0])
def inbox_by_document(request, document_id):
return redirect('inbox-message', mailbox_id=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.delete()
except DeletedDocument.DoesNotExist:
pass
return_view = 'outbox-trash' if outbox else 'inbox-trash'
return redirect(return_view)
from django import http
from django.contrib.auth import BACKEND_SESSION_KEY, SESSION_KEY
def su(request, username, redirect_url='/'):
"""Allows changing user for super-users. Super-user status is kept using a
flag on the session.
"""
if request.user.is_superuser or request.session.get('has_superuser_power'):
request.session.pop('related_users', None)
su_user = get_object_or_404(User, username=username)
if su_user.is_active:
if is_guest(su_user):
real_username = su_user.username.rsplit('-', 1)[0]
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['has_superuser_power'] = True
else:
request.session[SESSION_KEY] = su_user.id
request.session['has_superuser_power'] = True
request.session[BACKEND_SESSION_KEY] = 'django.contrib.auth.backends.ModelBackend'
return http.HttpResponseRedirect(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})
def robots(request):
output = '''User-Agent: *
Disallow: /
'''
return HttpResponse(output, content_type='text/plain')
def password_reset_done(request):
messages.info(request, _('Email with password reset instruction has been sent.'))
return redirect('auth_login')
class DateFilterMixinView:
def get_filter_form(self):
if not hasattr(self, '_form'):
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)
self._form = form
return self._form
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['filter_form'] = self.get_filter_form()
return context
def get_queryset(self):
qs = super().get_queryset()
filter_form = self.get_filter_form()
if filter_form.is_valid():
not_before = filter_form.cleaned_data.get('not_before')
not_after = filter_form.cleaned_data.get('not_after')
search_terms = filter_form.cleaned_data.get('search_terms')
if not_before is not None:
not_before = date_to_aware_datetime(not_before)
qs = qs.filter(date__gte=not_before)
if not_after is not None:
not_after += datetime.timedelta(days=1)
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
)
qs = qs.filter(id__in=objects_ids)
return qs
class ExtraContextMixin:
extra_ctx = {}
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
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.
"""
if 'related_users' not in request.session:
users = [request.user]
delegations = request.user.delegations_by.select_related('by')
if delegations:
users.extend(delegation.by for delegation in delegations)
request.session['related_users'] = [user.pk for user in users]
return User.objects.filter(pk__in=request.session['related_users'])
class MailboxQuerysetMixin:
def get_queryset(self):
qs = super().get_queryset()
if hasattr(self.request.user, 'delegate'):
qs = qs.filter(private=False)
if not hasattr(self, '_qs'):
self._qs = sql.get_documents(
qs, get_related_users(self.request), self.request.user, self.outbox
).distinct()
return self._qs
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['related_users'] = get_related_users(self.request)
return ctx
class TrashMailboxQuerysetMixin:
def get_queryset(self):
qs = super().get_queryset()
if not hasattr(self, '_qs'):
self._qs = sql.get_trash_documents(
qs, get_related_users(self.request), self.request.user, self.outbox
).distinct()
return self._qs
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['related_users'] = get_related_users(self.request)
return ctx
class MailboxView(ExtraContextMixin, MailboxQuerysetMixin, tables_views.SingleTableView):
model = Document
table_class = tables.MailboxTable
table_pagination = {
'per_page': app_settings.DOCBOW_MAILBOX_PER_PAGE,
}
def get_context_data(self):
context = super().get_context_data()
context['show_trash'] = settings.TRASH_DURATION > 0
return context
def get_table_kwargs(self):
return get_table_kwargs()
class TrashMailboxView(ExtraContextMixin, TrashMailboxQuerysetMixin, tables_views.SingleTableView):
model = Document
table_class = tables.MailboxTable
table_pagination = {
'per_page': app_settings.DOCBOW_MAILBOX_PER_PAGE,
}
def get_table_kwargs(self):
return get_table_kwargs()
class CSVMultipleObjectMixin:
mapping = ()
filename = ''
def get_header(self):
return [column['caption'] for column in self.mapping]
def get_cell(self, mailbox, attribute):
value = operator.attrgetter(attribute)(mailbox)
if callable(value):
value = value()
return force_str(value)
def get_row(self, mailbox):
for column in self.mapping:
yield self.get_cell(mailbox, column['attribute'])
def get_rows(self):
qs = self.get_queryset()
for mailbox in qs:
yield list(self.get_row(mailbox))
def get(self, request, *args, **kwargs):
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="%s"' % self.filename
writer = unicodecsv.UnicodeWriter(response, encoding='utf-8')
writer.writerow(self.get_header())
writer.writerows(self.get_rows())
return response
class ODSMultipleObjectMixin(CSVMultipleObjectMixin):
worksheet_name = '1'
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')
sheet = workbook.add_sheet(self.worksheet_name)
rows = chain(((0, self.get_header()),), enumerate(self.get_rows(), 1))
for i, row in rows:
for j, cell in enumerate(row):
sheet.write(i, j, force_str(cell))
workbook.save(response)
return response
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()
)
def get_header(self):
table = self.get_table()
for column in table.columns:
yield column.header
def get_row(self, row):
for column, cell in row.items():
yield cell
def get_rows(self):
table = self.get_table()
for table_row in table.rows:
yield self.get_row(table_row)
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()
)
class CSVInboxView(CSVMailboxView):
outbox = False
table_class = tables.InboxCsvTable
filename_prefix = 'inbox'
inbox_csv = CSVInboxView.as_view()
class CSVOutboxView(CSVMailboxView):
outbox = True
table_class = tables.OutboxCsvTable
filename_prefix = 'outbox'
outbox_csv = CSVOutboxView.as_view()
class ODSInboxView(ODSMailboxView, CSVInboxView):
pass
inbox_ods = ODSInboxView.as_view()
class ODSOutboxView(ODSMailboxView, CSVOutboxView):
pass
outbox_ods = ODSOutboxView.as_view()
class DeleteDocumentsView:
def post(self, request, *args, **kwargs):
documents = None
selection = request.POST.get('selection', '')
if selection == 'everything':
documents = self.get_queryset()
else:
selection = selection.split(',')
if all(map(str.isdigit, selection)):
selection = map(int, selection)
documents = self.get_queryset().filter(id__in=selection)
if documents:
if 'delete' in request.POST:
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
]
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',
}
def get_context_data(self):
context = super().get_context_data()
context['trash_url'] = reverse('inbox-trash')
return context
inbox_view = login_required(InboxView.as_view())
class InboxTrashView(TrashMailboxView):
outbox = False
table_class = tables.InboxTrashTable
template_name = 'docbow/inbox_trash_list.html'
extra_ctx = {
'view_name': 'inbox_trash',
}
inbox_trash_view = login_required(InboxTrashView.as_view())
class OutboxView(DeleteDocumentsView, DateFilterMixinView, MailboxView):
outbox = True
table_class = tables.OutboxTable
template_name = 'docbow/outbox_list.html'
extra_ctx = {
'view_name': 'outbox',
}
def get_context_data(self):
context = super().get_context_data()
context['trash_url'] = reverse('outbox-trash')
return context
outbox_view = login_required(OutboxView.as_view())
class OutboxTrashView(TrashMailboxView):
outbox = True
table_class = tables.OutboxTrashTable
template_name = 'docbow/outbox_trash_list.html'
extra_ctx = {
'view_name': 'outbox_trash',
}
outbox_trash_view = login_required(OutboxTrashView.as_view())
class SendFileSelectorView(ListView):
template_name = 'docbow/send_file_selector.html'
model = FileType
def get_queryset(self):
qs = super().get_queryset()
limitations = get_filetype_limitation(self.request.user)
if limitations:
return limitations
return qs.filter(is_active=True)
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())))
)
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,
)
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())]
for word in match_words:
if word in choices:
continue
choices.append(word)
if len(choices) == limit_choices:
break
if len(choices) == limit_choices:
break
return JsonResponse(choices, safe=False)
@login_required
def search_inbox(request):
return search_terms(request, outbox=False)
@login_required
def search_outbox(request):
return search_terms(request, outbox=True)