From 674a2e4436d03f065a822fdb3a5bc333cc1e3a24 Mon Sep 17 00:00:00 2001
From: Jorran de Wit <jorrandewit@outlook.com>
Date: Sun, 19 Mar 2017 21:35:15 +0100
Subject: [PATCH] Complete reconstruction of EditorialFellow depending on
 Contributor

---
 scipost/admin.py                              | 22 ++++--
 scipost/factories.py                          | 19 +++--
 scipost/management/commands/populate_db.py    | 27 +++++--
 scipost/migrations/0045_auto_20170319_2008.py | 38 ++++++++++
 scipost/migrations/0046_auto_20170319_2032.py | 25 +++++++
 scipost/migrations/0047_auto_20170319_2110.py | 25 +++++++
 scipost/models.py                             | 75 ++++++++++++++++---
 .../templates/scipost/_college_member.html    |  4 -
 .../templates/scipost/_contributor_short.html |  4 +
 scipost/templates/scipost/about.html          |  4 +-
 scipost/test_views.py                         |  8 +-
 scipost/views.py                              |  2 +-
 12 files changed, 209 insertions(+), 44 deletions(-)
 create mode 100644 scipost/migrations/0045_auto_20170319_2008.py
 create mode 100644 scipost/migrations/0046_auto_20170319_2032.py
 create mode 100644 scipost/migrations/0047_auto_20170319_2110.py
 delete mode 100644 scipost/templates/scipost/_college_member.html
 create mode 100644 scipost/templates/scipost/_contributor_short.html

diff --git a/scipost/admin.py b/scipost/admin.py
index 047aa36ae..d9684a154 100644
--- a/scipost/admin.py
+++ b/scipost/admin.py
@@ -1,3 +1,5 @@
+import datetime
+
 from django.contrib import admin
 
 from django.contrib.auth.admin import UserAdmin
@@ -8,7 +10,7 @@ from scipost.models import Contributor, Remark,\
                            AffiliationObject,\
                            SupportingPartner, SPBMembershipAgreement, RegistrationInvitation,\
                            AuthorshipClaim, PrecookedEmail,\
-                           EditorialCollege, EditorialCollegeMember
+                           EditorialCollege, EditorialCollegeFellow
 
 
 class ContributorInline(admin.StackedInline):
@@ -87,10 +89,18 @@ class EditorialCollegeAdmin(admin.ModelAdmin):
 admin.site.register(EditorialCollege, EditorialCollegeAdmin)
 
 
-class EditorialCollegeMemberAdmin(admin.ModelAdmin):
-    list_display = ('__str__', 'college')
-    list_filter = ('college', 'subtitle')
-    search_fields = ['name', 'subtitle', 'college']
+def college_fellow_is_active(fellow):
+    '''Check if fellow is currently active.'''
+    return fellow.is_active()
+
+
+class EditorialCollegeFellowAdmin(admin.ModelAdmin):
+    list_display = ('__str__', 'college', college_fellow_is_active)
+    list_filter = ('college', 'contributor__user')
+    search_fields = ['college', 'contributor__user']
+    fields = ('contributor', 'college', 'start_date', 'until_date')
+
+    college_fellow_is_active.boolean = True
 
 
-admin.site.register(EditorialCollegeMember, EditorialCollegeMemberAdmin)
+admin.site.register(EditorialCollegeFellow, EditorialCollegeFellowAdmin)
diff --git a/scipost/factories.py b/scipost/factories.py
index 8a747b60b..41aba74fb 100644
--- a/scipost/factories.py
+++ b/scipost/factories.py
@@ -4,17 +4,22 @@ import random
 from django.contrib.auth import get_user_model
 from django.contrib.auth.models import Group
 
-from .models import Contributor, EditorialCollege, EditorialCollegeMember
+from django_countries.data import COUNTRIES
+
+from .models import Contributor, EditorialCollege, EditorialCollegeFellow, TITLE_CHOICES
 
 
 class ContributorFactory(factory.django.DjangoModelFactory):
     class Meta:
         model = Contributor
 
-    title = "MR"
+    title = random.choice(list(dict(TITLE_CHOICES).keys()))
     user = factory.SubFactory('scipost.factories.UserFactory', contributor=None)
     status = 1  # normal user
     vetted_by = factory.SubFactory('scipost.factories.ContributorFactory', vetted_by=None)
+    personalwebpage = factory.Faker('url')
+    country_of_employment = factory.Iterator(list(COUNTRIES))
+    affiliation = factory.Faker('company')
 
 
 class VettingEditorFactory(ContributorFactory):
