From 71eab354591152c246a01dbd3b06114f0ecd400e Mon Sep 17 00:00:00 2001
From: Jorran de Wit <jorrandewit@outlook.com>
Date: Wed, 1 Nov 2017 22:10:54 +0100
Subject: [PATCH] Fully integrate "dumb" affiliations system

---
 affiliations/__init__.py                      |  1 +
 affiliations/apps.py                          |  8 +++
 affiliations/forms.py                         |  2 +-
 .../migrations/0004_auto_20171101_2208.py     | 19 +++++
 affiliations/models.py                        | 10 ++-
 affiliations/signals.py                       | 17 +++++
 affiliations/views.py                         |  5 +-
 notifications/migrations/0003_fakeactors.py   | 22 ++++++
 notifications/models.py                       | 10 +++
 petitions/views.py                            |  4 +-
 .../partials/proceedings/description.html     |  2 +-
 scipost/forms.py                              | 70 +++++++++++--------
 scipost/migrations/0067_auto_20171101_2132.py | 31 ++++++++
 scipost/migrations/0068_auto_20171101_2132.py | 20 ++++++
 scipost/models.py                             |  8 +--
 .../scipost/_private_info_as_table.html       |  4 +-
 .../scipost/_public_info_as_table.html        |  4 +-
 .../submissions/_report_tex_template.html     |  2 +-
 18 files changed, 192 insertions(+), 47 deletions(-)
 create mode 100644 affiliations/migrations/0004_auto_20171101_2208.py
 create mode 100644 affiliations/signals.py
 create mode 100644 notifications/migrations/0003_fakeactors.py
 create mode 100644 scipost/migrations/0067_auto_20171101_2132.py
 create mode 100644 scipost/migrations/0068_auto_20171101_2132.py

diff --git a/affiliations/__init__.py b/affiliations/__init__.py
index e69de29bb..82323b3df 100644
--- a/affiliations/__init__.py
+++ b/affiliations/__init__.py
@@ -0,0 +1 @@
+default_app_config = 'affiliations.apps.AffiliationsConfig'
diff --git a/affiliations/apps.py b/affiliations/apps.py
index 4be3888cf..3b0241f47 100644
--- a/affiliations/apps.py
+++ b/affiliations/apps.py
@@ -1,5 +1,13 @@
 from django.apps import AppConfig
+from django.db.models.signals import post_save
 
 
 class AffiliationsConfig(AppConfig):
     name = 'affiliations'
+
+    def ready(self):
+        super().ready()
+
+        from . import models, signals
+        post_save.connect(signals.notify_new_affiliation,
+                          sender=models.Affiliation)
diff --git a/affiliations/forms.py b/affiliations/forms.py
index f7cfd8f01..5fc623c48 100644
--- a/affiliations/forms.py
+++ b/affiliations/forms.py
@@ -17,6 +17,6 @@ class AffiliationMergeForm(forms.ModelForm):
     def save(self, commit=True):
         old_affiliation = self.cleaned_data['affiliation']
         if commit:
-            old_affiliation.contributors.update(_affiliation=self.instance)
+            old_affiliation.contributors.update(affiliation=self.instance)
             old_affiliation.delete()
         return self.instance
diff --git a/affiliations/migrations/0004_auto_20171101_2208.py b/affiliations/migrations/0004_auto_20171101_2208.py
new file mode 100644
index 000000000..4b85c2f77
--- /dev/null
+++ b/affiliations/migrations/0004_auto_20171101_2208.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-11-01 21:08
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('affiliations', '0003_auto_20171101_2022'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='affiliation',
+            options={'ordering': ['country']},
+        ),
+    ]
diff --git a/affiliations/models.py b/affiliations/models.py
index f5c175605..974921535 100644
--- a/affiliations/models.py
+++ b/affiliations/models.py
@@ -1,4 +1,5 @@
 from django.db import models
+from django.urls import reverse
 
 from django_countries.fields import CountryField
 
@@ -16,5 +17,12 @@ class Affiliation(models.Model):
     type = models.CharField(max_length=16, choices=AFFILIATION_TYPES,
                             default=AFFILIATION_UNIVERSITY)
 
+    class Meta:
+        default_related_name = 'affiliations'
+        ordering = ['country']
+
     def __str__(self):
