Source code for organizations.views

__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
__license__ = "AGPL v3"


from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import UserPassesTestMixin
from django.core.exceptions import PermissionDenied
from django.urls import reverse_lazy
from django.db import transaction
from django.db.models import Q
from django.shortcuts import get_object_or_404, render, redirect
from django.urls import reverse
from django.utils import timezone
from django.utils.html import format_html
from django.views.generic.detail import DetailView
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.views.generic.list import ListView

from dal import autocomplete
from guardian.decorators import permission_required

from .constants import ORGTYPE_PRIVATE_BENEFACTOR,\
    ORGANIZATION_EVENT_COMMENT, ORGANIZATION_EVENT_EMAIL_SENT
from .forms import OrganizationEventForm, ContactPersonForm,\
    NewContactForm, ContactActivationForm, ContactRoleForm
from .models import Organization, OrganizationEvent, ContactPerson, Contact, ContactRole

from funders.models import Funder
from mails.utils import DirectMailUtil
from mails.views import MailEditorSubview
from organizations.decorators import has_contact
from organizations.models import Organization

from scipost.mixins import PermissionsMixin, PaginationMixin


######################
# Autocomplete views #
######################

[docs]class OrganizationAutocompleteView(autocomplete.Select2QuerySetView): """ View to feed the Select2 widget. Flags of the organizations are displayed in the selection list; the stylesheet flags/sprite-hq.css from app django-countries must be accessible on the page for the flag to be displayed properly; we include it centrally in static and put this in the page head: .. code-block:: html <link rel="stylesheet" href="{% static 'flags/sprite-hq.css' %}"> The data-html attribute has to be set to True on all widgets, e.g. .. code-block:: python organization = forms.ModelChoiceField( queryset=Organization.objects.all(), widget=autocomplete.ModelSelect2( url='/organizations/organization-autocomplete', attrs={'data-html': True} ) ) """
[docs] def get_queryset(self): qs = Organization.objects.all() if self.q: qs = qs.filter( Q(name__icontains=self.q) | Q(name_original__icontains=self.q) | Q(acronym__icontains=self.q)) return qs
[docs] def get_result_label(self, item): return format_html( '<span><i class="{}" data-toggle="tooltip" title="{}"></i>&emsp;{}</span>', item.country.flag_css, item.country.name, item.name)
[docs]class OrganizationCreateView(PermissionsMixin, CreateView): """ Create a new Organization. """ permission_required = 'scipost.can_manage_organizations' model = Organization fields = '__all__' template_name = 'organizations/organization_create.html' success_url = reverse_lazy('organizations:organizations')
[docs]class OrganizationUpdateView(PermissionsMixin, UpdateView): """ Update an Organization. """ permission_required = 'scipost.can_manage_organizations' model = Organization fields = '__all__' template_name = 'organizations/organization_update.html' success_url = reverse_lazy('organizations:organizations')
[docs]class OrganizationDeleteView(PermissionsMixin, DeleteView): """ Delete an Organization. """ permission_required = 'scipost.can_manage_organizations' model = Organization success_url = reverse_lazy('organizations:organizations')
[docs]class OrganizationListView(PaginationMixin, ListView): model = Organization paginate_by = 50
[docs] def get_context_data(self, *args, **kwargs): context = super().get_context_data(*args, **kwargs) if self.request.user.has_perm('scipost.can_manage_organizations'): context['nr_funders_wo_organization'] = Funder.objects.filter(organization=None).count() context['pubyears'] = range(int(timezone.now().strftime('%Y')), 2015, -1) context['countrycodes'] = [code['country'] for code in list( Organization.objects.all().distinct('country').values('country'))] return context
[docs] def get_queryset(self): qs = super().get_queryset().exclude(orgtype=ORGTYPE_PRIVATE_BENEFACTOR) country = self.request.GET.get('country') order_by = self.request.GET.get('order_by') ordering = self.request.GET.get('ordering') if country: qs = qs.filter(country=country) if order_by == 'country': qs = qs.order_by('country') elif order_by == 'name': qs = qs.order_by('name') elif order_by == 'nap': qs = qs.exclude(cf_nr_associated_publications__isnull=True ).order_by('cf_nr_associated_publications') if ordering == 'desc': qs = qs.reverse() return qs
[docs]class OrganizationDetailView(DetailView): model = Organization
[docs] def get_context_data(self, *args, **kwargs): context = super().get_context_data(*args, **kwargs) context['pubyears'] = range(int(timezone.now().strftime('%Y')), 2015, -1) return context
[docs] def get_queryset(self): """ Restrict view to permitted people if Organization details not publicly viewable. """ queryset = super().get_queryset() if not self.request.user.has_perm('scipost.can_manage_organizations'): queryset = queryset.exclude(orgtype=ORGTYPE_PRIVATE_BENEFACTOR) return queryset
[docs]class OrganizationEventCreateView(PermissionsMixin, CreateView): permission_required = 'scipost.can_manage_organizations' model = OrganizationEvent form_class = OrganizationEventForm template_name = 'organizations/organizationevent_form.html'
[docs] def get_initial(self): organization = get_object_or_404(Organization, pk=self.kwargs.get('pk')) return {'organization': organization, 'noted_on': timezone.now, 'noted_by': self.request.user}
[docs] def get_success_url(self): return reverse_lazy('organizations:organization_details', kwargs={'pk': self.object.organization.id})
[docs]class OrganizationEventListView(PermissionsMixin, PaginationMixin, ListView): permission_required = 'scipost.can_manage_organizations' model = OrganizationEvent paginate_by = 10
[docs]class ContactPersonCreateView(PermissionsMixin, CreateView): permission_required = 'scipost.can_add_contactperson' model = ContactPerson form_class= ContactPersonForm template_name = 'organizations/contactperson_form.html'
[docs] def get_initial(self): try: organization = Organization.objects.get(pk=self.kwargs.get('organization_id')) return {'organization': organization} except Organization.DoesNotExist: pass
[docs] def form_valid(self, form): event = OrganizationEvent( organization=form.cleaned_data['organization'], event=ORGANIZATION_EVENT_COMMENT, comments=('Added ContactPerson: %s %s' % (form.cleaned_data['first_name'], form.cleaned_data['last_name'])), noted_on=timezone.now(), noted_by=self.request.user) event.save() return super().form_valid(form)
[docs] def get_success_url(self): return reverse_lazy('organizations:organization_details', kwargs={'pk': self.object.organization.id})
[docs]class ContactPersonUpdateView(PermissionsMixin, UpdateView): permission_required = 'scipost.can_add_contactperson' model = ContactPerson form_class= ContactPersonForm template_name = 'organizations/contactperson_form.html'
[docs] def form_valid(self, form): event = OrganizationEvent( organization=form.cleaned_data['organization'], event=ORGANIZATION_EVENT_COMMENT, comments=('Updated ContactPerson: %s %s' % (form.cleaned_data['first_name'], form.cleaned_data['last_name'])), noted_on=timezone.now(), noted_by=self.request.user) event.save() return super().form_valid(form)
[docs] def get_success_url(self): return reverse_lazy('organizations:organization_details', kwargs={'pk': self.object.organization.id})
[docs]class ContactPersonDeleteView(UserPassesTestMixin, DeleteView): model = ContactPerson
[docs] def test_func(self): """ Allow ContactPerson delete to OrgAdmins and all Contacts for this Organization. """ if self.request.user.has_perm('scipost.can_manage_organizations'): return True contactperson = get_object_or_404(ContactPerson, pk=self.kwargs.get('pk')) return self.request.user.has_perm('can_view_org_contacts', contactperson.organization)
[docs] def get_success_url(self): return reverse_lazy('organizations:organization_details', kwargs={'pk': self.object.organization.id})
[docs]class ContactPersonListView(PermissionsMixin, ListView): permission_required = 'scipost.can_add_contactperson' model = ContactPerson
[docs]@permission_required('scipost.can_manage_organizations', return_403=True) @transaction.atomic def email_contactperson(request, contactperson_id, mail=None): contactperson = get_object_or_404(ContactPerson, pk=contactperson_id) suffix = '' if mail == 'followup': mail_code = 'org_contacts/contactperson_followup_mail' suffix = ' (followup)' else: mail_code = 'org_contacts/contactperson_initial_mail' suffix = ' (initial)' mail_request = MailEditorSubview(request, mail_code, contactperson=contactperson) if mail_request.is_valid(): comments = 'Email{suffix} sent to ContactPerson {name}.'.format( suffix=suffix, name=contactperson) event = OrganizationEvent( organization=contactperson.organization, event=ORGANIZATION_EVENT_EMAIL_SENT, comments=comments, noted_on=timezone.now(), noted_by=request.user) event.save() messages.success(request, 'Email successfully sent.') mail_request.send_mail() return redirect(contactperson.organization.get_absolute_url()) else: return mail_request.interrupt()
[docs]@permission_required('scipost.can_manage_organizations', return_403=True) def organization_add_contact(request, organization_id, contactperson_id=None): organization = get_object_or_404(Organization, id=organization_id) if contactperson_id: contactperson = get_object_or_404(ContactPerson, id=contactperson_id) initial = { 'title': contactperson.title, 'first_name': contactperson.first_name, 'last_name': contactperson.last_name, 'email': contactperson.email } else: contactperson = None initial = {} form = NewContactForm(request.POST or None, initial=initial, organization=organization, contactperson=contactperson ) if form.is_valid(): contact = form.save(current_user=request.user) mail_sender = DirectMailUtil('org_contacts/email_contact_for_activation', contact=contact) mail_sender.send_mail() for role in contact.roles.all(): event = OrganizationEvent( organization=role.organization, event=ORGANIZATION_EVENT_COMMENT, comments=('Contact for %s created; activation pending' % str(contact)), noted_on=timezone.now(), noted_by=request.user) event.save() messages.success(request, '<h3>Created contact: %s</h3>Email has been sent.' % str(contact)) return redirect(reverse('organizations:organization_details', kwargs={'pk': organization.id})) context = { 'organization': organization, 'form': form } return render(request, 'organizations/organization_add_contact.html', context)
[docs]def activate_account(request, activation_key): contact = get_object_or_404(Contact, user__is_active=False, activation_key=activation_key, user__email__icontains=request.GET.get('email', None)) # TODO: Key Expires fallback form = ContactActivationForm(request.POST or None, instance=contact.user) if form.is_valid(): form.activate_user() for role in contact.roles.all(): event = OrganizationEvent( organization=role.organization, event=ORGANIZATION_EVENT_COMMENT, comments=('Contact %s activated their account' % str(contact)), noted_on=timezone.now(), noted_by=contact.user) event.save() messages.success(request, '<h3>Thank you for activating your account</h3>') return redirect(reverse('organizations:dashboard')) context = { 'contact': contact, 'form': form } return render(request, 'organizations/activate_account.html', context)
[docs]@login_required def dashboard(request): """ Administration page for Organization Contacts. This page is meant as a personal page for Contacts, where they will for example be able to read their personal data and agreements. """ if not (request.user.has_perm('scipost.can_manage_organizations') or has_contact(request.user)): raise PermissionDenied context = { 'contacts': Contact.objects.all() } if has_contact(request.user): context['own_roles'] = request.user.org_contact.roles.all() return render(request, 'organizations/dashboard.html', context)
[docs]class ContactDetailView(PermissionsMixin, DetailView): """ View details of a Contact. Accessible to Admin. """ permission_required = 'scipost.can_manage_organizations' model = Contact
[docs]class ContactRoleUpdateView(UserPassesTestMixin, UpdateView): """ Update a ContactRole. """ model = ContactRole form_class = ContactRoleForm template_name = 'organizations/contactrole_form.html'
[docs] def test_func(self): """ Allow ContactRole update to OrgAdmins and all Contacts for this Organization. """ if self.request.user.has_perm('scipost.can_manage_organizations'): return True contactrole = get_object_or_404(ContactRole, pk=self.kwargs.get('pk')) return self.request.user.has_perm('can_view_org_contacts', contactrole.organization)
[docs] def get_success_url(self): return reverse_lazy('organizations:organization_details', kwargs={'pk': self.object.organization.id})
[docs]class ContactRoleDeleteView(PermissionsMixin, DeleteView): """ Delete a ContactRole. """ permission_required = 'scipost.can_manage_organizations' model = ContactRole
[docs] def get_success_url(self): return reverse_lazy('organizations:organization_details', kwargs={'pk': self.object.organization.id})
[docs]@permission_required('scipost.can_manage_organizations', return_403=True) @transaction.atomic def email_contactrole(request, contactrole_id, mail=None): contactrole = get_object_or_404(ContactRole, pk=contactrole_id) suffix = '' if mail == 'renewal': mail_code = 'org_contacts/contactrole_subsidy_renewal_mail' suffix = ' (subsidy renewal query)' else: mail_code = 'org_contacts/contactrole_generic_mail' suffix = ' (generic)' mail_request = MailEditorSubview(request, mail_code, contactrole=contactrole) if mail_request.is_valid(): comments = 'Email{suffix} sent to Contact {name}.'.format( suffix=suffix, name=contactrole.contact) event = OrganizationEvent( organization=contactrole.organization, event=ORGANIZATION_EVENT_EMAIL_SENT, comments=comments, noted_on=timezone.now(), noted_by=request.user) event.save() messages.success(request, 'Email successfully sent.') mail_request.send_mail() return redirect(contactrole.organization.get_absolute_url()) else: return mail_request.interrupt()