@@ -59,12 +64,10 @@ class EditorialCollegeFactory(factory.django.DjangoModelFactory):
     discipline = random.choice(['Physics', 'Chemistry', 'Medicine'])
 
 
-class EditorialCollegeMemberFactory(factory.django.DjangoModelFactory):
+class EditorialCollegeFellowFactory(factory.django.DjangoModelFactory):
     class Meta:
-        model = EditorialCollegeMember
+        model = EditorialCollegeFellow
 
-    title = factory.Faker('prefix')
-    name = factory.Faker('name')
-    link = factory.Faker('url')
-    subtitle = factory.Faker('company')
     college = factory.Iterator(EditorialCollege.objects.all())
+    contributor = factory.Iterator(Contributor.objects.exclude(
+                                   user__username='deleted').order_by('?'))
diff --git a/scipost/management/commands/populate_db.py b/scipost/management/commands/populate_db.py
index e1296b85e..e99f98b54 100644
--- a/scipost/management/commands/populate_db.py
+++ b/scipost/management/commands/populate_db.py
@@ -2,7 +2,7 @@ from django.core.management.base import BaseCommand
 
 from news.factories import NewsItemFactory
 
-from ...factories import EditorialCollegeFactory, EditorialCollegeMemberFactory
+from ...factories import ContributorFactory, EditorialCollegeFactory, EditorialCollegeFellowFactory
 
 
 class Command(BaseCommand):
@@ -15,11 +15,18 @@ class Command(BaseCommand):
             help='Add NewsItems',
         )
         parser.add_argument(
-            '--editorial-college',
+            '--contributor',
+            action='store_true',
+            dest='contributor',
+            default=False,
+            help='Add Contributors',
+        )
+        parser.add_argument(
+            '--college',
             action='store_true',
             dest='editorial-college',
             default=False,
-            help='Add Editorial College and members',
+            help='Add Editorial College and Fellows (Contributors required)',
         )
         parser.add_argument(
             '--all',
@@ -30,19 +37,25 @@ class Command(BaseCommand):
         )
 
     def handle(self, *args, **kwargs):
+        if kwargs['contributor'] or kwargs['all']:
+            self.create_contributors()
         if kwargs['editorial-college'] or kwargs['all']:
             self.create_editorial_college()
-            self.create_editorial_college_members()
+            self.create_editorial_college_fellows()
         if kwargs['news'] or kwargs['all']:
             self.create_news_items()
 
+    def create_contributors(self):
+        ContributorFactory.create_batch(5)
+        self.stdout.write(self.style.SUCCESS('Successfully created Contributors.'))
+
     def create_editorial_college(self):
         EditorialCollegeFactory.create_batch(5)
         self.stdout.write(self.style.SUCCESS('Successfully created Editorial College\'s.'))
 
-    def create_editorial_college_members(self):
-        EditorialCollegeMemberFactory.create_batch(20)
-        self.stdout.write(self.style.SUCCESS('Successfully created Editorial College Members.'))
+    def create_editorial_college_fellows(self):
+        EditorialCollegeFellowFactory.create_batch(5)
+        self.stdout.write(self.style.SUCCESS('Successfully created Editorial College Fellows.'))
 
     def create_news_items(self):
         NewsItemFactory.create_batch(5)