-        return self.name
+        return '{name} ({country})'.format(name=self.name, country=self.get_country_display())
+
+    def get_absolute_url(self):
+        return reverse('affiliations:affiliation_details', args=(self.object.id,))
diff --git a/affiliations/signals.py b/affiliations/signals.py
new file mode 100644
index 000000000..c0e02335c
--- /dev/null
+++ b/affiliations/signals.py
@@ -0,0 +1,17 @@
+from django.contrib.auth.models import User
+
+from notifications.models import FakeActors
+from notifications.signals import notify
+
+
+def notify_new_affiliation(sender, instance, created, **kwargs):
+    """
+    Notify the SciPost Administration about a new Affiliation created to check it.
+    """
+    if created:
+        administrators = User.objects.filter(groups__name='SciPost Administrators')
+        actor, __ = FakeActors.objects.get_or_create(name='A SciPost user')
+        for user in administrators:
+            notify.send(sender=sender, recipient=user, actor=actor,
+                        verb=' created a new Affiliation instance. You may want to validate it.',
+                        target=instance)
diff --git a/affiliations/views.py b/affiliations/views.py
index d31da46ba..bae15ec4b 100644
--- a/affiliations/views.py
+++ b/affiliations/views.py
@@ -1,4 +1,4 @@
-from django.shortcuts import render, redirect
+from django.shortcuts import redirect
 from django.contrib import messages
 from django.urls import reverse
 from django.views.generic.edit import UpdateView
@@ -32,9 +32,6 @@ class AffiliationUpdateView(UpdateView):
         messages.success(self.request, 'Affiliation saved')
         return super().form_valid(*args, **kwargs)
 
-    def get_success_url(self):
-        return reverse('affiliations:affiliation_details', args=(self.object.id,))
-
 
 def merge_affiliations(request, affiliation_id):
     """
diff --git a/notifications/migrations/0003_fakeactors.py b/notifications/migrations/0003_fakeactors.py
new file mode 100644
index 000000000..d254da9d3
--- /dev/null
+++ b/notifications/migrations/0003_fakeactors.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-11-01 21:08
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('notifications', '0002_auto_20171021_1821'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='FakeActors',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=256)),
+            ],
+        ),
+    ]
diff --git a/notifications/models.py b/notifications/models.py
index d7d9fef89..4e54bf7c8 100644
--- a/notifications/models.py
+++ b/notifications/models.py
@@ -9,6 +9,16 @@ from .constants import NOTIFICATION_TYPES
 from .managers import NotificationQuerySet
 
 
+class FakeActors(models.Model):
+    """
+    This Model acts as a surrogate person that either is unknown, deceased, fake, etc. etc.
+    """
+    name = models.CharField(max_length=256)
+
+    def __str__(self):
+        return self.name
+
+
 class Notification(models.Model):
     """
     Action model describing the actor acting out a verb (on an optional
diff --git a/petitions/views.py b/petitions/views.py
index b702d8078..86c527ccc 100644
--- a/petitions/views.py
+++ b/petitions/views.py
@@ -26,8 +26,8 @@ def petition(request, slug):
             'first_name': request.user.first_name,
             'last_name': request.user.last_name,
             'email': request.user.email,
-            'country_of_employment': request.user.contributor.country_of_employment,
-            'affiliation': request.user.contributor.affiliation,
+            'country_of_employment': request.user.contributor.affiliation.country_of_employment,
+            'affiliation': request.user.contributor.affiliation.name,
         }
 
     form = SignPetitionForm(request.POST or None, initial=initial, petition=petition,
diff --git a/proceedings/templates/partials/proceedings/description.html b/proceedings/templates/partials/proceedings/description.html
index d6ae4a116..df878ae1e 100644
--- a/proceedings/templates/partials/proceedings/description.html
+++ b/proceedings/templates/partials/proceedings/description.html
@@ -15,7 +15,7 @@
     <h3>Guest Fellows responsible for this Issue</h3>
     <ul>
         {% for fellow in proceedings.fellowships.guests %}
-            <li>{{ fellow.contributor.get_title_display }} {{ fellow.contributor.user.first_name }} {{ fellow.contributor.user.last_name }}{% if fellow.contributor.affiliation %}, {{ fellow.contributor.affiliation }}{% endif %}</li>
+            <li>{{ fellow.contributor.get_title_display }} {{ fellow.contributor.user.first_name }} {{ fellow.contributor.user.last_name }}{% if fellow.contributor.affiliation.name %}, {{ fellow.contributor.affiliation.name }}{% endif %}</li>
         {% endfor %}
     </ul>
 {% endif %}
diff --git a/scipost/forms.py b/scipost/forms.py
index 614c14f44..b0164430e 100644
--- a/scipost/forms.py
+++ b/scipost/forms.py
@@ -24,6 +24,7 @@ from .decorators import has_contributor
 from .models import Contributor, DraftInvitation, RegistrationInvitation,\
                     UnavailabilityPeriod, PrecookedEmail
 
+from affiliations.models import Affiliation
 from common.forms import MonthYearWidget
 from partners.decorators import has_contact
 
@@ -115,14 +116,17 @@ class RegistrationForm(forms.Form):
             'password': self.cleaned_data['password'],
             'is_active': False
         })
