From 979b16d8de85166bb61d7cb8478989ab9c2294dd Mon Sep 17 00:00:00 2001
From: Jorran de Wit <jorrandewit@outlook.com>
Date: Mon, 12 Feb 2018 21:55:24 +0100
Subject: [PATCH] Publication authors ordered registered or not

---
 general.py                                    | 110 --------------
 journals/admin.py                             |  11 +-
 journals/forms.py                             |  10 +-
 .../migrations/0009_auto_20180212_1947.py     |  25 ++++
 .../migrations/0010_auto_20180212_1947.py     |  25 ++++
 .../migrations/0011_auto_20180212_1950.py     |  40 +++++
 .../migrations/0012_auto_20180212_1950.py     |  44 ++++++
 journals/models.py                            |  56 ++++++-
 journals/templates/journals/add_author.html   |  26 ++--
 .../templates/journals/manage_metadata.html   |  15 +-
 .../journals/publication_detail.html          |  54 +++----
 journals/urls/general.py                      |  11 +-
 journals/views.py                             | 141 ++++++++----------
 scipost/forms.py                              |   2 +-
 scipost/migrations/0004_auto_20180212_1932.py |  22 +++
 scipost/models.py                             |   3 +-
 scipost/views.py                              |  43 +++---
 17 files changed, 341 insertions(+), 297 deletions(-)
 delete mode 100644 general.py
 create mode 100644 journals/migrations/0009_auto_20180212_1947.py
 create mode 100644 journals/migrations/0010_auto_20180212_1947.py
 create mode 100644 journals/migrations/0011_auto_20180212_1950.py
 create mode 100644 journals/migrations/0012_auto_20180212_1950.py
 create mode 100644 scipost/migrations/0004_auto_20180212_1932.py

diff --git a/general.py b/general.py
deleted file mode 100644
index 5570b6627..000000000
--- a/general.py
+++ /dev/null
@@ -1,110 +0,0 @@
-from django.conf.urls import url
-from django.urls import reverse_lazy
-from django.views.generic import TemplateView, RedirectView
-
-from journals import views as journals_views
-
-urlpatterns = [
-    # Journals
-    url(r'^$', journals_views.journals, name='journals'),
-    url(r'scipost_physics', RedirectView.as_view(url=reverse_lazy('scipost:landing_page',
-                                                 args=['SciPostPhys']))),
-    url(r'^journals_terms_and_conditions$',
-        TemplateView.as_view(template_name='journals/journals_terms_and_conditions.html'),
-        name='journals_terms_and_conditions'),
-    url(r'^crossmark_policy$',
-        TemplateView.as_view(template_name='journals/crossmark_policy.html'),
-        name='crossmark_policy'),
-
-    # Editorial and Administrative Workflow
-    url(r'^initiate_publication$',
-        journals_views.initiate_publication,
-        name='initiate_publication'),
-    url(r'^validate_publication$',
-        journals_views.validate_publication,
-        name='validate_publication'),
-    url(r'^mark_first_author/(?P<publication_id>[0-9]+)/(?P<contributor_id>[0-9]+)$',
-        journals_views.mark_first_author,
-        name='mark_first_author'),
-    url(r'^mark_first_author_unregistered/(?P<publication_id>[0-9]+)/(?P<unregistered_author_id>[0-9]+)$',
-        journals_views.mark_first_author_unregistered,
-        name='mark_first_author_unregistered'),
-    url(r'^add_author/(?P<publication_id>[0-9]+)/(?P<contributor_id>[0-9]+)$',
-        journals_views.add_author,
-        name='add_author'),
-    url(r'^add_author/(?P<publication_id>[0-9]+)$',
-        journals_views.add_author,
-        name='add_author'),
-    url(r'^add_unregistered_author/(?P<publication_id>[0-9]+)/(?P<unregistered_author_id>[0-9]+)$',
-        journals_views.add_unregistered_author,
-        name='add_unregistered_author'),
-    url(r'^add_new_unreg_author/(?P<publication_id>[0-9]+)$',
-        journals_views.add_new_unreg_author,
-        name='add_new_unreg_author'),
-    url(r'^manage_metadata/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})$',
-        journals_views.manage_metadata,
-        name='manage_metadata'),
-    url(r'^manage_metadata/(?P<issue_doi_label>[a-zA-Z]+.[0-9]+.[0-9]+)$',
-        journals_views.manage_metadata,
-        name='manage_metadata'),
-    url(r'^manage_metadata/$',
-        journals_views.manage_metadata,
-        name='manage_metadata'),
-    url(r'^create_citation_list_metadata/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})$',
-        journals_views.create_citation_list_metadata,
-        name='create_citation_list_metadata'),
-    url(r'^create_funding_info_metadata/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})$',
-        journals_views.create_funding_info_metadata,
-        name='create_funding_info_metadata'),
-    url(r'^add_associated_grant/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})$',
-        journals_views.add_associated_grant,
-        name='add_associated_grant'),
-    url(r'^add_generic_funder/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})$',
-        journals_views.add_generic_funder,
-        name='add_generic_funder'),
-    url(r'^create_metadata_xml/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})$',
-        journals_views.create_metadata_xml,
-        name='create_metadata_xml'),
-    url(r'^metadata_xml_deposit/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})/(?P<option>[a-z]+)$',
-        journals_views.metadata_xml_deposit,
-        name='metadata_xml_deposit'),
-    url(r'^mark_deposit_success/(?P<deposit_id>[0-9]+)/(?P<success>[0-1])$',
-        journals_views.mark_deposit_success,
-        name='mark_deposit_success'),
-    url(r'^produce_metadata_DOAJ/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})$',
-        journals_views.produce_metadata_DOAJ,
-        name='produce_metadata_DOAJ'),
-    url(r'^metadata_DOAJ_deposit/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})$',
-        journals_views.metadata_DOAJ_deposit,
-        name='metadata_DOAJ_deposit'),
-    url(r'^mark_doaj_deposit_success/(?P<deposit_id>[0-9]+)/(?P<success>[0-1])$',
-        journals_views.mark_doaj_deposit_success,
-        name='mark_doaj_deposit_success'),
-    url(r'^harvest_citedby_list/$',
-        journals_views.harvest_citedby_list,
-        name='harvest_citedby_list'),
-    url(r'^harvest_citedby_links/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})$',
-        journals_views.harvest_citedby_links,
-        name='harvest_citedby_links'),
-    url(r'^sign_existing_report/(?P<report_id>[0-9]+)$',
-        journals_views.sign_existing_report,
-        name='sign_existing_report'),
-    url(r'^manage_report_metadata/$',
-        journals_views.manage_report_metadata,
-        name='manage_report_metadata'),
-    url(r'^manage_comment_metadata/$',
-        journals_views.manage_comment_metadata,
-        name='manage_comment_metadata'),
-    url(r'^mark_report_doi_needed/(?P<report_id>[0-9]+)/(?P<needed>[0-1])$',
-        journals_views.mark_report_doi_needed,
-        name='mark_report_doi_needed'),
-    url(r'^mark_comment_doi_needed/(?P<comment_id>[0-9]+)/(?P<needed>[0-1])$',
-        journals_views.mark_comment_doi_needed,
-        name='mark_comment_doi_needed'),
-    url(r'^generic_metadata_xml_deposit/(?P<type_of_object>[a-z]+)/(?P<object_id>[0-9]+)$',
-        journals_views.generic_metadata_xml_deposit,
-        name='generic_metadata_xml_deposit'),
-    url(r'^mark_generic_deposit_success/(?P<deposit_id>[0-9]+)/(?P<success>[0-1])$',
-        journals_views.mark_generic_deposit_success,
-        name='mark_generic_deposit_success'),
-]
diff --git a/journals/admin.py b/journals/admin.py
index 5aff8e167..18468831a 100644
--- a/journals/admin.py
+++ b/journals/admin.py
@@ -2,7 +2,7 @@ from django.contrib import admin, messages
 from django import forms
 
 from journals.models import UnregisteredAuthor, Journal, Volume, Issue, Publication, \