diff --git a/scipost/migrations/0045_auto_20170319_2008.py b/scipost/migrations/0045_auto_20170319_2008.py
new file mode 100644
index 000000000..0f3ef75bf
--- /dev/null
+++ b/scipost/migrations/0045_auto_20170319_2008.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.3 on 2017-03-19 19:08
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+import django.utils.timezone
+import scipost.db.fields
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('scipost', '0044_auto_20170319_0940'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='EditorialCollegeFellow',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('created', models.DateTimeField(default=django.utils.timezone.now)),
+                ('latest_activity', scipost.db.fields.AutoDateTimeField(blank=True, default=django.utils.timezone.now, editable=False)),
+                ('college', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='fellows', to='scipost.EditorialCollege')),
+                ('contributor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='scipost.Contributor')),
+            ],
+            options={
+                'abstract': False,
+            },
+        ),
+        migrations.RemoveField(
+            model_name='editorialcollegemember',
+            name='college',
+        ),
+        migrations.DeleteModel(
+            name='EditorialCollegeMember',
+        ),
+    ]
diff --git a/scipost/migrations/0046_auto_20170319_2032.py b/scipost/migrations/0046_auto_20170319_2032.py
new file mode 100644
index 000000000..2e15e2bf9
--- /dev/null
+++ b/scipost/migrations/0046_auto_20170319_2032.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.3 on 2017-03-19 19:32
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('scipost', '0045_auto_20170319_2008'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='editorialcollegefellow',
+            name='start_date',
+            field=models.DateField(blank=True, null=True),
+        ),
+        migrations.AddField(
+            model_name='editorialcollegefellow',
+            name='until_date',
+            field=models.DateField(blank=True, null=True),
+        ),
+    ]
diff --git a/scipost/migrations/0047_auto_20170319_2110.py b/scipost/migrations/0047_auto_20170319_2110.py
new file mode 100644
index 000000000..6f24286a6
--- /dev/null
+++ b/scipost/migrations/0047_auto_20170319_2110.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.3 on 2017-03-19 20:10
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import scipost.models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('scipost', '0046_auto_20170319_2032'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='contributor',
+            name='vetted_by',
+            field=models.ForeignKey(blank=True, null=True, on_delete=models.SET(scipost.models.get_sentinel_user), related_name='contrib_vetted_by', to='scipost.Contributor'),
+        ),
+        migrations.AlterUniqueTogether(
+            name='editorialcollegefellow',
+            unique_together=set([('contributor', 'college', 'start_date', 'until_date')]),
+        ),
+    ]
diff --git a/scipost/models.py b/scipost/models.py
index fcec39e5f..668f53644 100644
--- a/scipost/models.py
+++ b/scipost/models.py
@@ -4,6 +4,7 @@ from django import forms
 from django.contrib.auth.models import User
 from django.contrib.postgres.fields import ArrayField
 from django.db import models
+from django.db.models import Q
 from django.template import Template, Context
 from django.utils import timezone
 from django.utils.safestring import mark_safe
@@ -72,6 +73,12 @@ class TimeStampedModel(models.Model):
         abstract = True
 
 
