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 (
    SelectOrganizationForm,
    OrganizationForm,
    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-bs-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" form_class = OrganizationForm 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 form_class = OrganizationForm 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") ) ] context["select_organization_form"] = SelectOrganizationForm() 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.prefetch_related('logos')
[docs]def get_organization_detail(request): org_id = request.GET.get("organization", None) if org_id: return redirect( reverse("organizations:organization_detail", kwargs={"pk": org_id}) ) return redirect(reverse("organizations:organizations"))
[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) #context["balance"] = self.object.get_balance_info() context["balance"] = self.object.cf_balance_info 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 return queryset.select_related("parent").prefetch_related( "children", "subsidy_set", "contactperson_set", "contactrole_set", "funder_set", "organizationevent_set", "pubfractions", )
[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_detail", 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_detail", 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_detail", 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_detail", 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_detail", 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_detail", 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_detail", 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()