-    Deposit, DOAJDeposit, GenericDOIDeposit, Reference
+    Deposit, DOAJDeposit, GenericDOIDeposit, Reference, PublicationAuthorsTable
 
 from scipost.models import Contributor
 from submissions.models import Submission
@@ -59,16 +59,21 @@ class ReferenceInline(admin.TabularInline):
     model = Reference
 
 
+class AuthorsInline(admin.TabularInline):
+    model = PublicationAuthorsTable
+    extra = 0
+
+
 class PublicationAdmin(admin.ModelAdmin):
     search_fields = ['title', 'author_list']
     list_display = ['title', 'author_list', 'in_issue', 'doi_string', 'publication_date']
     date_hierarchy = 'publication_date'
     list_filter = ['in_issue']
-    inlines = [ReferenceInline]
+    inlines = [AuthorsInline, ReferenceInline]
     form = PublicationAdminForm
 
-admin.site.register(Publication, PublicationAdmin)
 
+admin.site.register(Publication, PublicationAdmin)
 
 
 class DepositAdmin(admin.ModelAdmin):
diff --git a/journals/forms.py b/journals/forms.py
index 232e62179..44462b752 100644
--- a/journals/forms.py
+++ b/journals/forms.py
@@ -6,9 +6,8 @@ from django import forms
 from django.forms import BaseModelFormSet, modelformset_factory
 from django.utils import timezone
 
-from .models import Issue, Publication, Reference
+from .models import Issue, Publication, Reference, UnregisteredAuthor
 
-from scipost.models import Contributor
 from scipost.services import DOICaller
 from submissions.models import Submission
 
@@ -31,12 +30,9 @@ class ValidatePublicationForm(forms.ModelForm):
 
 
 class UnregisteredAuthorForm(forms.ModelForm):
-    first_name = forms.CharField()
-    last_name = forms.CharField()
-
     class Meta:
-        model = Contributor
-        fields = ()
+        model = UnregisteredAuthor
+        fields = ('first_name', 'last_name')
 
 
 class CitationListBibitemsForm(forms.Form):