+        affiliation, __ = Affiliation.objects.get_or_create(
+            country=self.cleaned_data['country_of_employment'],
+            name=self.cleaned_data['affiliation'],
+        )
         contributor, new = Contributor.objects.get_or_create(**{
             'user': user,
             'invitation_key': self.cleaned_data.get('invitation_key', ''),
             'title': self.cleaned_data['title'],
             'orcid_id': self.cleaned_data['orcid_id'],
-            'country_of_employment': self.cleaned_data['country_of_employment'],
             'address': self.cleaned_data['address'],
-            'affiliation': self.cleaned_data['affiliation'],
+            'affiliation': affiliation,
             'personalwebpage': self.cleaned_data['personalwebpage'],
         })
 
@@ -257,39 +261,47 @@ class UpdateUserDataForm(forms.ModelForm):
 
 
 class UpdatePersonalDataForm(forms.ModelForm):
+    country_of_employment = LazyTypedChoiceField(choices=countries, widget=CountrySelectWidget())
+    affiliation = forms.CharField(max_length=300)
+
     class Meta:
         model = Contributor
-        fields = ['title', 'discipline', 'expertises', 'orcid_id', 'country_of_employment',
-                  'affiliation', 'address', 'personalwebpage'
-                  ]
-        widgets = {'country_of_employment': CountrySelectWidget()}
+        fields = [
+            'title',
+            'discipline',
+            'expertises',
+            'orcid_id',
+            'address',
+            'personalwebpage'
+        ]
 
-    # def __init__(self, *args, **kwargs):
-    #     super().__init__(*args, **kwargs)
-    #     self.fields['mail_subscription'] = forms.ModelMultipleChoiceField(
-    #             queryset=MailchimpList.objects.open_to_subscribe(kwargs['instance']).distinct(),
-    #             widget=forms.CheckboxSelectMultiple(),
-    #             label='Subscribe to the following mailing lists:',
-    #             required=False)
-    #     self.fields['mailing_lists'] = forms.ModelMultipleChoiceField(
-    #             queryset=MailchimpList.objects.open_to_subscribe(kwargs['instance']).distinct(),
-    #             widget=forms.CheckboxSelectMultiple(),
-    #             label='Subscribe to the following mailing lists:',
-    #             required=False)
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.fields['country_of_employment'].initial = self.instance.affiliation.country
+        self.fields['affiliation'].initial = self.instance.affiliation.name
+
+    def save(self, commit=True):
+        contributor = super().save(commit)
+        if commit:
+            if contributor.affiliation.contributors.count() == 1:
+                # Just update if there are no other people using this Affiliation
+                affiliation = contributor.affiliation
+                affiliation.name = self.cleaned_data['affiliation']
+                affiliation.country = self.cleaned_data['country_of_employment']
+                affiliation.save()
+            else:
+                affiliation, __ = Affiliation.objects.get_or_create(
+                    name=self.cleaned_data['affiliation'],
+                    country=self.cleaned_data['country_of_employment'])
+                contributor.affiliation = affiliation
+                contributor.save()
+        return contributor
 
     def sync_lists(self):
+        """
+        Pseudo U/S; do not remove
+        """
         return