+def get_sentinel_user():
+    '''Deceased people talk after their death.'''
+    user, new = User.objects.get_or_create(username='deleted')
+    return Contributor.objects.get_or_create(status=-4, user=user)[0]
+
+
 class Contributor(models.Model):
     """
     All users of SciPost are Contributors.
@@ -98,7 +105,7 @@ class Contributor(models.Model):
                                default='', blank=True)
     personalwebpage = models.URLField(verbose_name='personal web page',
                                       blank=True)
-    vetted_by = models.ForeignKey('self', on_delete=models.CASCADE,
+    vetted_by = models.ForeignKey('self', on_delete=models.SET(get_sentinel_user),
                                   related_name="contrib_vetted_by",
                                   blank=True, null=True)
     accepts_SciPost_emails = models.BooleanField(
@@ -517,6 +524,17 @@ class SPBMembershipAgreement(models.Model):
 # Static info models #
 ######################
 
+class FellowManager(models.Manager):
+    def current_fellows(self, *args, **kwargs):
+        today = datetime.date.today()
+        return self.filter(
+            Q(start_date__lte=today, until_date__isnull=True) |
+            Q(start_date__isnull=True, until_date__gte=today) |
+            Q(start_date__lte=today, until_date__gte=today) |
+            Q(start_date__isnull=True, until_date__isnull=True),
+            **kwargs)
+
+
 class EditorialCollege(models.Model):
     '''A SciPost Editorial College for a specific discipline.'''
     discipline = models.CharField(max_length=255, unique=True)
@@ -524,17 +542,50 @@ class EditorialCollege(models.Model):
     def __str__(self):
         return self.discipline
 
+    @property
+    def active_fellows(self):
+        return self.fellows.current_fellows()
 
-class EditorialCollegeMember(models.Model):
-    """
-    Editorial College Members for non-functional use!
-    This model is used for static information purposes only.
-    """
-    name = models.CharField(max_length=255)
-    title = models.CharField(max_length=10, blank=True)
-    link = models.URLField(blank=True)
-    subtitle = models.CharField(max_length=255, blank=True)
-    college = models.ForeignKey('scipost.EditorialCollege', related_name='member')
+
+class EditorialCollegeFellow(TimeStampedModel):
+    '''Editorial College Fellow connecting Editorial College and Contributors.'''
+    contributor = models.ForeignKey('scipost.Contributor', on_delete=models.CASCADE,
+                                    related_name='+')
+    college = models.ForeignKey('scipost.EditorialCollege', on_delete=models.CASCADE,
+                                related_name='fellows')
+    start_date = models.DateField(null=True, blank=True)
+    until_date = models.DateField(null=True, blank=True)
+
+    objects = FellowManager()
+
+    class Meta:
+        unique_together = ('contributor', 'college', 'start_date', 'until_date')
 
     def __str__(self):
-        return ('%s %s' % (self.title, self.name)).strip()
+        return self.contributor.__str__()
+
+    def is_active(self):
+        today = datetime.date.today()
+        if not self.start_date:
+            if not self.until_date:
+                return True
+            elif today <= self.until_date:
+                return True
+            return False
+        elif not self.until_date:
+            if today >= self.start_date:
+                return True
+        elif today >= self.start_date and today <= self.until_date:
+            return True
+        return False
+        #
+        #
+        # if not self.start_date and not self.until_date:
+        #     return True
+        # elif not self.start_date and today <= self.until_date:
+        #     return True
+        # elif today >= self.start_date and not self.until_date:
+        #     return True
+        # elif today >= self.start_date and today <= self.until_date:
+        #     return True
+        # return False
diff --git a/scipost/templates/scipost/_college_member.html b/scipost/templates/scipost/_college_member.html
deleted file mode 100644
index a6114acac..000000000
--- a/scipost/templates/scipost/_college_member.html
+++ /dev/null
@@ -1,4 +0,0 @@
-{% if member.link %}<a target="_blank" href="{{ member.link }}">{% endif %}{{ member }}{% if member.link %}</a>{% endif %}
-{% if member.subtitle %}
-<br/>({{member.subtitle}})
-{% endif %}
diff --git a/scipost/templates/scipost/_contributor_short.html b/scipost/templates/scipost/_contributor_short.html
new file mode 100644
index 000000000..52c7fb8bb
--- /dev/null
+++ b/scipost/templates/scipost/_contributor_short.html
@@ -0,0 +1,4 @@
+{% if contributor.personalwebpage %}<a target="_blank" href="{{ contributor.personalwebpage }}">{% endif %}{{ contributor.get_title_display }} {{ contributor.user.first_name }} {{ contributor.user.last_name }}{% if contributor.personalwebpage %}</a>{% endif %}
+{% if contributor.affiliation %}
+<br/>({{contributor.affiliation}})
+{% endif %}
diff --git a/scipost/templates/scipost/about.html b/scipost/templates/scipost/about.html
index 725dc4092..36028f137 100644
--- a/scipost/templates/scipost/about.html
+++ b/scipost/templates/scipost/about.html
@@ -155,9 +155,9 @@
 </div>
 
 <div class="row">
-    {% for member in college.member.all %}
+    {% for fellow in college.active_fellows %}
         <div class="col-md-4 py-1">
-            {% include 'scipost/_college_member.html' with member=member %}
+            {% include 'scipost/_contributor_short.html' with contributor=fellow.contributor %}
         </div>
     {% endfor %}
 </div>
diff --git a/scipost/test_views.py b/scipost/test_views.py
index d6b766630..5b764927f 100644
--- a/scipost/test_views.py
+++ b/scipost/test_views.py
@@ -8,8 +8,8 @@ from commentaries.forms import CommentarySearchForm
 from commentaries.models import Commentary
 
 from .factories import ContributorFactory,\
-                       EditorialCollegeMemberFactory, EditorialCollegeFactory
-from .models import EditorialCollege, EditorialCollegeMember
+                       EditorialCollegeFellowFactory, EditorialCollegeFactory
+from .models import EditorialCollege, EditorialCollegeFellow
 
 
 class RequestCommentaryTest(TestCase):
@@ -137,7 +137,7 @@ class AboutViewTest(TestCase):
 
         # Create College with 10 members
         self.college = EditorialCollegeFactory()
-        EditorialCollegeMemberFactory.create_batch(10)
+        EditorialCollegeFellowFactory.create_batch(10)
 
     def test_status_code_200_including_members(self):
         response = self.client.get(self.target)
@@ -151,4 +151,4 @@ class AboutViewTest(TestCase):
         # Members exist in college
         self.assertTrue(college.member.count() >= 10)
         last_member = college.member.last()
-        self.assertTrue(isinstance(last_member, EditorialCollegeMember))
+        self.assertTrue(isinstance(last_member, EditorialCollegeFellow))
diff --git a/scipost/views.py b/scipost/views.py
index aad9aaf68..c491d98e5 100644
--- a/scipost/views.py
+++ b/scipost/views.py
@@ -1756,4 +1756,4 @@ def SPB_membership_request(request):
 class AboutView(ListView):
     model = EditorialCollege
     template_name = 'scipost/about.html'
-    queryset = EditorialCollege.objects.prefetch_related('member')
+    queryset = EditorialCollege.objects.prefetch_related('fellows__contributor')
-- 
GitLab