diff --git a/journals/migrations/0009_auto_20180212_1947.py b/journals/migrations/0009_auto_20180212_1947.py
new file mode 100644
index 000000000..3b45ebe54
--- /dev/null
+++ b/journals/migrations/0009_auto_20180212_1947.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-02-12 18:47
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('journals', '0008_auto_20180203_1229'),
+    ]
+
+    operations = [
+        migrations.RenameField(
+            model_name='publication',
+            old_name='authors',
+            new_name='authors_old',
+        ),
+        migrations.RenameField(
+            model_name='publication',
+            old_name='authors_unregistered',
+            new_name='authors_unregistered_old',
+        ),
+    ]
diff --git a/journals/migrations/0010_auto_20180212_1947.py b/journals/migrations/0010_auto_20180212_1947.py
new file mode 100644
index 000000000..6b60b98e1
--- /dev/null
+++ b/journals/migrations/0010_auto_20180212_1947.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-02-12 18:47
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('journals', '0009_auto_20180212_1947'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='publication',
+            name='authors_old',
+            field=models.ManyToManyField(blank=True, related_name='publications_old', to='scipost.Contributor'),
+        ),
+        migrations.AlterField(
+            model_name='publication',
+            name='authors_unregistered_old',
+            field=models.ManyToManyField(blank=True, related_name='publications_old', to='journals.UnregisteredAuthor'),
+        ),
+    ]
diff --git a/journals/migrations/0011_auto_20180212_1950.py b/journals/migrations/0011_auto_20180212_1950.py
new file mode 100644
index 000000000..a6003ecac
--- /dev/null
+++ b/journals/migrations/0011_auto_20180212_1950.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-02-12 18:50
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('scipost', '0004_auto_20180212_1932'),
+        ('journals', '0010_auto_20180212_1947'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='PublicationAuthorsTable',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('order', models.PositiveSmallIntegerField()),
+                ('contributor', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='scipost.Contributor')),
+                ('publication', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='authors', to='journals.Publication')),
+                ('unregistered_author', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='journals.UnregisteredAuthor')),
+            ],
+            options={
+                'ordering': ('order',),
+            },
+        ),
+        migrations.AddField(
+            model_name='publication',
+            name='authors_registered',
+            field=models.ManyToManyField(blank=True, related_name='publications', through='journals.PublicationAuthorsTable', to='scipost.Contributor'),
+        ),
+        migrations.AddField(
+            model_name='publication',
+            name='authors_unregistered',
+            field=models.ManyToManyField(blank=True, related_name='publications', through='journals.PublicationAuthorsTable', to='journals.UnregisteredAuthor'),
+        ),
+    ]
diff --git a/journals/migrations/0012_auto_20180212_1950.py b/journals/migrations/0012_auto_20180212_1950.py
new file mode 100644
index 000000000..69128e76f
--- /dev/null
+++ b/journals/migrations/0012_auto_20180212_1950.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-02-12 18:50
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+def transfer_publication_authors_to_separate_table(apps, schema_editor):
+    Contributor = apps.get_model('scipost', 'Contributor')
+    Publication = apps.get_model('journals', 'Publication')
+    UnregisteredAuthor = apps.get_model('journals', 'UnregisteredAuthor')
+    PublicationAuthorsTable = apps.get_model('journals', 'PublicationAuthorsTable')
+
+    for publication in Publication.objects.all():
+        registered_authors = Contributor.objects.filter(publications_old__id=publication.id)
+        unregistered_authors = UnregisteredAuthor.objects.filter(publications_old__id=publication.id)
+
+        count = 1
+        for author in registered_authors:
+            PublicationAuthorsTable.objects.create(
+                publication=publication,
+                contributor=author,
+                order=count,
+            )
+            count += 1
+
+        for author in unregistered_authors:
+            PublicationAuthorsTable.objects.create(
+                publication=publication,
+                unregistered_author=author,
+                order=count,
+            )
+            count += 1
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('journals', '0011_auto_20180212_1950'),
+    ]
+
+    operations = [
+        migrations.RunPython(transfer_publication_authors_to_separate_table)
+    ]
diff --git a/journals/models.py b/journals/models.py
index c583a4c52..41cd2052c 100644
--- a/journals/models.py
+++ b/journals/models.py
@@ -30,6 +30,46 @@ class UnregisteredAuthor(models.Model):
         return self.last_name + ', ' + self.first_name
 
 
+class PublicationAuthorsTable(models.Model):
+    publication = models.ForeignKey('journals.Publication', related_name='authors')
+    unregistered_author = models.ForeignKey('journals.UnregisteredAuthor', null=True, blank=True,
+                                            related_name='+')
+    contributor = models.ForeignKey('scipost.Contributor', null=True, blank=True, related_name='+')
+    order = models.PositiveSmallIntegerField()
+
+    class Meta:
+        ordering = ('order',)
+
+    def __str__(self):
+        if self.contributor:
+            return str(self.contributor)
+        elif self.unregistered_author:
+            return str(self.unregistered_author)
+
+    def save(self, *args, **kwargs):
+        if not self.order:
+            self.order = self.publication.authors.count() + 1
+        return super().save(*args, **kwargs)
+
+    @property
+    def is_registered(self):
+        return self.contributor is not None
+
+    @property
+    def first_name(self):
+        if self.contributor:
+            return self.contributor.user.first_name
+        if self.unregistered_author:
+            return self.unregistered_author.first_name
+
+    @property
+    def last_name(self):
+        if self.contributor:
+            return self.contributor.user.last_name
+        if self.unregistered_author:
+            return self.unregistered_author.last_name
+
+
 class Journal(models.Model):
     name = models.CharField(max_length=100, choices=SCIPOST_JOURNALS, unique=True)
     doi_label = models.CharField(max_length=200, unique=True, db_index=True,
@@ -240,9 +280,15 @@ class Publication(models.Model):
         models.CharField(max_length=10, choices=SCIPOST_SUBJECT_AREAS), blank=True, null=True)
 
     # Authors
