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> {}</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()