-        # contributor = self.instance
-        # original_lists = list(self.fields['mailing_lists'].queryset)
-        #
-        # # Subscribe to lists
-        # for _list in self.cleaned_data['mailing_lists']:
-        #     _list.update_membership([contributor])
-        #     original_lists.remove(_list)
-        #
-        # # Unsubscribe from the leftovers
-        # for _list in original_lists:
-        #     _list.update_membership([contributor], status='unsubscribed')
 
     def propagate_orcid(self):
         """
diff --git a/scipost/migrations/0067_auto_20171101_2132.py b/scipost/migrations/0067_auto_20171101_2132.py
new file mode 100644
index 000000000..a202ec2ae
--- /dev/null
+++ b/scipost/migrations/0067_auto_20171101_2132.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-11-01 20:32
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('scipost', '0066_contributor__affiliation'),
+    ]
+
+    operations = [
+        migrations.RenameField(
+            model_name='contributor',
+            old_name='affiliation',
+            new_name='old_affiliation',
+        ),
+        migrations.RenameField(
+            model_name='contributor',
+            old_name='country_of_employment',
+            new_name='old_country_of_employment',
+        ),
+        migrations.AlterField(
+            model_name='contributor',
+            name='_affiliation',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='contributors', to='affiliations.Affiliation'),
+        ),
+    ]
diff --git a/scipost/migrations/0068_auto_20171101_2132.py b/scipost/migrations/0068_auto_20171101_2132.py
new file mode 100644
index 000000000..f59223bee
--- /dev/null
+++ b/scipost/migrations/0068_auto_20171101_2132.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-11-01 20:32
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('scipost', '0067_auto_20171101_2132'),
+    ]
+
+    operations = [
+        migrations.RenameField(
+            model_name='contributor',
+            old_name='_affiliation',
+            new_name='affiliation',
+        ),
+    ]
diff --git a/scipost/models.py b/scipost/models.py
index 3b03789c2..813cb7caf 100644
--- a/scipost/models.py
+++ b/scipost/models.py
@@ -50,10 +50,10 @@ class Contributor(models.Model):
         blank=True, null=True)
     orcid_id = models.CharField(max_length=20, verbose_name="ORCID id",
                                 blank=True)
-    country_of_employment = CountryField()
-    affiliation = models.CharField(max_length=300, verbose_name='affiliation')
-    _affiliation = models.ForeignKey('affiliations.Affiliation', null=True, blank=True,
-                                     related_name='contributors')
+    old_country_of_employment = CountryField()
+    old_affiliation = models.CharField(max_length=300, verbose_name='affiliation')
+    affiliation = models.ForeignKey('affiliations.Affiliation', null=True, blank=True,
+                                    related_name='contributors')
     address = models.CharField(max_length=1000, verbose_name="address",
                                blank=True)
     personalwebpage = models.URLField(verbose_name='personal web page',
diff --git a/scipost/templates/scipost/_private_info_as_table.html b/scipost/templates/scipost/_private_info_as_table.html
index 04d525681..e8fc45a52 100644
--- a/scipost/templates/scipost/_private_info_as_table.html
+++ b/scipost/templates/scipost/_private_info_as_table.html
@@ -5,8 +5,8 @@
     <tr><td>Email: </td><td>&nbsp;</td><td>{{ contributor.user.email }}</td></tr>
     <tr><td>ORCID id: </td><td>&nbsp;</td><td>{{ contributor.orcid_id }}</td></tr>
     <tr><td>Country of employment: </td><td>&nbsp;</td>
-    <td>{{ contributor.country_of_employment.name }}</td></tr>
-    <tr><td>Affiliation: </td><td>&nbsp;</td><td>{{ contributor.affiliation }}</td></tr>
+    <td>{{ contributor.affiliation.get_country_display }}</td></tr>
+    <tr><td>Affiliation: </td><td>&nbsp;</td><td>{{ contributor.affiliation.name }}</td></tr>
     <tr><td>Address: </td><td>&nbsp;</td><td>{{ contributor.address }}</td></tr>
     <tr><td>Personal web page: </td><td>&nbsp;</td><td>{{ contributor.personalwebpage }}</td></tr>
     <tr><td>Accept SciPost emails: </td><td>&nbsp;</td><td>{{ contributor.accepts_SciPost_emails }}</td></tr>
diff --git a/scipost/templates/scipost/_public_info_as_table.html b/scipost/templates/scipost/_public_info_as_table.html
index e41461ee1..0b141e6b2 100644
--- a/scipost/templates/scipost/_public_info_as_table.html
+++ b/scipost/templates/scipost/_public_info_as_table.html
@@ -3,7 +3,7 @@
     <tr><td>First name: </td><td>&nbsp;</td><td>{{ contributor.user.first_name }}</td></tr>
     <tr><td>Last name: </td><td>&nbsp;</td><td>{{ contributor.user.last_name }}</td></tr>
     <tr><td>ORCID id: </td><td>&nbsp;</td><td>{{ contributor.orcid_id|default:'-' }}</td></tr>
-    <tr><td>Country of employment: </td><td>&nbsp;</td><td>{{ contributor.country_of_employment.name|default:'-'}}</td></tr>
-    <tr><td>Affiliation: </td><td>&nbsp;</td><td>{{ contributor.affiliation|default:'-' }}</td></tr>
+    <tr><td>Country of employment: </td><td>&nbsp;</td><td>{{ contributor.affiliation.get_country_display|default:'-'}}</td></tr>
+    <tr><td>Affiliation: </td><td>&nbsp;</td><td>{{ contributor.affiliation.name|default:'-' }}</td></tr>
     <tr><td>Personal web page: </td><td>&nbsp;</td><td>{{ contributor.personalwebpage|default:'-' }}</td></tr>
 </table>
diff --git a/submissions/templates/submissions/_report_tex_template.html b/submissions/templates/submissions/_report_tex_template.html
index ceb16ce3c..264dbc3a3 100644
--- a/submissions/templates/submissions/_report_tex_template.html
+++ b/submissions/templates/submissions/_report_tex_template.html
@@ -48,7 +48,7 @@ Report by {% if report.anonymous %}Anonymous{% else %}{{report.author.user.first
 
 \begin{center}
 %%%%%%%%%% AFFILIATIONS
-{\bf 1} {{report.author.affiliation}}\\
+{\bf 1} {{report.author.affiliation.name}}\\
 \end{center}
 {% endif %}
 
-- 
GitLab