-    authors = models.ManyToManyField('scipost.Contributor', blank=True,
-                                     related_name='publications')
+    authors_registered = models.ManyToManyField('scipost.Contributor', blank=True,
+                                                through='PublicationAuthorsTable',
+                                                through_fields=('publication', 'contributor'),
+                                                related_name='publications')
     authors_unregistered = models.ManyToManyField('journals.UnregisteredAuthor', blank=True,
+                                                  through='PublicationAuthorsTable',
+                                                  through_fields=(
+                                                    'publication',
+                                                    'unregistered_author'),
                                                   related_name='publications')
     first_author = models.ForeignKey('scipost.Contributor', blank=True, null=True,
                                      on_delete=models.CASCADE,
@@ -282,6 +328,12 @@ class Publication(models.Model):
     latest_metadata_update = models.DateTimeField(blank=True, null=True)
     latest_activity = models.DateTimeField(default=timezone.now)
 
+    # Deprecated fields. About to be removed after successful database migration on production.
+    authors_old = models.ManyToManyField('scipost.Contributor', blank=True,
+                                         related_name='publications_old')
+    authors_unregistered_old = models.ManyToManyField('journals.UnregisteredAuthor', blank=True,
+                                                      related_name='publications_old')
+
     objects = PublicationQuerySet.as_manager()
 
     def __str__(self):
diff --git a/journals/templates/journals/add_author.html b/journals/templates/journals/add_author.html
index ce194a4f4..b49a3a040 100644
--- a/journals/templates/journals/add_author.html
+++ b/journals/templates/journals/add_author.html
@@ -29,18 +29,16 @@
 
 <div class="row">
     <div class="col-12">
-      <h3>Current list of authors as contributors:</h3>
+      <h3>Current list of authors</h3>
       <ul>
         {% for author in publication.authors.all %}
-            <li><a href="{% url 'scipost:contributor_info' author.id %}">{{ author.user.first_name }} {{ author.user.last_name }}</a></li>
-        {% empty %}
-            <li>No unregistered authors known.</li>
-        {% endfor %}
-      </ul>
-      <h3>Current list of additional authors (unregistered):</h3>
-      <ul>
-        {% for author in publication.authors_unregistered.all %}
-            <li>{{ author }}</li>
+            <li>
+                {% if author.is_registered %}
+                    <a href="{{ author.contributor.get_absolute_url }}">{{ author.contributor }}</a>
+                {% else %}
+                    {{ author.unregistered_author }}
+                {% endif %}
+            </li>
         {% empty %}
             <li>No unregistered authors known.</li>
         {% endfor %}
@@ -57,7 +55,7 @@
 
       {% if form.has_changed %}
           <br>
-            <h3>Identified as contributor:</h3>
+            <h3>Identified as Contributor:</h3>
             <ul>
               {% for contributor in contributors_found %}
                   <li>
@@ -70,17 +68,17 @@
             </ul>
 
             <h3>You can otherwise add the author manually and link it to the publication</h3>
-            <form action="{% url 'journals:add_new_unreg_author' publication_id=publication.id %}" method="post">
+            <form action="{% url 'journals:add_author' publication_id=publication.id %}" method="post">
                 {% csrf_token %}
                 {{ form|bootstrap }}
                 <input class="btn btn-primary" type="submit" value="Add">
             </form>
         <br>
       {% endif %}
-
-      <p>Return to the <a href="{{publication.get_absolute_url}}">publication's page</a> or to the <a href="{% url 'journals:manage_metadata' %}">metadata management page</a>.</p>
     </div>
 </div>
 
+<p>Return to the <a href="{{publication.get_absolute_url}}">publication's page</a> or to the <a href="{% url 'journals:manage_metadata' %}">metadata management page</a>.</p>
+
 
 {% endblock content %}
diff --git a/journals/templates/journals/manage_metadata.html b/journals/templates/journals/manage_metadata.html
index 56f960111..0b0547b47 100644
--- a/journals/templates/journals/manage_metadata.html
+++ b/journals/templates/journals/manage_metadata.html
@@ -74,20 +74,11 @@ event: "focusin"
 	<div class="row">
           <div class="col-md-5">
             <ul>
-              <li>Mark the first author (currently: {% if publication.first_author %}{{ publication.first_author }} {% elif publication.first_author_unregistered %}{{ publication.first_author_unregistered }} (unregistered){% endif %})
-                <p>registered authors:</p>
-                <ul>
+              <li>Mark the first author (currently: {% if publication.first_author %}{{ publication.first_author }}{% elif publication.first_author_unregistered %}{{ publication.first_author_unregistered }} (unregistered){% endif %})
+                <ul class="list-unstyled pl-4">
                   {% for author in publication.authors.all %}
                   <li>
-                    <a href="{% url 'journals:mark_first_author' publication_id=publication.id contributor_id=author.id %}">{{ author }}</a>
-                  </li>
-                  {% endfor %}
-                </ul>
-                <p>unregistered authors:</p>
-                <ul>
-                  {% for author_unreg in publication.authors_unregistered.all %}
-                  <li>
-                    <a href="{% url 'journals:mark_first_author_unregistered' publication_id=publication.id unregistered_author_id=author_unreg.id %}">{{ author_unreg }}</a>
+                    {{ author.order }}. <a href="{% url 'journals:mark_first_author' publication_id=publication.id author_object_id=author.id %}">{{ author }}</a>
                   </li>
                   {% endfor %}
                 </ul>
diff --git a/journals/templates/journals/publication_detail.html b/journals/templates/journals/publication_detail.html
index 56ed342a7..358a8bbca 100644
--- a/journals/templates/journals/publication_detail.html
+++ b/journals/templates/journals/publication_detail.html
@@ -20,10 +20,11 @@
 
     <meta name="citation_title" content="{{ publication.title }}"/>
     {% for author in publication.authors.all %}
-    <meta name="citation_author" content="{{ author.user.last_name }}, {{ author.user.first_name }}"/>
-    {% endfor %}
-    {% for author in publication.authors_unregistered.all %}
-    <meta name="citation_author" content="{{ author.last_name }}, {{ author.first_name }}"/>
+        {% if author.contributor %}
+            <meta name="citation_author" content="{{ author.contributor.user.last_name }}, {{ author.contributor.user.first_name }}"/>
+        {% elif author.unregistered_author %}
+            <meta name="citation_author" content="{{ author.unregistered_author.last_name }}, {{ author.unregistered_author.first_name }}"/>
+        {% endif %}
     {% endfor %}
     <meta name="citation_doi" content="{{ publication.doi_string }}"/>
     <meta name="citation_publication_date" content="{{ publication.publication_date|date:'Y/m/d' }}"/>
@@ -83,10 +84,11 @@
             <h3>Authors</h3>
             <ul>
                 {% for author in publication.authors.all %}
-                      <li><a href="{{author.get_absolute_url}}">{{ author }}</a></li>
-                {% endfor %}
-                {% for author in publication.authors_unregistered.all %}
-                      <li>{{ author }}</li>
+                    {% if author.is_registered %}
+                        <li><a href="{{ author.contributor.get_absolute_url }}">{{ author.contributor }}</a></li>
+                    {% else %}
+                        <li>{{ author.unregistered_author }}</li>
+                    {% endif %}
                 {% endfor %}
             </ul>
 
@@ -96,7 +98,7 @@
                 {# This function is not available for public yet! #}
                 <em>The following is not available for the public yet:</em>
                 {% include 'partials/journals/references.html' with publication=publication %}
-                
+
                 {% if publication.funders_generic.exists %}
                     <h3>Funder{{ publication.funders_generic.count|pluralize }} for this publication:</h3>
                     <ul>
@@ -118,7 +120,7 @@
         </div>
     </div>
 
-    {% if request.user and request.user.contributor in publication.authors.all %}
+    {% if request.user and request.user.contributor in publication.registered_authors.all %}
         <h3>Author actions</h3>
         <ul>
             <li><a href="{% url 'commentaries:comment_on_publication' publication.doi_label %}">Place a comment on this publication</a></li>
@@ -131,29 +133,15 @@
         <div class="col-12">
             <h3>Editorial Administration tools: </h3>
             <ul>
-              <li>Mark the first author (currently: {% if publication.first_author %}{{ publication.first_author }} {% elif publication.first_author_unregistered %}{{ publication.first_author_unregistered }} (unregistered){% endif %})
-                <div class="row">
-                    <div class="col-md-5">
-                      <p>registered authors:</p>
-                      <ul>
-                        {% for author in publication.authors.all %}
-                          <li>
-                            <a href="{% url 'journals:mark_first_author' publication_id=publication.id contributor_id=author.id %}">{{ author }}</a>
-                          </li>
-                        {% endfor %}
-                      </ul>
-                    </div>
-                    <div class="col-md-5">
-                      <p>unregistered authors:</p>
-                      <ul>
-                        {% for author_unreg in publication.authors_unregistered.all %}
-                          <li>
-                            <a href="{% url 'journals:mark_first_author_unregistered' publication_id=publication.id unregistered_author_id=author_unreg.id %}">{{ author_unreg }}</a>
-                          </li>
-                        {% endfor %}
-                      </ul>
-                    </div>
-                </div>
+              <li>
+                  Mark the first author (currently: {% if publication.first_author %}{{ publication.first_author }}{% elif publication.first_author_unregistered %}{{ publication.first_author_unregistered }} (unregistered){% endif %})
+                  <ul class="list-unstyled pl-4">
+                    {% for author in publication.authors.all %}
+                      <li>
+                        {{ author.order }}. <a href="{% url 'journals:mark_first_author' publication_id=publication.id author_object_id=author.id %}">{{ author }}</a>
+                      </li>
+                    {% endfor %}
+                  </ul>
               </li>
               <li><a href="{% url 'journals:add_author' publication.id %}">Add a missing author</a></li>
               <li><a href="{% url 'journals:create_citation_list_metadata' publication.doi_label %}">Create/update citation list metadata</a></li>
diff --git a/journals/urls/general.py b/journals/urls/general.py
index dd9cfea9e..c57c961fd 100644
--- a/journals/urls/general.py
+++ b/journals/urls/general.py
@@ -23,24 +23,15 @@ urlpatterns = [
     url(r'^validate_publication$',
         journals_views.validate_publication,
         name='validate_publication'),
-    url(r'^mark_first_author/(?P<publication_id>[0-9]+)/(?P<contributor_id>[0-9]+)$',
+    url(r'^mark_first_author/(?P<publication_id>[0-9]+)/(?P<author_object_id>[0-9]+)$',
         journals_views.mark_first_author,
         name='mark_first_author'),
-    url(r'^mark_first_author_unregistered/(?P<publication_id>[0-9]+)/(?P<unregistered_author_id>[0-9]+)$',
-        journals_views.mark_first_author_unregistered,
-        name='mark_first_author_unregistered'),
     url(r'^add_author/(?P<publication_id>[0-9]+)/(?P<contributor_id>[0-9]+)$',
         journals_views.add_author,
         name='add_author'),
     url(r'^add_author/(?P<publication_id>[0-9]+)$',
         journals_views.add_author,
         name='add_author'),
-    url(r'^add_unregistered_author/(?P<publication_id>[0-9]+)/(?P<unregistered_author_id>[0-9]+)$',
-        journals_views.add_unregistered_author,
-        name='add_unregistered_author'),
-    url(r'^add_new_unreg_author/(?P<publication_id>[0-9]+)$',
-        journals_views.add_new_unreg_author,
-        name='add_new_unreg_author'),
     url(r'^manage_metadata/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})$',
         journals_views.manage_metadata,
         name='manage_metadata'),
diff --git a/journals/views.py b/journals/views.py
index 863ca75f9..2456765e8 100644
--- a/journals/views.py
+++ b/journals/views.py
@@ -21,8 +21,8 @@ from django.shortcuts import get_object_or_404, render, redirect
 
 from .exceptions import PaperNumberingError
 from .helpers import paper_nr_string, issue_doi_label_from_doi_label
-from .models import Journal, Issue, Publication, UnregisteredAuthor, Deposit, DOAJDeposit,\
-                    GenericDOIDeposit
+from .models import Journal, Issue, Publication, Deposit, DOAJDeposit,\
+                    GenericDOIDeposit, PublicationAuthorsTable
 from .forms import FundingInfoForm, InitiatePublicationForm, ValidatePublicationForm,\
                    UnregisteredAuthorForm, CreateMetadataXMLForm, CitationListBibitemsForm,\
                    ReferenceFormSet
@@ -246,18 +246,22 @@ def validate_publication(request):
     if validate_publication_form.is_valid():
         publication = validate_publication_form.save()
 
-        # Fill in remaining data
+        # Fill remaining data
         submission = publication.accepted_submission
-        publication.authors.add(*submission.authors.all())
-        if publication.first_author:
-            publication.authors.add(publication.first_author)
         if publication.first_author_unregistered:
-            publication.authors_unregistered.add(publication.first_author_unregistered)
+            PublicationAuthorsTable.objects.create(
+                order=1,
+                publication=publication,
+                unregistered_author=publication.first_author_unregistered)
+
+        for submission_author in submission.authors.all():
+            PublicationAuthorsTable.objects.create(
+                publication=publication, contributor=submission_author)
         publication.authors_claims.add(*submission.authors_claims.all())
         publication.authors_false_claims.add(*submission.authors_false_claims.all())
 
         # Add Institutions to the publication
-        for author in publication.authors.all():
+        for author in publication.authors_registered.all():
             for current_affiliation in author.affiliations.active():
                 publication.institutions.add(current_affiliation.institution)
 
@@ -334,25 +338,29 @@ def manage_metadata(request, issue_doi_label=None, doi_label=None):
 
 
 @permission_required('scipost.can_publish_accepted_submission', return_403=True)
-@transaction.atomic
-def mark_first_author(request, publication_id, contributor_id):
+def mark_first_author(request, publication_id, author_object_id):
     publication = get_object_or_404(Publication, id=publication_id)
-    contributor = get_object_or_404(Contributor, id=contributor_id)
-    publication.first_author = contributor
-    publication.first_author_unregistered = None
-    publication.save()
-    return redirect(reverse('journals:manage_metadata',
-                            kwargs={'doi_label': publication.doi_label}))
-
+    author_object = get_object_or_404(publication.authors, id=author_object_id)
 
-@permission_required('scipost.can_publish_accepted_submission', return_403=True)
-@transaction.atomic
-def mark_first_author_unregistered(request, publication_id, unregistered_author_id):
-    publication = get_object_or_404(Publication, id=publication_id)
-    unregistered_author = get_object_or_404(UnregisteredAuthor, id=unregistered_author_id)
-    publication.first_author = None
-    publication.first_author_unregistered = unregistered_author
+    # Save explicit relation
+    if author_object.is_registered:
+        publication.first_author = author_object.contributor
+        publication.first_author_unregistered = None
+    else:
+        publication.first_author = None
+        publication.first_author_unregistered = author_object.unregistered_author
     publication.save()
+
+    # Redo ordering
+    author_object.order = 1
+    author_object.save()
+    author_objects = publication.authors.exclude(id=author_object.id)
+    count = 2
+    for author in author_objects:
+        author.order = count
+        author.save()
+        count += 1
+    messages.success(request, 'Marked {} first author'.format(author_object))
     return redirect(reverse('journals:manage_metadata',
                             kwargs={'doi_label': publication.doi_label}))
 
@@ -369,22 +377,29 @@ def add_author(request, publication_id, contributor_id=None, unregistered_author
     publication = get_object_or_404(Publication, id=publication_id)
     if contributor_id:
         contributor = get_object_or_404(Contributor, id=contributor_id)
-        publication.authors.add(contributor)
+        PublicationAuthorsTable.objects.create(contributor=contributor, publication=publication)
         publication.save()
+        messages.success(request, 'Added {} as an author.'.format(contributor))
         return redirect(reverse('journals:manage_metadata',
                                 kwargs={'doi_label': publication.doi_label}))
 
     contributors_found = None
-    if request.POST:
-        form = UnregisteredAuthorForm(request.POST)
-        if form.is_valid():
-            contributors_found = Contributor.objects.filter(
-                user__last_name__icontains=form.cleaned_data['last_name'])
-    else:
-        form = UnregisteredAuthorForm(request.GET or None)
-        if form.is_valid():
-            contributors_found = Contributor.objects.filter(
-                user__last_name__icontains=form.cleaned_data['last_name'])
+    form = UnregisteredAuthorForm(request.POST or request.GET or None)
+
+    if request.POST and form.is_valid():
+        unregistered_author = form.save()
+        PublicationAuthorsTable.objects.create(
+            publication=publication,
+            unregistered_author=unregistered_author)
+        messages.success(request, 'Added {} as an unregistered author.'.format(
+            unregistered_author
+        ))
+        return redirect(reverse('journals:manage_metadata',
+                                kwargs={'doi_label': publication.doi_label}))
+    elif form.is_valid():
+        contributors_found = Contributor.objects.filter(
+            # user__first_name__icontains=form.cleaned_data['first_name'],
+            user__last_name__icontains=form.cleaned_data['last_name'])
     context = {
         'publication': publication,
         'contributors_found': contributors_found,
@@ -393,31 +408,6 @@ def add_author(request, publication_id, contributor_id=None, unregistered_author
     return render(request, 'journals/add_author.html', context)
 
 
-@permission_required('scipost.can_publish_accepted_submission', return_403=True)
-@transaction.atomic
-def add_unregistered_author(request, publication_id, unregistered_author_id):
-    publication = get_object_or_404(Publication, id=publication_id)
-    unregistered_author = get_object_or_404(UnregisteredAuthor, id=unregistered_author_id)
-    publication.authors_unregistered.add(unregistered_author)
-    publication.save()
-    return redirect(reverse('journals:manage_metadata',
-                            kwargs={'doi_label': publication.doi_label}))
-
-
-@permission_required('scipost.can_publish_accepted_submission', return_403=True)
-@transaction.atomic
-def add_new_unreg_author(request, publication_id):
-    publication = get_object_or_404(Publication, id=publication_id)
-    if request.method == 'POST':
-        new_unreg_author_form = UnregisteredAuthorForm(request.POST)
-        if new_unreg_author_form.is_valid():
-            new_unreg_author = new_unreg_author_form.save()
-            publication.authors_unregistered.add(new_unreg_author)
-            return redirect(reverse('journals:manage_metadata',
-                                    kwargs={'doi_label': publication.doi_label}))
-    raise Http404
-
-
 @permission_required('scipost.can_publish_accepted_submission', return_403=True)
 @transaction.atomic
 def create_citation_list_metadata(request, doi_label):
@@ -604,40 +594,27 @@ def create_metadata_xml(request, doi_label):
         '</journal_issue>\n'
         '<journal_article publication_type=\'full_text\'>\n'
         '<titles><title>' + publication.title + '</title></titles>\n'
-        '<contributors>\n'
     )
 
     # Precondition: all authors MUST be listed in authors field of publication instance,
     # this to be checked by EdAdmin before publishing.
-    for author in publication.authors.all():
-        if author == publication.first_author:
+    initial['metadata_xml'] += '<contributors>\n'
+    for author_object in publication.authors.all():
+        if author_object.order == 1:
             initial['metadata_xml'] += (
                 '<person_name sequence=\'first\' contributor_role=\'author\'> '
-                '<given_name>' + author.user.first_name + '</given_name> '
-                '<surname>' + author.user.last_name + '</surname> '
+                '<given_name>' + author_object.first_name + '</given_name> '
+                '<surname>' + author_object.last_name + '</surname> '
             )
         else:
             initial['metadata_xml'] += (
                 '<person_name sequence=\'additional\' contributor_role=\'author\'> '
-                '<given_name>' + author.user.first_name + '</given_name> '
-                '<surname>' + author.user.last_name + '</surname> '
-            )
-        if author.orcid_id:
-            initial['metadata_xml'] += '<ORCID>http://orcid.org/' + author.orcid_id + '</ORCID>'
-        initial['metadata_xml'] += '</person_name>\n'
-
-    for author_unreg in publication.authors_unregistered.all():
-        if author_unreg == publication.first_author_unregistered:
-            initial['metadata_xml'] += (
-                '<person_name sequence=\'first\' contributor_role=\'author\'> '
-                '<given_name>' + author_unreg.first_name + '</given_name> '
-                '<surname>' + author_unreg.last_name + '</surname> '
+                '<given_name>' + author_object.first_name + '</given_name> '
+                '<surname>' + author_object.last_name + '</surname> '
             )
-        else:
+        if author_object.contributor and author_object.contributor.orcid_id:
             initial['metadata_xml'] += (
-                '<person_name sequence=\'additional\' contributor_role=\'author\'> '
-                '<given_name>' + author_unreg.first_name + '</given_name> '
-                '<surname>' + author_unreg.last_name + '</surname> '
+                '<ORCID>http://orcid.org/' + author_object.contributor.orcid_id + '</ORCID>'
             )
         initial['metadata_xml'] += '</person_name>\n'
     initial['metadata_xml'] += '</contributors>\n'
diff --git a/scipost/forms.py b/scipost/forms.py
index 66d4f85c8..e52294ab5 100644
--- a/scipost/forms.py
+++ b/scipost/forms.py
@@ -313,7 +313,7 @@ class UpdatePersonalDataForm(forms.ModelForm):
         and changes the orcid_id. It marks all Publications, Reports and Comments
         authors by this Contributor with a deposit_requires_update == True.
         """
-        publications = Publication.objects.filter(authors=self.instance)
+        publications = Publication.objects.filter(authors_registered=self.instance)
         for publication in publications:
             publication.doideposit_needs_updating = True
             publication.save()
diff --git a/scipost/migrations/0004_auto_20180212_1932.py b/scipost/migrations/0004_auto_20180212_1932.py
new file mode 100644
index 000000000..f7e69e16c
--- /dev/null
+++ b/scipost/migrations/0004_auto_20180212_1932.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-02-12 18:32
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('scipost', '0003_auto_20180206_1940'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='contributor',
+            name='user',
+            field=models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
+        ),
+    ]
diff --git a/scipost/models.py b/scipost/models.py
index 7e964c1db..b217d78e7 100644
--- a/scipost/models.py
+++ b/scipost/models.py
@@ -39,8 +39,7 @@ class Contributor(models.Model):
     All *science* users of SciPost are Contributors.
     username, password, email, first_name and last_name are inherited from User.
     """
-    user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.PROTECT,
-                                unique=True, blank=True, null=True)
+    user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, unique=True)
     invitation_key = models.CharField(max_length=40, blank=True)
     activation_key = models.CharField(max_length=40, blank=True)
     key_expires = models.DateTimeField(default=timezone.now)
diff --git a/scipost/views.py b/scipost/views.py
index 64fd94a57..a5623844c 100644
--- a/scipost/views.py
+++ b/scipost/views.py
@@ -44,7 +44,7 @@ from affiliations.forms import AffiliationsFormset
 from colleges.permissions import fellowship_or_admin_required
 from commentaries.models import Commentary
 from comments.models import Comment
-from journals.models import Publication, Journal
+from journals.models import Publication, Journal, PublicationAuthorsTable
 from mails.views import MailEditingSubView
 from news.models import NewsItem
 from submissions.models import Submission, RefereeInvitation,\
@@ -798,7 +798,7 @@ def _personal_page_publications(request):
     }
     context['nr_publication_authorships_to_claim'] = Publication.objects.filter(
         author_list__contains=request.user.last_name).exclude(
-        authors=contributor).exclude(
+        authors_registered=contributor).exclude(
         authors_claims=contributor).exclude(
         authors_false_claims=contributor).count()
     return render(request, 'partials/scipost/personal_page/publications.html', context)
@@ -1026,28 +1026,28 @@ def claim_authorships(request):
     contributor = Contributor.objects.get(user=request.user)
 
     publication_authorships_to_claim = (Publication.objects
-                                       .filter(author_list__contains=contributor.user.last_name)
-                                       .exclude(authors__in=[contributor])
-                                       .exclude(authors_claims__in=[contributor])
-                                       .exclude(authors_false_claims__in=[contributor]))
+                                        .filter(author_list__contains=contributor.user.last_name)
+                                        .exclude(authors_registered=contributor)
+                                        .exclude(authors_claims=contributor)
+                                        .exclude(authors_false_claims=contributor))
     pub_auth_claim_form = AuthorshipClaimForm()
     submission_authorships_to_claim = (Submission.objects
                                        .filter(author_list__contains=contributor.user.last_name)
-                                       .exclude(authors__in=[contributor])
-                                       .exclude(authors_claims__in=[contributor])
-                                       .exclude(authors_false_claims__in=[contributor]))
+                                       .exclude(authors=contributor)
+                                       .exclude(authors_claims=contributor)
+                                       .exclude(authors_false_claims=contributor))
     sub_auth_claim_form = AuthorshipClaimForm()
     commentary_authorships_to_claim = (Commentary.objects
                                        .filter(author_list__contains=contributor.user.last_name)
-                                       .exclude(authors__in=[contributor])
-                                       .exclude(authors_claims__in=[contributor])
-                                       .exclude(authors_false_claims__in=[contributor]))
+                                       .exclude(authors=contributor)
+                                       .exclude(authors_claims=contributor)
+                                       .exclude(authors_false_claims=contributor))
     com_auth_claim_form = AuthorshipClaimForm()
     thesis_authorships_to_claim = (ThesisLink.objects
                                    .filter(author__contains=contributor.user.last_name)
-                                   .exclude(author_as_cont__in=[contributor])
-                                   .exclude(author_claims__in=[contributor])
-                                   .exclude(author_false_claims__in=[contributor]))
+                                   .exclude(author_as_cont=contributor)
+                                   .exclude(author_claims=contributor)
+                                   .exclude(author_false_claims=contributor))
     thesis_auth_claim_form = AuthorshipClaimForm()
 
     context = {
@@ -1140,16 +1140,17 @@ def vet_authorship_claim(request, claim_id, claim):
         vetting_contributor = Contributor.objects.get(user=request.user)
         claim_to_vet = AuthorshipClaim.objects.get(pk=claim_id)
 
-        if claim_to_vet.publication is not None:
+        if claim_to_vet.publication:
             claim_to_vet.publication.authors_claims.remove(claim_to_vet.claimant)
             if claim == '1':
-                claim_to_vet.publication.authors.add(claim_to_vet.claimant)
+                PublicationAuthorsTable.objects.create(
+                    publication=claim_to_vet.publication, contributor=claim_to_vet.claimant)
                 claim_to_vet.status = '1'
             elif claim == '0':
                 claim_to_vet.publication.authors_false_claims.add(claim_to_vet.claimant)
                 claim_to_vet.status = '-1'
             claim_to_vet.publication.save()
-        if claim_to_vet.submission is not None:
+        if claim_to_vet.submission:
             claim_to_vet.submission.authors_claims.remove(claim_to_vet.claimant)
             if claim == '1':
                 claim_to_vet.submission.authors.add(claim_to_vet.claimant)
@@ -1158,7 +1159,7 @@ def vet_authorship_claim(request, claim_id, claim):
                 claim_to_vet.submission.authors_false_claims.add(claim_to_vet.claimant)
                 claim_to_vet.status = '-1'
             claim_to_vet.submission.save()
-        if claim_to_vet.commentary is not None:
+        if claim_to_vet.commentary:
             claim_to_vet.commentary.authors_claims.remove(claim_to_vet.claimant)
             if claim == '1':
                 claim_to_vet.commentary.authors.add(claim_to_vet.claimant)
@@ -1167,7 +1168,7 @@ def vet_authorship_claim(request, claim_id, claim):
                 claim_to_vet.commentary.authors_false_claims.add(claim_to_vet.claimant)
                 claim_to_vet.status = '-1'
             claim_to_vet.commentary.save()
-        if claim_to_vet.thesislink is not None:
+        if claim_to_vet.thesislink:
             claim_to_vet.thesislink.author_claims.remove(claim_to_vet.claimant)
             if claim == '1':
                 claim_to_vet.thesislink.author_as_cont.add(claim_to_vet.claimant)
@@ -1189,7 +1190,7 @@ def contributor_info(request, contributor_id):
     on the relevant name (in listing headers of Submissions, ...).
     """
     contributor = get_object_or_404(Contributor, pk=contributor_id)
-    contributor_publications = Publication.objects.published().filter(authors=contributor)
+    contributor_publications = Publication.objects.published().filter(authors_registered=contributor)
     contributor_submissions = Submission.objects.public_unlisted().filter(authors=contributor)
     contributor_commentaries = Commentary.objects.filter(authors=contributor)
     contributor_theses = ThesisLink.objects.vetted().filter(author_as_cont=contributor)
-- 
GitLab