diff --git a/finances/templates/finances/subsidy_list.html b/finances/templates/finances/subsidy_list.html index a3668a1491bd14172e022c8ab81acd1c48ba8b58..8997c30f95edd0a061909210af700e6ce7577cbd 100644 --- a/finances/templates/finances/subsidy_list.html +++ b/finances/templates/finances/subsidy_list.html @@ -3,6 +3,7 @@ {% block pagetitle %}: Subsidies{% endblock pagetitle %} {% load staticfiles %} +{% load bootstrap %} {% block headsup %} <script type="text/javascript"> diff --git a/organizations/forms.py b/organizations/forms.py index f44363265df23cd0e912bbf08366a8bad4416599..56f605f937e78e16a8f575fe6f2d1ec77097f31c 100644 --- a/organizations/forms.py +++ b/organizations/forms.py @@ -31,6 +31,23 @@ class ContactPersonForm(forms.ModelForm): model = ContactPerson fields = '__all__' + def clean_email(self): + """ + Check if the email is already associated to an existing ContactPerson or Contact. + """ + email = self.cleaned_data['email'] + try: + ContactPerson.objects.get(email=email) + self.add_error('email', 'This email is already associated to a Contact Person.') + except ContactPerson.DoesNotExist: + pass + try: + Contact.objects.get(user__email=email) + self.add_error('email', 'This email is already associated to a Contact.') + except Contact.DoesNotExist: + pass + return email + class UpdateContactDataForm(forms.ModelForm): """ diff --git a/organizations/migrations/0009_auto_20190223_1001.py b/organizations/migrations/0009_auto_20190223_1001.py new file mode 100644 index 0000000000000000000000000000000000000000..c93eb96c7cc628ef24fda101a2794696bf0ea790 --- /dev/null +++ b/organizations/migrations/0009_auto_20190223_1001.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2019-02-23 09:01 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('organizations', '0008_auto_20190222_1120'), + ] + + operations = [ + migrations.AlterModelOptions( + name='contactperson', + options={'ordering': ['last_name', 'first_name', 'organization']}, + ), + ] diff --git a/organizations/models.py b/organizations/models.py index 039e35b8b8fb125c50cf28db6fe7497902a02c0a..3085a008196e8b972ded6adb106dab774cc778cb 100644 --- a/organizations/models.py +++ b/organizations/models.py @@ -234,6 +234,9 @@ class ContactPerson(models.Model): email = models.EmailField() role = models.CharField(max_length=128) + class Meta: + ordering = ['last_name', 'first_name', 'organization'] + def __str__(self): return "%s %s %s" % (self.get_title_display(), self.first_name, self.last_name) diff --git a/organizations/templates/organizations/_organization_card.html b/organizations/templates/organizations/_organization_card.html index 3c9e4e89ea51735ecb098a2054b72122369a42f4..1f85d191c4bf62c6e53953b5d6ef107f4b5aa9e4 100644 --- a/organizations/templates/organizations/_organization_card.html +++ b/organizations/templates/organizations/_organization_card.html @@ -193,7 +193,7 @@ $(document).ready(function($) { {% if perms.scipost.can_manage_organizations or "can_view_org_contacts" in user_org_perms %} <td>{% if contactrole.contact.user.is_active %}<i class="fa fa-check-circle text-success"></i>{% else %}<i class="fa fa-times-circle text-danger"></i>{% endif %}</td> <td> - <ul> + <ul class="list-unstyled"> {% if perms.scipost.can_manage_organizations %} <li><a href="{% url 'organizations:email_contactrole' contactrole_id=contactrole.id %}">Email (generic)</a></li> <li><a href="{% url 'organizations:email_contactrole' contactrole_id=contactrole.id mail='renewal' %}">Email (subsidy renewal)</a></li> @@ -225,7 +225,7 @@ $(document).ready(function($) { <td>{{ contactperson.role }}</td> {% if perms.scipost.can_manage_organizations or "can_view_org_contacts" in user_org_perms %} <td> - <ul> + <ul class="list-unstyled"> {% if perms.scipost.can_manage_organizations %} <li><a href="{% url 'organizations:email_contactperson' contactperson_id=contactperson.id %}">Email (initial)</a></li> <li><a href="{% url 'organizations:email_contactperson' contactperson_id=contactperson.id mail='followup' %}">Email (followup)</a></li> diff --git a/organizations/templates/organizations/contactperson_list.html b/organizations/templates/organizations/contactperson_list.html new file mode 100644 index 0000000000000000000000000000000000000000..94a9e2d511640a2ddb414f2dbb0e024e1c092a59 --- /dev/null +++ b/organizations/templates/organizations/contactperson_list.html @@ -0,0 +1,48 @@ +{% extends 'scipost/base.html' %} + +{% block pagetitle %}: Contact Persons{% endblock pagetitle %} + +{% load bootstrap %} + +{% block breadcrumb_items %} +{{ block.super }} +<span class="breadcrumb-item">Contact Persons</span> +{% endblock %} + +{% block content %} + +<div class="row"> + <div class="col-12"> + <h3 class="highlight">Contact Persons</h3> + <p>Contact Persons are employees of an Organization, who play a role of potential relevance to SciPost's sponsoring efforts.</p> + <p>SciPost Admin takes charge of contacting Contact Persons listed here, to try to promote them to registered Contacts (members of the Sponsors Board).</p> + <p>Do you people who you think should appear on this list? Help us out: <a href="{% url 'organizations:contactperson_create' %}">add a Contact Person</a></p> + + <table class="table"> + <thead class="thead-default"> + <tr> + <td>Name</td> + <td>Organization</td> + <td>Role</td> + <td>Actions</td> + </tr> + </thead> + <tbody> + {% for cp in object_list %} + <tr> + <td>{{ cp.last_name }}, {{ cp.get_title_display }} {{ cp.first_name }}</td> + <td><a href="{{ cp.organization.get_absolute_url }}">{{ cp.organization }}</a></td> + <td>{{ cp.role }}</td> + <td> + <ul class="list-unstyled"> + <li><a href="{% url 'organizations:contactperson_update' pk=cp.id %}"><span class="text-warning">Update</span></a></li> + </ul> + </td> + </tr> + {% endfor %} + </tbody> + </table> + </div> +</div> + +{% endblock content %} diff --git a/organizations/templates/organizations/dashboard.html b/organizations/templates/organizations/dashboard.html index 1f5e7db220351c4c2e32f02d2e27080e501b5e18..3feb6ec5a81c9a059ad27cc62a00beee76ee78e4 100644 --- a/organizations/templates/organizations/dashboard.html +++ b/organizations/templates/organizations/dashboard.html @@ -43,7 +43,7 @@ $(document).ready(function($) { </li> {% endif %} <li class="nav-item btn btn-outline-secondary"> - <a href="#board" class="nav-link" data-toggle="tab">Sponsors Board</a> + <a href="#board" class="nav-link" data-toggle="tab">Sponsors Board<br/><span class="small text-muted">[registered Contacts]</span></a> </li> </ul> </div> @@ -163,7 +163,7 @@ $(document).ready(function($) { <div class="row"> <div class="col-12"> <p>The Sponsors Board is composed of all registered Organization Contacts.</p> - + <p>Do you know people who you think should appear on this list? Help us by checking if they are already on our <a href="{% url 'organizations:contactperson_list' %}" target="_blank">list of Contact Persons</a>, and if not, please add them!</p> <h3>Active Contacts</h3> <table class="table"> <thead class="thead-default"> diff --git a/organizations/urls.py b/organizations/urls.py index 726f6851427d7bf6b473a5f229b293fb73f43ef2..91d8ffacfbddbe302dd8f2a93c558a57b3499d5a 100644 --- a/organizations/urls.py +++ b/organizations/urls.py @@ -42,6 +42,11 @@ urlpatterns = [ views.ContactPersonCreateView.as_view(), name='contactperson_create' ), + url( + r'^contactperson/add/$', + views.ContactPersonCreateView.as_view(), + name='contactperson_create' + ), url( r'^contactperson/(?P<pk>[0-9]+)/update/$', views.ContactPersonUpdateView.as_view(), @@ -52,6 +57,11 @@ urlpatterns = [ views.ContactPersonDeleteView.as_view(), name='contactperson_delete' ), + url( + r'^contactpersons/$', + views.ContactPersonListView.as_view(), + name='contactperson_list' + ), url( r'^contactperson/(?P<contactperson_id>[0-9]+)/email/(?P<mail>followup)$', views.email_contactperson, diff --git a/organizations/views.py b/organizations/views.py index 4f06204d1a54ce2708fd5afc5b8a6b9c8b71da86..cece8973e6a94f2ed6f8eff95112846d1a87ebee 100644 --- a/organizations/views.py +++ b/organizations/views.py @@ -16,7 +16,8 @@ from django.views.generic.list import ListView from guardian.decorators import permission_required -from .constants import ORGTYPE_PRIVATE_BENEFACTOR, ORGANIZATION_EVENT_EMAIL_SENT +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 @@ -137,8 +138,22 @@ class ContactPersonCreateView(PermissionsMixin, CreateView): template_name = 'organizations/contactperson_form.html' def get_initial(self): - organization = get_object_or_404(Organization, pk=self.kwargs.get('organization_id')) - return {'organization': organization} + try: + organization = Organization.objects.get(pk=self.kwargs.get('organization_id')) + return {'organization': organization} + except Organization.DoesNotExist: + pass + + 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) def get_success_url(self): return reverse_lazy('organizations:organization_details', @@ -151,6 +166,17 @@ class ContactPersonUpdateView(PermissionsMixin, UpdateView): form_class= ContactPersonForm template_name = 'organizations/contactperson_form.html' + 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) + def get_success_url(self): return reverse_lazy('organizations:organization_details', kwargs={'pk': self.object.organization.id}) @@ -173,6 +199,11 @@ class ContactPersonDeleteView(UserPassesTestMixin, DeleteView): kwargs={'pk': self.object.organization.id}) +class ContactPersonListView(PermissionsMixin, ListView): + permission_required = 'scipost.can_add_contactperson' + model = ContactPerson + + @permission_required('scipost.can_manage_organizations', return_403=True) @transaction.atomic def email_contactperson(request, contactperson_id, mail=None):