diff --git a/SciPost_v1/settings/base.py b/SciPost_v1/settings/base.py
index 44532bc63c162b25e9049c152ba72e079bfeee4a..5a5468d6df9aa34e0204a18d1029a0e75d713f41 100644
--- a/SciPost_v1/settings/base.py
+++ b/SciPost_v1/settings/base.py
@@ -84,6 +84,7 @@ INSTALLED_APPS = (
     'haystack',
     'rest_framework',
     'sphinxdoc',
+    'affiliations',
     'colleges',
     'commentaries',
     'comments',
diff --git a/SciPost_v1/urls.py b/SciPost_v1/urls.py
index 5e06ce57cc97bd5caf2b866a23a16bbb4ccaf333..5122b6590b62f946fcac51070dbc94fff95a3b50 100644
--- a/SciPost_v1/urls.py
+++ b/SciPost_v1/urls.py
@@ -29,6 +29,7 @@ urlpatterns = [
     url(r'^10.21468/%s/' % JOURNAL_REGEX, include('journals.urls.journal', namespace="journal")),
     url(r'^%s/' % JOURNAL_REGEX, include('journals.urls.journal', namespace="_journal")),
     url(r'^', include('scipost.urls', namespace="scipost")),
+    url(r'^affiliations/', include('affiliations.urls', namespace="affiliations")),
     url(r'^colleges/', include('colleges.urls', namespace="colleges")),
     url(r'^commentaries/', include('commentaries.urls', namespace="commentaries")),
     url(r'^commentary/', include('commentaries.urls', namespace="_commentaries")),
diff --git a/affiliations/__init__.py b/affiliations/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..82323b3dfc6109a67eb86b3f5207b5736e368fa2
--- /dev/null
+++ b/affiliations/__init__.py
@@ -0,0 +1 @@
+default_app_config = 'affiliations.apps.AffiliationsConfig'
diff --git a/affiliations/admin.py b/affiliations/admin.py
new file mode 100644
index 0000000000000000000000000000000000000000..c5caa196a71f9055181c39f64ce325781942d563
--- /dev/null
+++ b/affiliations/admin.py
@@ -0,0 +1,7 @@
+from django.contrib import admin
+
+from .models import Affiliation, Institute
+
+
+admin.site.register(Affiliation)
+admin.site.register(Institute)
diff --git a/affiliations/apps.py b/affiliations/apps.py
new file mode 100644
index 0000000000000000000000000000000000000000..f0e52fdbca89da2fb6af3a3f746f97a019459e75
--- /dev/null
+++ b/affiliations/apps.py
@@ -0,0 +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.Institute)
diff --git a/affiliations/constants.py b/affiliations/constants.py
new file mode 100644
index 0000000000000000000000000000000000000000..d1da522fd4d6cc9dad61d159d840fb114ef05d7a
--- /dev/null
+++ b/affiliations/constants.py
@@ -0,0 +1,4 @@
+TYPE_UNIVERSITY = 'university'
+INSTITUTE_TYPES = (
+    (TYPE_UNIVERSITY, 'University'),
+ )
diff --git a/affiliations/forms.py b/affiliations/forms.py
new file mode 100644
index 0000000000000000000000000000000000000000..c475964a874231ba9841e819d6255f47e60f68cc
--- /dev/null
+++ b/affiliations/forms.py
@@ -0,0 +1,115 @@
+from django import forms
+from django.forms import BaseModelFormSet, modelformset_factory
+# from django.db.models import F
+
+from django_countries import countries
+from django_countries.fields import LazyTypedChoiceField
+from django_countries.widgets import CountrySelectWidget
+
+from common.widgets import DateWidget
+
+from .models import Affiliation, Institute
+
+
+class AffiliationForm(forms.ModelForm):
+    name = forms.CharField(label='* Affiliation', max_length=300)
+    country = LazyTypedChoiceField(
+        choices=countries, label='* Country', widget=CountrySelectWidget())
+
+    class Meta:
+        model = Affiliation
+        fields = (
+            'name',
+            'country',
+            'begin_date',
+            'end_date',
+        )
+        widgets = {
+            'begin_date': DateWidget(required=False),
+            'end_date': DateWidget(required=False)
+        }
+
+    class Media:
+        js = ('scipost/formset.js',)
+
+    def __init__(self, *args, **kwargs):
+        self.contributor = kwargs.pop('contributor')
+        affiliation = kwargs.get('instance')
+        if hasattr(affiliation, 'institute'):
+            institute = affiliation.institute
+            kwargs['initial'] = {
+                'name': institute.name,
+                'country': institute.country
+            }
+        super().__init__(*args, **kwargs)
+
+    def save(self, commit=True):
+        """
+        Save the Affiliation and Institute if neccessary.
+        """
+        affiliation = super().save(commit=False)
+        affiliation.contributor = self.contributor
+
+        if commit:
+            if hasattr(affiliation, 'institute') and affiliation.institute.affiliations.count() == 1:
+                # Just update if there are no other people using this Institute
+                institute = affiliation.institute
+                institute.name = self.cleaned_data['name']
+                institute.country = self.cleaned_data['country']
+                institute.save()
+            else:
+                institute, __ = Institute.objects.get_or_create(
+                    name=self.cleaned_data['name'],
+                    country=self.cleaned_data['country'])
+                affiliation.institute = institute
+            affiliation.save()
+        return affiliation
+
+
+class AffiliationsFormSet(BaseModelFormSet):
+    """
+    This formset helps update the Institutes for the Contributor at specific time periods.
+    """
+    def __init__(self, *args, **kwargs):
+        self.contributor = kwargs.pop('contributor')
+        super().__init__(*args, **kwargs)
+        self.queryset = self.contributor.affiliations.all()
+
+    def get_form_kwargs(self, *args, **kwargs):
+        kwargs = super().get_form_kwargs(*args, **kwargs)
+        kwargs['contributor'] = self.contributor
+        return kwargs
+
+    def save(self, commit=True):
+        self.deleted_objects = []
+
+        for form in self.forms:
+            form.save(commit)
+
+        # Delete Affiliations if needed
+        for form in self.deleted_forms:
+            self.deleted_objects.append(form.instance)
+            self.delete_existing(form.instance, commit=commit)
+
+
+AffiliationsFormset = modelformset_factory(Affiliation, form=AffiliationForm, can_delete=True,
+                                           formset=AffiliationsFormSet, extra=0)
+
+
+class InstituteMergeForm(forms.ModelForm):
+    institute = forms.ModelChoiceField(queryset=Institute.objects.none())
+
+    class Meta:
+        model = Institute
+        fields = []
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.fields['institute'].queryset = Institute.objects.exclude(id=self.instance.id)
+
+    def save(self, commit=True):
+        old_institute = self.cleaned_data['institute']
+        if commit:
+            Affiliation.objects.filter(institute=old_institute).update(institute=self.instance)
+            old_institute.delete()
+        return self.instance
diff --git a/affiliations/managers.py b/affiliations/managers.py
new file mode 100644
index 0000000000000000000000000000000000000000..1cc7840aedaba9741dbf02be686327f3967dcbaa
--- /dev/null
+++ b/affiliations/managers.py
@@ -0,0 +1,14 @@
+import datetime
+
+from django.db import models
+from django.db.models import Q
+
+
+class AffiliationQuerySet(models.QuerySet):
+    def active(self):
+        today = datetime.date.today()
+        return self.filter(
+            Q(begin_date__lte=today, end_date__isnull=True) |
+            Q(begin_date__isnull=True, end_date__gte=today) |
+            Q(begin_date__lte=today, end_date__gte=today) |
+            Q(begin_date__isnull=True, end_date__isnull=True))
diff --git a/affiliations/migrations/0001_initial.py b/affiliations/migrations/0001_initial.py
new file mode 100644
index 0000000000000000000000000000000000000000..c6f23fbf3012655430243cadf43e90b45ba37f55
--- /dev/null
+++ b/affiliations/migrations/0001_initial.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-11-01 19:00
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django_countries.fields
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Institute',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=255)),
+                ('country', django_countries.fields.CountryField(max_length=2)),
+                ('type', models.CharField(choices=[('university', 'University')], default='university', max_length=16)),
+            ],
+        ),
+    ]
diff --git a/affiliations/migrations/0002_affiliation_acronym.py b/affiliations/migrations/0002_affiliation_acronym.py
new file mode 100644
index 0000000000000000000000000000000000000000..01d4fb2775e7a720a601742f3cbbec47857629af
--- /dev/null
+++ b/affiliations/migrations/0002_affiliation_acronym.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-11-01 19:06
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('affiliations', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='institute',
+            name='acronym',
+            field=models.CharField(blank=True, max_length=16),
+        ),
+    ]
diff --git a/affiliations/migrations/0003_auto_20171101_2022.py b/affiliations/migrations/0003_auto_20171101_2022.py
new file mode 100644
index 0000000000000000000000000000000000000000..65eda86a2566d86f96ad9d7cadda678b4c865b39
--- /dev/null
+++ b/affiliations/migrations/0003_auto_20171101_2022.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-11-01 19:22
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+def fill_institutes(apps, schema_editor):
+    Contributor = apps.get_model('scipost', 'Contributor')
+    Institute = apps.get_model('affiliations', 'Institute')
+    for contributor in Contributor.objects.all():
+        affiliation, __ = Institute.objects.get_or_create(
+            name=contributor.affiliation, country=contributor.country_of_employment)
+        contributor._affiliation = affiliation
+        contributor.save()
+
+
+def return_none(*args, **kwargs):
+    return
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('affiliations', '0002_affiliation_acronym'),
+        ('scipost', '0066_contributor__affiliation')
+    ]
+
+    operations = [
+        migrations.RunPython(fill_institutes, return_none),
+    ]
diff --git a/affiliations/migrations/0004_auto_20171101_2208.py b/affiliations/migrations/0004_auto_20171101_2208.py
new file mode 100644
index 0000000000000000000000000000000000000000..be2b86fd29e02e05d65e33b75b6c5b1672f57ef9
--- /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='institute',
+            options={'ordering': ['country']},
+        ),
+    ]
diff --git a/affiliations/migrations/0005_affiliation.py b/affiliations/migrations/0005_affiliation.py
new file mode 100644
index 0000000000000000000000000000000000000000..7d4a84394dd3b11b752ff9295448beae27837c0a
--- /dev/null
+++ b/affiliations/migrations/0005_affiliation.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-11-02 07:41
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('scipost', '0069_auto_20171102_0840'),
+        ('affiliations', '0004_auto_20171101_2208'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Affiliation',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('begin_date', models.DateField(blank=True, null=True)),
+                ('end_date', models.DateField(blank=True, null=True)),
+                ('contributor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='affiliations', to='scipost.Contributor')),
+                ('institute', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='affiliations', to='affiliations.Institute')),
+            ],
+            options={
+                'default_related_name': 'affiliations',
+            },
+        ),
+    ]
diff --git a/affiliations/migrations/0006_auto_20171102_0843.py b/affiliations/migrations/0006_auto_20171102_0843.py
new file mode 100644
index 0000000000000000000000000000000000000000..bf8745a366ed9df5067bb99ca24254415596790f
--- /dev/null
+++ b/affiliations/migrations/0006_auto_20171102_0843.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-11-02 07:43
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+def fill_affiliations(apps, schema_editor):
+    Contributor = apps.get_model('scipost', 'Contributor')
+    Affiliation = apps.get_model('affiliations', 'Affiliation')
+    for contributor in Contributor.objects.all():
+        Affiliation.objects.get_or_create(
+            institute=contributor.old_affiliation_fk, contributor=contributor)
+
+
+def return_none(*args, **kwargs):
+    return
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('affiliations', '0005_affiliation'),
+    ]
+
+    operations = [
+        migrations.RunPython(fill_affiliations, return_none),
+    ]
diff --git a/affiliations/migrations/0007_auto_20171102_1256.py b/affiliations/migrations/0007_auto_20171102_1256.py
new file mode 100644
index 0000000000000000000000000000000000000000..0f044ab100678bee7a9753772f7b68506dbacb60
--- /dev/null
+++ b/affiliations/migrations/0007_auto_20171102_1256.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-11-02 11:56
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('affiliations', '0006_auto_20171102_0843'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='affiliation',
+            options={'ordering': ['begin_date', 'end_date', 'institute']},
+        ),
+    ]
diff --git a/affiliations/migrations/__init__.py b/affiliations/migrations/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/affiliations/models.py b/affiliations/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..fddbf443c2a86de9f5703abc3d4d026aab3edc98
--- /dev/null
+++ b/affiliations/models.py
@@ -0,0 +1,48 @@
+from django.db import models
+from django.urls import reverse
+
+from django_countries.fields import CountryField
+
+from .constants import INSTITUTE_TYPES, TYPE_UNIVERSITY
+from .managers import AffiliationQuerySet
+
+
+class Institute(models.Model):
+    """
+    Any (scientific) Institute in the world should ideally have a SciPost registration.
+    """
+    name = models.CharField(max_length=255)
+    acronym = models.CharField(max_length=16, blank=True)
+    country = CountryField()
+    type = models.CharField(max_length=16, choices=INSTITUTE_TYPES, default=TYPE_UNIVERSITY)
+
+    class Meta:
+        default_related_name = 'institutes'
+        ordering = ['country']
+
+    def __str__(self):
+        return '{name} ({country})'.format(name=self.name, country=self.get_country_display())
+
+    def get_absolute_url(self):
+        return reverse('affiliations:institute_details', args=(self.id,))
+
+
+class Affiliation(models.Model):
+    """
+    An Affiliation is a (time dependent) connection between an Institute and a Contributor.
+    This could thus be changed over time and history will be preserved.
+    """
+    institute = models.ForeignKey('affiliations.Institute')
+    contributor = models.ForeignKey('scipost.Contributor')
+    begin_date = models.DateField(null=True, blank=True)
+    end_date = models.DateField(null=True, blank=True)
+
+    objects = AffiliationQuerySet.as_manager()
+
+    class Meta:
+        default_related_name = 'affiliations'
+        ordering = ['begin_date', 'end_date', 'institute']
+
+    def __str__(self):
+        return '{contributor} ({institute})'.format(
+            contributor=self.contributor, institute=self.institute.name)
diff --git a/affiliations/signals.py b/affiliations/signals.py
new file mode 100644
index 0000000000000000000000000000000000000000..2d583e8df78604bb2c2cef5cdb9d3c7737db6902
--- /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 Institute instance. You may want to validate it.',
+                        target=instance)
diff --git a/affiliations/templates/affiliations/institute_form.html b/affiliations/templates/affiliations/institute_form.html
new file mode 100644
index 0000000000000000000000000000000000000000..57d68b57f9c1b361ceb937b8790d1fec092da1fe
--- /dev/null
+++ b/affiliations/templates/affiliations/institute_form.html
@@ -0,0 +1,55 @@
+{% extends 'scipost/_personal_page_base.html' %}
+
+{% load bootstrap %}
+
+{% block pagetitle %}: Institutes details{% endblock pagetitle %}
+
+{% block breadcrumb_items %}
+    {{ block.super }}
+    <a href="{% url 'affiliations:institutes' %}" class="breadcrumb-item">Institutes</a>
+    <span class="breadcrumb-item">Institute detail</span>
+{% endblock %}
+
+{% block content %}
+
+<h1>Institute {{ institute }}</h1>
+
+<form method="post">
+    {% csrf_token %}
+    {{ form|bootstrap }}
+    <input class="btn btn-primary" type="submit" value="Update Institute">
+</form>
+
+<br>
+<a href="javascript:;" data-toggle="toggle" data-target="#merging">Merge another institute into {{ institute }}?</a>
+<div id="merging" style="display: none;">
+    <h3>Merge institutes</h3>
+    <div class="card border-danger">
+        <div class="card-body d-flex flex-row">
+            <div class="p-2">
+                <i class="fa fa-2x fa-exclamation-triangle text-warning" aria-hidden="true"></i>
+            </div>
+            <div class="px-2">
+                You better be sure what you are doing, this is a one-way street. This will merge the chosen institute into {{ institute }}. The fields of the chosen institute <strong>will be lost</strong> however.
+            </div>
+
+        </div>
+    </div>
+    <br>
+    <form action="{% url 'affiliations:merge_institutes' institute.id %}" method="post">
+        {% csrf_token %}
+        {{ merge_form|bootstrap }}
+        <input class="btn btn-primary" type="submit" value="Merge institutes" onclick="return confirm('Are you sure this is what you want?')">
+    </form>
+</div>
+
+<br>
+<br>
+<h3>Contributors of {{ institute }}</h3>
+<ul>
+    {% for contributor in institute.contributors.all %}
+        <li>{{ contributor }}</li>
+    {% endfor %}
+</ul>
+
+{% endblock content %}
diff --git a/affiliations/templates/affiliations/institute_list.html b/affiliations/templates/affiliations/institute_list.html
new file mode 100644
index 0000000000000000000000000000000000000000..c40b5a8c9f9e50339f5f07e1e228810cf148d711
--- /dev/null
+++ b/affiliations/templates/affiliations/institute_list.html
@@ -0,0 +1,30 @@
+{% extends 'scipost/_personal_page_base.html' %}
+
+
+{% block pagetitle %}: Institutes{% endblock pagetitle %}
+
+{% block breadcrumb_items %}
+    {{ block.super }}
+    <span class="breadcrumb-item">Institutes</span>
+{% endblock %}
+
+{% block content %}
+
+<h1>All Institutes in the database</h1>
+
+{% if is_paginated %}
+    {% include 'partials/pagination.html' with page_obj=page_obj %}
+{% endif %}
+<ul>
+    {% for institute in object_list %}
+        <li><a href="{% url 'affiliations:institute_details' institute.id %}">{{ institute }}</a></li>
+    {% empty %}
+        <li><em>There are no Institutes known yet.</em><li>
+    {% endfor %}
+</ul>
+{% if is_paginated %}
+    {% include 'partials/pagination.html' with page_obj=page_obj %}
+{% endif %}
+
+
+{% endblock content %}
diff --git a/affiliations/tests.py b/affiliations/tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ce503c2dd97ba78597f6ff6e4393132753573f6
--- /dev/null
+++ b/affiliations/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/affiliations/urls.py b/affiliations/urls.py
new file mode 100644
index 0000000000000000000000000000000000000000..6cc8b42ba3ce945719a8b6ca27bfd52eb9f2da1a
--- /dev/null
+++ b/affiliations/urls.py
@@ -0,0 +1,11 @@
+from django.conf.urls import url
+
+from . import views
+
+urlpatterns = [
+    url(r'^$', views.InstituteListView.as_view(), name='institutes'),
+    url(r'^(?P<institute_id>[0-9]+)/$', views.InstituteUpdateView.as_view(),
+        name='institute_details'),
+    url(r'^(?P<institute_id>[0-9]+)/merge$', views.merge_institutes,
+        name='merge_institutes'),
+]
diff --git a/affiliations/views.py b/affiliations/views.py
new file mode 100644
index 0000000000000000000000000000000000000000..dd028053c37ba0fb1c8e77d1b463ad6a16155854
--- /dev/null
+++ b/affiliations/views.py
@@ -0,0 +1,52 @@
+from django.shortcuts import redirect
+from django.contrib import messages
+from django.contrib.auth.decorators import permission_required
+from django.urls import reverse
+from django.utils.decorators import method_decorator
+from django.views.generic.edit import UpdateView
+from django.views.generic.list import ListView
+from django.shortcuts import get_object_or_404
+
+from .forms import InstituteMergeForm
+from .models import Institute
+
+
+@method_decorator(permission_required('scipost.can_manage_affiliations'), name='dispatch')
+class InstituteListView(ListView):
+    model = Institute
+    paginate_by = 100
+
+
+@method_decorator(permission_required('scipost.can_manage_affiliations'), name='dispatch')
+class InstituteUpdateView(UpdateView):
+    model = Institute
+    pk_url_kwarg = 'institute_id'
+    fields = [
+        'name',
+        'acronym',
+        'country',
+    ]
+
+    def get_context_data(self, *args, **kwargs):
+        context = super().get_context_data(*args, **kwargs)
+        context['merge_form'] = InstituteMergeForm()
+        return context
+
+    def form_valid(self, *args, **kwargs):
+        messages.success(self.request, 'Institute saved')
+        return super().form_valid(*args, **kwargs)
+
+
+@permission_required('scipost.can_manage_affiliations')
+def merge_institutes(request, institute_id):
+    """
+    Merge Affiliation (affiliation_id) into the Affliation chosen in the form.
+    """
+    institute = get_object_or_404(Institute, id=institute_id)
+    form = InstituteMergeForm(request.POST or None, instance=institute)
+    if form.is_valid():
+        form.save()
+        messages.success(request, 'Institute {a} merged into {b}'.format(
+            a=form.cleaned_data.get('institute', '?'), b=institute))
+
+    return redirect(reverse('affiliations:institute_details', args=(institute.id,)))
diff --git a/common/widgets.py b/common/widgets.py
new file mode 100644
index 0000000000000000000000000000000000000000..faa904bb9279dc95b3b5e9f1cfd8f0a85b51a28f
--- /dev/null
+++ b/common/widgets.py
@@ -0,0 +1,96 @@
+# import calendar
+import datetime
+import re
+
+from django.forms.widgets import Widget, Select, NumberInput
+from django.utils.dates import MONTHS
+from django.utils.safestring import mark_safe
+
+__all__ = ('DateWidget',)
+
+RE_DATE = re.compile(r'(\d{4})-(\d\d?)-(\d\d?)$')
+
+
+class DateWidget(Widget):
+    """
+    A Widget that splits date input into two <select> boxes for month and year,
+    with 'day' defaulting to the first of the month.
+
+    Based on SelectDateWidget, in
+
+    django/trunk/django/forms/extras/widgets.py
+
+
+    """
+    none_value = (0, 'Month')
+    day_field = '%s_day'
+    month_field = '%s_month'
+    year_field = '%s_year'
+
+    def __init__(self, attrs=None, end=False, required=False):
+        self.attrs = attrs or {}
+        self.required = required
+        self.today = datetime.date.today()
+        self.round_to_end = end
+
+        # Month
+        self.month_choices = dict(MONTHS.items())
+        if not self.required:
+            self.month_choices[self.none_value[0]] = self.none_value[1]
+        self.month_choices = sorted(self.month_choices.items())
+
+    def sqeeze_form_group(self, html, width=4):
+        return '<div class="col-md-{width}">{html}</div>'.format(width=width, html=html)
+
+    def render(self, name, value, attrs=None):
+        try:
+            year_val, month_val, day_val = value.year, value.month, value.day
+        except AttributeError:
+            year_val = month_val = day_val = None
+            if isinstance(value, (str, bytes)):
+                match = RE_DATE.match(value)
+                if match:
+                    year_val, month_val, day_val = [int(v) for v in match.groups()]
+
+        output = []
+
+        if 'id' in self.attrs:
+            id_ = self.attrs['id']
+        else:
+            id_ = 'id_%s' % name
+
+        # Day input
+        local_attrs = self.build_attrs({'id': self.day_field % id_})
+        s = NumberInput(attrs={'class': 'form-control', 'placeholder': 'Day'})
+        select_html = s.render(self.day_field % name, day_val, local_attrs)
+        output.append(self.sqeeze_form_group(select_html))
+
+        # Month input
+        if hasattr(self, 'month_choices'):
+            local_attrs = self.build_attrs({'id': self.month_field % id_})
+            s = Select(choices=self.month_choices, attrs={'class': 'form-control'})
+            select_html = s.render(self.month_field % name, month_val, local_attrs)
+            output.append(self.sqeeze_form_group(select_html))
+
+        # Year input
+        local_attrs = self.build_attrs({'id': self.year_field % id_})
+        s = NumberInput(attrs={'class': 'form-control', 'placeholder': 'Year'})
+        select_html = s.render(self.year_field % name, year_val, local_attrs)
+        output.append(self.sqeeze_form_group(select_html))
+
+        output = '<div class="row mb-0">{0}</div>'.format(u'\n'.join(output))
+
+        return mark_safe(output)
+
+    @classmethod
+    def id_for_label(self, id_):
+        return '%s_month' % id_
+
+    def value_from_datadict(self, data, files, name):
+        y = data.get(self.year_field % name)
+        m = data.get(self.month_field % name)
+        d = data.get(self.day_field % name)
+
+        if m == "0":
+            return None
+        return '%s-%s-%s' % (y, m, d)
diff --git a/funders/views.py b/funders/views.py
index 8a2593f66dbad45d914f89e3efd0150c15f8749d..1b3d3b8c20ab40e39a335be12f57d250615c24f6 100644
--- a/funders/views.py
+++ b/funders/views.py
@@ -10,7 +10,7 @@ from .models import Funder, Grant
 from .forms import FunderRegistrySearchForm, FunderForm, GrantForm
 
 
-@permission_required('scipost.can_publish_accepted_submission', raise_exception=True)
+@permission_required('scipost.can_view_all_funding_info', raise_exception=True)
 def funders(request):
     funders = Funder.objects.all()
     form = FunderRegistrySearchForm()
@@ -21,7 +21,7 @@ def funders(request):
     return render(request, 'funders/funders.html', context)
 
 
-@permission_required('scipost.can_publish_accepted_submission', raise_exception=True)
+@permission_required('scipost.can_view_all_funding_info', raise_exception=True)
 def query_crossref_for_funder(request):
     """
     Checks Crossref's Fundref Registry for an entry
@@ -41,7 +41,7 @@ def query_crossref_for_funder(request):
     return render(request, 'funders/query_crossref_for_funder.html', context)
 
 
-@permission_required('scipost.can_publish_accepted_submission', raise_exception=True)
+@permission_required('scipost.can_view_all_funding_info', raise_exception=True)
 def add_funder(request):
     form = FunderForm(request.POST or None)
     if form.is_valid():
@@ -63,7 +63,7 @@ def funder_publications(request, funder_id):
     return render(request, 'funders/funder_details.html', context)
 
 
-@permission_required('scipost.can_publish_accepted_submission', raise_exception=True)
+@permission_required('scipost.can_view_all_funding_info', raise_exception=True)
 def add_grant(request):
     grant_form = GrantForm(request.POST or None)
     if grant_form.is_valid():
diff --git a/journals/migrations/0049_auto_20171101_2000.py b/journals/migrations/0049_auto_20171101_2000.py
new file mode 100644
index 0000000000000000000000000000000000000000..d16635f0b7243a049ac2bf60984c551937bf31c8
--- /dev/null
+++ b/journals/migrations/0049_auto_20171101_2000.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-11-01 19:00
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('journals', '0048_auto_20171101_1028'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='publication',
+            name='funders_generic',
+            field=models.ManyToManyField(blank=True, related_name='publications', to='funders.Funder'),
+        ),
+        migrations.AlterField(
+            model_name='publication',
+            name='grants',
+            field=models.ManyToManyField(blank=True, related_name='publications', to='funders.Grant'),
+        ),
+    ]
diff --git a/journals/migrations/0050_publication_institutes.py b/journals/migrations/0050_publication_institutes.py
new file mode 100644
index 0000000000000000000000000000000000000000..71f4c2adfd91e8d086d6d8d5df34f5226ab7d015
--- /dev/null
+++ b/journals/migrations/0050_publication_institutes.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-11-02 12:05
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('affiliations', '0007_auto_20171102_1256'),
+        ('journals', '0049_auto_20171101_2000'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='publication',
+            name='institutes',
+            field=models.ManyToManyField(blank=True, related_name='publications', to='affiliations.Institute'),
+        ),
+    ]
diff --git a/journals/migrations/0051_auto_20171102_1307.py b/journals/migrations/0051_auto_20171102_1307.py
new file mode 100644
index 0000000000000000000000000000000000000000..aa16b353181771fa0bd88287991f6ec99322d596
--- /dev/null
+++ b/journals/migrations/0051_auto_20171102_1307.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-11-02 12:07
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+def fill_publications(apps, schema_editor):
+    """
+    Add all Institutes to a Publication, assuming all Contributors Affiliations are
+    active at moment of publication.
+    """
+    Publication = apps.get_model('journals', 'Publication')
+    for publication in Publication.objects.all():
+        for author in publication.authors.all():
+            for affiliation in author.affiliations.all():
+                publication.institutes.add(affiliation.institute)
+
+
+def return_none(*args, **kwargs):
+    return
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('journals', '0050_publication_institutes'),
+    ]
+
+    operations = [
+        migrations.RunPython(fill_publications, return_none),
+    ]
diff --git a/journals/models.py b/journals/models.py
index f32b6386af8fc7c9c2e0241af54f67bd41711888..a7ac541c86f2cf72f6150e39ac764d264132dcf0 100644
--- a/journals/models.py
+++ b/journals/models.py
@@ -123,20 +123,28 @@ class Issue(models.Model):
 
 
 class Publication(models.Model):
+    """
+    A Publication is an object directly related to an accepted Submission. It contains metadata,
+    the actual publication file, author data, etc. etc.
+    """
+    # Publication data
     accepted_submission = models.OneToOneField('submissions.Submission', on_delete=models.CASCADE)
     in_issue = models.ForeignKey(Issue, on_delete=models.CASCADE)
     paper_nr = models.PositiveSmallIntegerField()
+
+    # Core fields
+    title = models.CharField(max_length=300)
+    author_list = models.CharField(max_length=1000, verbose_name="author list")
+    abstract = models.TextField()
+    pdf_file = models.FileField(upload_to='UPLOADS/PUBLICATIONS/%Y/%m/', max_length=200)
     discipline = models.CharField(max_length=20, choices=SCIPOST_DISCIPLINES, default='physics')
     domain = models.CharField(max_length=3, choices=SCIPOST_JOURNALS_DOMAINS)
     subject_area = models.CharField(max_length=10, choices=SCIPOST_SUBJECT_AREAS,
                                     verbose_name='Primary subject area', default='Phys:QP')
-    secondary_areas = ChoiceArrayField(models.CharField(max_length=10,
-                                                        choices=SCIPOST_SUBJECT_AREAS),
-                                       blank=True, null=True)
-    title = models.CharField(max_length=300)
-    author_list = models.CharField(max_length=1000, verbose_name="author list")
+    secondary_areas = ChoiceArrayField(
+        models.CharField(max_length=10, choices=SCIPOST_SUBJECT_AREAS), blank=True, null=True)
 
-    # Authors which have been mapped to contributors:
+    # Authors
     authors = models.ManyToManyField('scipost.Contributor', blank=True,
                                      related_name='publications')
     authors_unregistered = models.ManyToManyField(UnregisteredAuthor, blank=True,
@@ -151,26 +159,33 @@ class Publication(models.Model):
                                             related_name='claimed_publications')
     authors_false_claims = models.ManyToManyField('scipost.Contributor', blank=True,
                                                   related_name='false_claimed_publications')
-    abstract = models.TextField()
-    pdf_file = models.FileField(upload_to='UPLOADS/PUBLICATIONS/%Y/%m/', max_length=200)
+
     cc_license = models.CharField(max_length=32, choices=CC_LICENSES, default=CCBY4)
+
+    # Funders
     grants = models.ManyToManyField('funders.Grant', blank=True, related_name="publications")
-    funders_generic = models.ManyToManyField('funders.Funder', blank=True,
-                                             related_name="publications")  # not linked to a grant
+    funders_generic = models.ManyToManyField(
+        'funders.Funder', blank=True, related_name="publications")  # not linked to a grant
+    institutes = models.ManyToManyField('affiliations.Institute',
+                                        blank=True, related_name="publications")
+
+    # Metadata
     metadata = JSONField(default={}, blank=True, null=True)
     metadata_xml = models.TextField(blank=True, null=True)  # for Crossref deposit
-    latest_metadata_update = models.DateTimeField(blank=True, null=True)
     metadata_DOAJ = JSONField(default={}, blank=True, null=True)
-    BiBTeX_entry = models.TextField(blank=True, null=True)
     doi_label = models.CharField(max_length=200, unique=True, db_index=True,
                                  validators=[doi_publication_validator])
+    BiBTeX_entry = models.TextField(blank=True, null=True)
     doideposit_needs_updating = models.BooleanField(default=False)
+    citedby = JSONField(default={}, blank=True, null=True)
+
+    # Date fields
     submission_date = models.DateField(verbose_name='submission date')
     acceptance_date = models.DateField(verbose_name='acceptance date')
     publication_date = models.DateField(verbose_name='publication date')
     latest_citedby_update = models.DateTimeField(null=True, blank=True)
+    latest_metadata_update = models.DateTimeField(blank=True, null=True)
     latest_activity = models.DateTimeField(default=timezone.now)
-    citedby = JSONField(default={}, blank=True, null=True)
 
     objects = PublicationManager()
 
diff --git a/journals/templates/journals/publication_detail.html b/journals/templates/journals/publication_detail.html
index a3f4acd26f431be4c3c53f37d2a446c3bb910aa9..64a0039f85cbb602a1f8da5a5053912a8c8357ea 100644
--- a/journals/templates/journals/publication_detail.html
+++ b/journals/templates/journals/publication_detail.html
@@ -3,8 +3,9 @@
 {% load journals_extras %}
 {% load staticfiles %}
 {% load scipost_extras %}
+{% load user_groups %}
 
-{% block pagetitle %}: Publication detail{% endblock pagetitle %}
+{% block pagetitle %}: {{ publication.citation }} - {{ publication.title }}{% endblock pagetitle %}
 
 {% block breadcrumb_items %}
     {{block.super}}
@@ -46,6 +47,7 @@
 {% endblock headsup %}
 
 {% block content %}
+    {% is_edcol_admin request.user as is_edcol_admin %}
 
     {% include 'journals/_publication_details.html' with publication=publication %}
 
@@ -85,10 +87,20 @@
               {{ author }} {% if not forloop.last %}&nbsp;&middot;&nbsp;{% endif %}
             {% endfor %}
             </p>
+
+            {% if is_edcol_admin %}
+                {# This function is not available for public yet! #}
+                <h3>Institutes related to this Publication: <small>(adminstrator only)</small></h3>
+                <ul>
+                    {% for institute in publication.institutes.all %}
+                        <li>{{ institute }}</li>
+                    {% endfor %}
+                </ul>
+            {% endif %}
         </div>
     </div>
 
-    {% if request.user|is_in_group:'Editorial Administrators' %}
+    {% if is_edcol_admin %}
     <hr>
     <div class="row">
         <div class="col-12">
diff --git a/journals/views.py b/journals/views.py
index 792062e6b868e99ac9f1f681f05f768a39ded83f..bbd2b074b7cbd282d582d9f23d05a7f2828b2d10 100644
--- a/journals/views.py
+++ b/journals/views.py
@@ -253,6 +253,13 @@ def validate_publication(request):
             publication.authors_unregistered.add(publication.first_author_unregistered)
         publication.authors_claims.add(*submission.authors_claims.all())
         publication.authors_false_claims.add(*submission.authors_false_claims.all())
+
+        # Add Institutes to the publication
+        for author in publication.authors.all():
+            for institute in author.affiliations.active():
+                publication.institutes.add(institute)
+
+        # Save the beast
         publication.save()
 
         # Move file to final location
diff --git a/notifications/managers.py b/notifications/managers.py
index 4ec6b22b904ec620247661c0314472a25ade2ba7..1ac9a2e2e0a9f527127475ed4906cdca2477b07c 100644
--- a/notifications/managers.py
+++ b/notifications/managers.py
@@ -13,6 +13,10 @@ class NotificationQuerySet(models.query.QuerySet):
         """Return only unread items in the current queryset"""
         return self.filter(unread=True)
 
+    def pseudo_unread(self):
+        """Return only unread items in the current queryset"""
+        return self.filter(pseudo_unread=True)
+
     def read(self):
         """Return only read items in the current queryset"""
         return self.filter(unread=False)
@@ -27,6 +31,16 @@ class NotificationQuerySet(models.query.QuerySet):
 
         return qs.update(unread=False)
 
+    def mark_all_as_pseudo_read(self, recipient=None):
+        """Mark as read any unread messages in the current queryset."""
+        # We want to filter out read ones, as later we will store
+        # the time they were marked as read.
+        qs = self.pseudo_unread()
+        if recipient:
+            qs = qs.filter(recipient=recipient)
+
+        return qs.update(pseudo_unread=False)
+
     def mark_all_as_unread(self, recipient=None):
         """Mark as unread any read messages in the current queryset."""
         qs = self.read()
diff --git a/notifications/migrations/0003_fakeactors.py b/notifications/migrations/0003_fakeactors.py
new file mode 100644
index 0000000000000000000000000000000000000000..d254da9d3748ba9bb62c850f48a8f3b696304b02
--- /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/migrations/0003_notification_pseudo_unread.py b/notifications/migrations/0003_notification_pseudo_unread.py
new file mode 100644
index 0000000000000000000000000000000000000000..6e6953bef863e3fd5fd39d9818696faffc1cefa5
--- /dev/null
+++ b/notifications/migrations/0003_notification_pseudo_unread.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-10-29 20:37
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('notifications', '0002_auto_20171021_1821'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='notification',
+            name='pseudo_unread',
+            field=models.BooleanField(default=True),
+        ),
+    ]
diff --git a/notifications/migrations/0004_merge_20171103_1132.py b/notifications/migrations/0004_merge_20171103_1132.py
new file mode 100644
index 0000000000000000000000000000000000000000..d1e1181dc2afeed0f959be72acb45789d4e141e7
--- /dev/null
+++ b/notifications/migrations/0004_merge_20171103_1132.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-11-03 10:32
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('notifications', '0003_fakeactors'),
+        ('notifications', '0003_notification_pseudo_unread'),
+    ]
+
+    operations = [
+    ]
diff --git a/notifications/migrations/0005_auto_20171105_1004.py b/notifications/migrations/0005_auto_20171105_1004.py
new file mode 100644
index 0000000000000000000000000000000000000000..6fa563d7794c73ef134263774bd4dc39936b5a60
--- /dev/null
+++ b/notifications/migrations/0005_auto_20171105_1004.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-11-05 09:04
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('notifications', '0004_merge_20171103_1132'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='notification',
+            name='unread_datetime',
+            field=models.DateTimeField(blank=True, null=True),
+        ),
+        migrations.AlterField(
+            model_name='notification',
+            name='created',
+            field=models.DateTimeField(auto_now_add=True),
+        ),
+    ]
diff --git a/notifications/migrations/0006_remove_notification_unread_datetime.py b/notifications/migrations/0006_remove_notification_unread_datetime.py
new file mode 100644
index 0000000000000000000000000000000000000000..8de0a18add4020f5ac0f0715a493efb758f09adf
--- /dev/null
+++ b/notifications/migrations/0006_remove_notification_unread_datetime.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-11-05 09:09
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('notifications', '0005_auto_20171105_1004'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='notification',
+            name='unread_datetime',
+        ),
+    ]
diff --git a/notifications/models.py b/notifications/models.py
index d7d9fef894b28bc7af060517cb9d136c502f0940..e05b53710ade32063ef292107119e6f9b0d4835c 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
@@ -33,6 +43,7 @@ class Notification(models.Model):
     recipient = models.ForeignKey(settings.AUTH_USER_MODEL, blank=False,
                                   related_name='notifications')
     unread = models.BooleanField(default=True)
+    pseudo_unread = models.BooleanField(default=True)  # Used to keep notification-bg "active"
 
     actor_content_type = models.ForeignKey(ContentType, related_name='notify_actor')
     actor_object_id = models.CharField(max_length=255)
@@ -51,7 +62,7 @@ class Notification(models.Model):
     action_object_object_id = models.CharField(max_length=255, blank=True, null=True)
     action_object = GenericForeignKey('action_object_content_type', 'action_object_object_id')
 
-    created = models.DateTimeField(default=timezone.now)
+    created = models.DateTimeField(auto_now_add=True)
 
     # This field is for internal use only. It is used to prevent duplicate sending
     # of notifications.
@@ -95,19 +106,22 @@ class Notification(models.Model):
         return id2slug(self.id)
 
     def mark_toggle(self):
-        if self.unread:
+        if self.pseudo_unread:
             self.unread = False
-            self.save()
+            self.pseudo_unread = False
         else:
             self.unread = True
-            self.save()
+            self.pseudo_unread = True
+        self.save()
 
     def mark_as_read(self):
-        if self.unread:
+        if self.unread or self.pseudo_unread:
             self.unread = False
+            self.pseudo_unread = False
             self.save()
 
     def mark_as_unread(self):
-        if not self.unread:
+        if not self.unread or not self.pseudo_unread:
             self.unread = True
+            self.pseudo_unread = True
             self.save()
diff --git a/notifications/templatetags/notifications_tags.py b/notifications/templatetags/notifications_tags.py
index 42c62d09bfa42bf4735008e895623171f7f500ec..8d8bccd2402cd1f213000b4597ba82bc67390833 100644
--- a/notifications/templatetags/notifications_tags.py
+++ b/notifications/templatetags/notifications_tags.py
@@ -1,35 +1,60 @@
 # -*- coding: utf-8 -*-
+from django.core.urlresolvers import reverse
 from django.template import Library
 from django.utils.html import format_html
 
 register = Library()
 
 
-@register.assignment_tag(takes_context=True)
-def notifications_unread(context):
-    user = user_context(context)
-    if not user:
-        return ''
-    return user.notifications.unread().count()
+@register.simple_tag(takes_context=True)
+def live_notify_badge(context, classes=''):
+    html = "<span class='live_notify_badge {classes}'>0</span>".format(classes=classes)
+    return format_html(html)
 
 
 @register.simple_tag(takes_context=True)
-def live_notify_badge(context, badge_class='live_notify_badge', classes=''):
+def live_notify_list(context):
     user = user_context(context)
     if not user:
         return ''
 
-    html = "<span class='{badge_class} {classes}' data-count='{unread}'>{unread}</span>".format(
-        badge_class=badge_class, unread=user.notifications.unread().count(),
-        classes=classes
-    )
-    return format_html(html)
+    html = '<div class="popover-template popover">'
+    html += '<div class="popover notifications" role="tooltip">'
+
+    # User default links
+    html += '<h6 class="header">Welcome {first_name} {last_name}</h6>'.format(
+        first_name=user.first_name, last_name=user.last_name)
+
+    if hasattr(user, 'contributor'):
+        html += '<a class="item" href="{url}">Personal Page</a>'.format(
+            url=reverse('scipost:personal_page'))
+
+    # User specific links
+    if user.has_perm('scipost.can_read_partner_page'):
+        html += '<a class="item" href="{url}">Partner Page</a>'.format(
+            url=reverse('partners:dashboard'))
+    if user.has_perm('scipost.can_view_timesheets'):
+        html += '<a class="item" href="{url}">Financial Administration</a>'.format(
+            url=reverse('finances:finance'))
+    if user.has_perm('scipost.can_view_all_funding_info'):
+        html += '<a class="item" href="{url}">Funders</a>'.format(
+            url=reverse('funders:funders'))
+    if user.has_perm('scipost.can_view_production'):
+        html += '<a class="item" href="{url}">Production</a>'.format(
+            url=reverse('production:production'))
+    if user.has_perm('scipost.can_view_pool'):
+        html += '<a class="item" href="{url}">Submission Pool</a>'.format(
+            url=reverse('submissions:pool'))
 
+    # Logout links
+    html += '<div class="divider"></div>'
+    html += '<a class="item" href="{url}">Logout</a>'.format(
+        url=reverse('scipost:logout'))
 
-@register.simple_tag
-def live_notify_list(list_class='live_notify_list', classes=''):
-    html = "<ul class='{list_class} {classes}'></ul>".format(list_class=list_class,
-                                                             classes=classes)
+    # Notifications
+    html += '<div class="divider"></div><h6 class="header">Inbox</h6>'
+    html += '<div class="live_notify_list"></div></div>'
+    html += '<div class="popover-body"></div></div>'
     return format_html(html)
 
 
diff --git a/notifications/urls.py b/notifications/urls.py
index 7fd8627126d17a50579d3d39bdd523c1841ab137..a80a7a6769ffa3f1c17e446232a00976a5840819 100644
--- a/notifications/urls.py
+++ b/notifications/urls.py
@@ -5,9 +5,7 @@ from . import views
 
 urlpatterns = [
     url(r'^redirect/(?P<slug>\d+)$', views.forward, name='forward'),
-    url(r'^mark-all-as-read/$', views.mark_all_as_read, name='mark_all_as_read'),
     url(r'^mark-toggle/(?P<slug>\d+)/$', views.mark_toggle, name='mark_toggle'),
-    url(r'^delete/(?P<slug>\d+)/$', views.delete, name='delete'),
     url(r'^api/unread_count/$', views.live_unread_notification_count,
         name='live_unread_notification_count'),
     url(r'^api/list/$', views.live_notification_list, name='live_unread_notification_list'),
diff --git a/notifications/views.py b/notifications/views.py
index bcdf7e3b03fc93946b0d254b3136d9c9b342fc5a..b36eaab4806253da91d5c18cfc1aa033983249b9 100644
--- a/notifications/views.py
+++ b/notifications/views.py
@@ -24,22 +24,6 @@ def forward(request, slug):
     return redirect(notification.target.get_absolute_url())
 
 
-@login_required
-@user_passes_test(is_test_user)
-def mark_all_as_read(request):
-    request.user.notifications.mark_all_as_read()
-
-    _next = request.GET.get('next')
-
-    if _next:
-        return redirect(_next)
-
-    if request.GET.get('json'):
-        return JsonResponse({'unread': 0})
-
-    return redirect('notifications:all')
-
-
 @login_required
 @user_passes_test(is_test_user)
 def mark_toggle(request, slug=None):
@@ -58,22 +42,6 @@ def mark_toggle(request, slug=None):
     return redirect('notifications:all')
 
 
-@login_required
-@user_passes_test(is_test_user)
-def delete(request, slug=None):
-    id = slug2id(slug)
-
-    notification = get_object_or_404(Notification, recipient=request.user, id=id)
-    notification.delete()
-
-    _next = request.GET.get('next')
-
-    if _next:
-        return redirect(_next)
-
-    return redirect('notifications:all')
-
-
 def live_unread_notification_count(request):
     if not request.user.is_authenticated():
         data = {'unread_count': 0}
@@ -101,6 +69,7 @@ def live_notification_list(request):
 
     for n in request.user.notifications.all()[:num_to_fetch]:
         struct = model_to_dict(n)
+        struct['unread'] = struct['pseudo_unread']
         struct['slug'] = id2slug(n.id)
         if n.actor:
             if isinstance(n.actor, User):
@@ -119,8 +88,11 @@ def live_notification_list(request):
         struct['timesince'] = n.timesince()
 
         list.append(struct)
-        if request.GET.get('mark_as_read'):
-            n.mark_as_read()
+
+    if request.GET.get('mark_as_read'):
+        # Mark all as read
+        request.user.notifications.mark_all_as_read()
+
     data = {
         'unread_count': request.user.notifications.unread().count(),
         'list': list
diff --git a/petitions/views.py b/petitions/views.py
index b702d80784f700c223fe6be5da4f6228a3955790..86c527ccc405f6270909f55622607d7cfff08f70 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 0375ee4b73bdb7eaac45f7e0d4536fa493246e74..a28c66442a8810b3d8123707b9754709246434db 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/production/views.py b/production/views.py
index 49bfa8926e18bd7ba4c338287cb27d08c1c6110d..cc7e0d4063e42b191d0b4074367e3d58ee4b259e 100644
--- a/production/views.py
+++ b/production/views.py
@@ -1,4 +1,3 @@
-import datetime
 import mimetypes
 
 from django.contrib import messages
diff --git a/scipost/admin.py b/scipost/admin.py
index 8cfaffcf2da4ef93af08a909d043e8164df872fe..3b8059b73e28851d0ebc3e071091ae2b846fc0b2 100644
--- a/scipost/admin.py
+++ b/scipost/admin.py
@@ -6,7 +6,6 @@ from django.contrib.auth.models import User, Permission
 
 from scipost.models import Contributor, Remark,\
                            DraftInvitation,\
-                           AffiliationObject,\
                            RegistrationInvitation,\
                            AuthorshipClaim, PrecookedEmail,\
                            EditorialCollege, EditorialCollegeFellowship, UnavailabilityPeriod
@@ -177,13 +176,6 @@ class PrecookedEmailAdmin(admin.ModelAdmin):
 admin.site.register(PrecookedEmail, PrecookedEmailAdmin)
 
 
-class AffiliationObjectAdmin(admin.ModelAdmin):
-    search_fields = ['country', 'institution', 'subunit']
-
-
-admin.site.register(AffiliationObject, AffiliationObjectAdmin)
-
-
 class EditorialCollegeAdmin(admin.ModelAdmin):
     search_fields = ['discipline', 'member']
 
@@ -195,6 +187,7 @@ def college_fellow_is_active(fellow):
     '''Check if fellow is currently active.'''
     return fellow.is_active()
 
+
 class EditorialCollegeFellowshipAdminForm(forms.ModelForm):
     contributor = forms.ModelChoiceField(
         queryset=Contributor.objects.order_by('user__last_name'))
diff --git a/scipost/forms.py b/scipost/forms.py
index 614c14f44fad3314c1e6333c434e8ad634e56d21..6a175bb129bf87ef00e2c342fcb4f324793724bc 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, Institute
 from common.forms import MonthYearWidget
 from partners.decorators import has_contact
 
@@ -115,16 +116,22 @@ class RegistrationForm(forms.Form):
             'password': self.cleaned_data['password'],
             'is_active': False
         })
+        institute, __ = Institute.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'],
             'personalwebpage': self.cleaned_data['personalwebpage'],
         })
+        affiliation, __ = Affiliation.objects.get_or_create(
+            contributor=contributor,
+            institute=institute,
+        )
 
         if contributor.activation_key == '':
             # Seems redundant?
@@ -259,37 +266,20 @@ class UpdateUserDataForm(forms.ModelForm):
 class UpdatePersonalDataForm(forms.ModelForm):
     class Meta:
         model = Contributor
-        fields = ['title', 'discipline', 'expertises', 'orcid_id', 'country_of_employment',
-                  'affiliation', 'address', 'personalwebpage'
-                  ]
-        widgets = {'country_of_employment': CountrySelectWidget()}
-
-    # 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)
+        fields = [
+            'title',
+            'discipline',
+            'expertises',
+            'orcid_id',
+            'address',
+            'personalwebpage'
+        ]
 
     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):
         """
@@ -479,12 +469,6 @@ class SearchForm(HayStackSearchForm):
     start = forms.DateField(widget=MonthYearWidget(), required=False)  # Month
     end = forms.DateField(widget=MonthYearWidget(end=True), required=False)  # Month
 
-    # def __init__(self, *args, **kwargs):
-    #     super().__init__(*args, **kwargs)
-    #     models = self.fields['models'].choices
-    #     models = filter(lambda x: x[0] != 'sphinxdoc.document', models)
-    #     self.fields['models'].choices = models
-
     def search(self):
         sqs = super().search()
 
diff --git a/scipost/management/commands/add_groups_and_permissions.py b/scipost/management/commands/add_groups_and_permissions.py
index f94c680845f9d5a85f29aa35bb9b238e1e9124f3..1b99ccf50734665e25491a3b6b0e8934057d33d0 100644
--- a/scipost/management/commands/add_groups_and_permissions.py
+++ b/scipost/management/commands/add_groups_and_permissions.py
@@ -262,6 +262,12 @@ class Command(BaseCommand):
             name='Can view timesheets',
             content_type=content_type)
 
+        # Affiliations administration
+        can_manage_affiliations, created = Permission.objects.get_or_create(
+            codename='can_manage_affiliations',
+            name='Can manage affiliations',
+            content_type=content_type)
+
         # Mailchimp
         can_manage_mailchimp, created = Permission.objects.get_or_create(
             codename='can_manage_mailchimp',
@@ -293,6 +299,7 @@ class Command(BaseCommand):
             can_manage_mailchimp,
             can_view_all_production_streams,
             can_promote_to_production_team,
+            can_manage_affiliations,
         ])
 
         FinancialAdmin.permissions.set([
diff --git a/scipost/migrations/0066_contributor__affiliation.py b/scipost/migrations/0066_contributor__affiliation.py
new file mode 100644
index 0000000000000000000000000000000000000000..345021dd99dabb24f7eaebd34da3c943d0aa9ee4
--- /dev/null
+++ b/scipost/migrations/0066_contributor__affiliation.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-11-01 19:28
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('affiliations', '0002_affiliation_acronym'),
+        ('scipost', '0065_authorshipclaim_publication'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='contributor',
+            name='_affiliation',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='affiliations.Institute'),
+        ),
+    ]
diff --git a/scipost/migrations/0067_auto_20171101_2132.py b/scipost/migrations/0067_auto_20171101_2132.py
new file mode 100644
index 0000000000000000000000000000000000000000..43e5ed004e74df1fcd53f6e1044a92cb8b98a43f
--- /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.Institute'),
+        ),
+    ]
diff --git a/scipost/migrations/0068_auto_20171101_2132.py b/scipost/migrations/0068_auto_20171101_2132.py
new file mode 100644
index 0000000000000000000000000000000000000000..f59223bee41208b98d73e363ccea8ab890db2be7
--- /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/migrations/0069_auto_20171102_0840.py b/scipost/migrations/0069_auto_20171102_0840.py
new file mode 100644
index 0000000000000000000000000000000000000000..1de2b91a9358bde40dc595dd50e8484e6f89841c
--- /dev/null
+++ b/scipost/migrations/0069_auto_20171102_0840.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-11-02 07:40
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('scipost', '0068_auto_20171101_2132'),
+    ]
+
+    operations = [
+        migrations.RenameField(
+            model_name='contributor',
+            old_name='affiliation',
+            new_name='old_affiliation_fk',
+        ),
+    ]
diff --git a/scipost/migrations/0070_remove_contributor_old_affiliation_fk.py b/scipost/migrations/0070_remove_contributor_old_affiliation_fk.py
new file mode 100644
index 0000000000000000000000000000000000000000..01a7bf2698a49cb00331d27fb633a29c7301fcdb
--- /dev/null
+++ b/scipost/migrations/0070_remove_contributor_old_affiliation_fk.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-11-02 07:55
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('affiliations', '0006_auto_20171102_0843'),
+        ('scipost', '0069_auto_20171102_0840'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='contributor',
+            name='old_affiliation_fk',
+        ),
+    ]
diff --git a/scipost/migrations/0071_auto_20171102_0858.py b/scipost/migrations/0071_auto_20171102_0858.py
new file mode 100644
index 0000000000000000000000000000000000000000..2719f788adab65209a7313825393ea9c855d160d
--- /dev/null
+++ b/scipost/migrations/0071_auto_20171102_0858.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-11-02 07:58
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('scipost', '0070_remove_contributor_old_affiliation_fk'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='contributor',
+            name='old_affiliation',
+        ),
+        migrations.RemoveField(
+            model_name='contributor',
+            name='old_country_of_employment',
+        ),
+    ]
diff --git a/scipost/migrations/0072_delete_affiliationobject.py b/scipost/migrations/0072_delete_affiliationobject.py
new file mode 100644
index 0000000000000000000000000000000000000000..3f82fd9db28aa1045add819d8df6332c999b344c
--- /dev/null
+++ b/scipost/migrations/0072_delete_affiliationobject.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-11-02 11:56
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('scipost', '0071_auto_20171102_0858'),
+    ]
+
+    operations = [
+        migrations.DeleteModel(
+            name='AffiliationObject',
+        ),
+    ]
diff --git a/scipost/models.py b/scipost/models.py
index 92d9f20429454017cf9634938617bd2ee781c849..4cd14cef37edb1e16bc8e44f6e1247cf3849a428 100644
--- a/scipost/models.py
+++ b/scipost/models.py
@@ -50,8 +50,6 @@ 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')
     address = models.CharField(max_length=1000, verbose_name="address",
                                blank=True)
     personalwebpage = models.URLField(verbose_name='personal web page',
@@ -225,7 +223,7 @@ class RegistrationInvitation(models.Model):
 
 
 class CitationNotification(models.Model):
-    contributor = models.ForeignKey(Contributor, on_delete=models.CASCADE)
+    contributor = models.ForeignKey('scipost.Contributor', on_delete=models.CASCADE)
     cited_in_submission = models.ForeignKey('submissions.Submission',
                                             on_delete=models.CASCADE,
                                             blank=True, null=True)
@@ -288,16 +286,6 @@ class PrecookedEmail(models.Model):
         return self.email_subject
 
 
-#######################
-# Affiliation Objects #
-#######################
-
-class AffiliationObject(models.Model):
-    country = CountryField()
-    institution = models.CharField(max_length=128)
-    subunit = models.CharField(max_length=128)
-
-
 ######################
 # Static info models #
 ######################
diff --git a/scipost/static/scipost/assets/config/preconfig.scss b/scipost/static/scipost/assets/config/preconfig.scss
index 7ddb10372dd8fed7fd054288653e0a21ca3e1024..67b14aebb660f6e677e7fb8e820338a89786ea26 100644
--- a/scipost/static/scipost/assets/config/preconfig.scss
+++ b/scipost/static/scipost/assets/config/preconfig.scss
@@ -65,6 +65,7 @@ $card-shadow-color: #ccc;
 $card-grey-border-bottom-color: #d0d1d5;
 $card-grey-border-color: #e5e6e9 #dfe0e4 $card-grey-border-bottom-color;
 
+
 // breadcrumb
 //
 $breadcrumb-bg: #f9f9f9;
@@ -108,6 +109,7 @@ $font-family-base:       $font-family-sans-serif;
 
 $font-size-base: 0.8rem;
 $font-size-sm: 0.75rem;
+$font-size-lg: 1.0rem;
 
 $h1-font-size: 1.8em;
 $h2-font-size: 1.5em;
@@ -132,7 +134,7 @@ $nav-link-padding-y: 0.4rem;
 $input-border-radius: 0;
 $btn-border-radius: $base-border-radius;
 $btn-border-radius-sm: $base-border-radius;
-$btn-border-radius-lg: 0;
+$btn-border-radius-lg: $base-border-radius;
 
 
 // Block quote
diff --git a/scipost/static/scipost/assets/css/_breadcrumb.scss b/scipost/static/scipost/assets/css/_breadcrumb.scss
index cbb8d8e6be656517b8849ca6ee3e78ea67bd6646..4e3915ebcfbd361517c401b2fa6e389afcc6d538 100644
--- a/scipost/static/scipost/assets/css/_breadcrumb.scss
+++ b/scipost/static/scipost/assets/css/_breadcrumb.scss
@@ -9,6 +9,7 @@
     .container {
         width: 100%;
         display: flex;
+        flex-wrap: nowrap;
     }
 
     .breadcrumb-item {
diff --git a/scipost/static/scipost/assets/css/_form.scss b/scipost/static/scipost/assets/css/_form.scss
index 25694c4652bcedaceed0f28c56a540a518ed0b14..bfd15ced62c30b8de7bbcdd4edcc652ad5452517 100644
--- a/scipost/static/scipost/assets/css/_form.scss
+++ b/scipost/static/scipost/assets/css/_form.scss
@@ -121,3 +121,18 @@ input[type="file"] {
         }
     }
 }
+
+.formset-group {
+    .formset-form {
+        border-bottom: 1px solid #ddd;
+        padding: 1rem 0 0.5rem;
+
+        &.form-1 {
+            padding-top: 0;
+        }
+
+        &.to_be_deleted {
+            opacity: 0.2;
+        }
+    }
+}
diff --git a/scipost/static/scipost/assets/css/_homepage.scss b/scipost/static/scipost/assets/css/_homepage.scss
index 16da6e2fd48b0948f08dec3ed1fe31c34d22b41c..2940939bb54bee8eaa06fa6383e895e80e042b68 100644
--- a/scipost/static/scipost/assets/css/_homepage.scss
+++ b/scipost/static/scipost/assets/css/_homepage.scss
@@ -24,8 +24,6 @@
         max-height: 100%;
         height: 100%;
         -webkit-overflow-scrolling: touch;
-        padding-left: 1rem;
-        padding-right: 1rem;
     }
 
     .main-panel {
@@ -44,6 +42,8 @@
         border-left: 1px solid #ddd;
         position: relative;
         padding-top: 0.25rem;
+        padding-left: 1rem;
+        padding-right: 1rem;
         margin-top: 2rem;
 
         .card {
@@ -70,10 +70,15 @@
             padding-right: 0;
         }
     }
+}
+
 
-    @media (min-width: 768px) {
+@media (min-width: 768px) {
+    .has-sidebar {
         .main-panel {
             width: calc(100% - 350px);
+            padding-left: 1rem;
+            padding-right: 1rem;
         }
         .sidebar {
             width: 350px;
@@ -94,8 +99,10 @@
             }
         }
     }
+}
 
-    @media (min-width: 1280px) {
+@media (min-width: 1280px) {
+    .has-sidebar {
         .main-panel {
             width: calc(100% - 400px);
         }
diff --git a/scipost/static/scipost/assets/css/_nav.scss b/scipost/static/scipost/assets/css/_nav.scss
index 9551795390051441481d3a22f222af65cf3f28a7..a5e59525ef3c8b4ee262156b3af58e73b546fc0a 100644
--- a/scipost/static/scipost/assets/css/_nav.scss
+++ b/scipost/static/scipost/assets/css/_nav.scss
@@ -68,6 +68,10 @@ nav.main-nav {
     }
 }
 
+.nav {
+    flex-wrap: nowrap;
+}
+
 nav.submenu {
     margin-top: -1.5rem;
     margin-bottom: 1rem;
@@ -76,9 +80,15 @@ nav.submenu {
     border-radius: 0;
     border-top: 1px solid #fff;
     border-bottom: 1px solid #ddd;
+    display: flex;
+    flex-wrap: nowrap;
+    flex-direction: row;
+    overflow-x: scroll;
 
     .item {
         padding: 0 0.5rem;
+        white-space: nowrap;
+        display: block;
         border-right: 2px solid #fff;
     }
     .item:first-child,
diff --git a/scipost/static/scipost/assets/css/_navbar.scss b/scipost/static/scipost/assets/css/_navbar.scss
index a1ffa8e02c7f39a7152f6c0ff12fa3e724f444cd..211d612f9d49f42ea5ea0de34305cc0f567830f8 100644
--- a/scipost/static/scipost/assets/css/_navbar.scss
+++ b/scipost/static/scipost/assets/css/_navbar.scss
@@ -4,6 +4,9 @@
  */
 .navbar {
     margin-bottom: 1.5rem;
+    display: flex;
+    flex-wrap: nowrap;
+    overflow-x: scroll;
 
     &.main-nav {
         border-bottom: 1px solid #ddd;
@@ -25,10 +28,6 @@
         }
     }
 
-    .navbar-nav {
-        flex-direction: row;
-    }
-
     .active > .nav-link {
         border-color: $scipost-darkblue;
         box-shadow: 0 1px 0 0 #ccc;
@@ -38,8 +37,26 @@
         background-color: rgba(255, 255, 255, 0.6);
     }
 
-    .nav-item {
-        margin-right: 0.5rem;
+    .navbar-nav {
+        display: flex;
+        flex-direction: row;
+    }
+}
+
+@media (min-width: 768px) {
+    .navbar {
+
+        .navbar-nav {
+            margin-right: auto;
+        }
+
+        .nav-item {
+            margin-right: 0.5rem;
+        }
+
+        [data-toggle="collapse"] {
+            display: none;
+        }
     }
 }
 
@@ -77,6 +94,10 @@
 .navbar-counter {
     position: relative;
 
+    .nav-link.notifications_container:hover {
+        background-color: #fff;
+    }
+
     a.dropdown-toggle {
         min-width: 45px;
     }
@@ -92,47 +113,59 @@
             content: "\f0f3";
         }
     }
+}
 
-    .badge {
-        vertical-align: top;
-        margin-left: -15px;
-        margin-top: -5px;
-        height: 16px;
-        min-width: 16px;
-        line-height: 10px;
-        display: none;
-        padding: 0.25em;
-        border-radius: 99px;
-        border: 1px solid #f9f9f9;
-    }
+.live_notify_badge {
+    vertical-align: top;
+    margin-left: -15px;
+    margin-top: -5px;
+    height: 16px;
+    min-width: 16px;
+    line-height: 10px;
+    display: none;
+    padding: 0.25em;
+    border-radius: 99px;
+    border: 1px solid #f9f9f9;
 }
 
 .notifications_container {
-    color: $scipost-lightestblue;
+    .badge_link {
+        color: $scipost-lightestblue;
+
+        // &::after {
+        //     content: none;
+        // }
+    }
 
     .user {
         color: $scipost-darkblue;
     }
 
-    .fa {
-        font-size: 150%;
+    .fa-inbox {
         vertical-align: bottom;
         margin: 0 0.25rem;
+        min-width: 17px;
+
+        &:before {
+            font-size: 150%;
+        }
+    }
+
+    &.show .fa-inbox {
+        color: $scipost-darkblue;
     }
 
     &.positive_count {
-        color: $scipost-orange;
+        .badge_link {
+            color: $scipost-orange;
+        }
 
         .user {
             color: $scipost-darkblue;
         }
 
-        .badge {
+        .live_notify_badge {
             display: inline-block;
         }
     }
-
-    &::after {
-        content: none;
-    }
 }
diff --git a/scipost/static/scipost/assets/css/_popover.scss b/scipost/static/scipost/assets/css/_notifications.scss
similarity index 52%
rename from scipost/static/scipost/assets/css/_popover.scss
rename to scipost/static/scipost/assets/css/_notifications.scss
index 5ef240e8b434fe68d4c9fd09f3c5052a61ad69f6..8670e2f38dbbc8047210e2cf016255b074506a33 100644
--- a/scipost/static/scipost/assets/css/_popover.scss
+++ b/scipost/static/scipost/assets/css/_notifications.scss
@@ -1,43 +1,28 @@
-.popover {
-    width: 500px;
-    box-shadow: #ccc 0px 1px 2px 1px;
-}
 
 .navbar-counter .nav-link:hover {
     background-color: $white;
 }
 
-.popover.bs-popover-bottom,
-.popover.bs-popover-auto[x-placement^="bottom"] {
-    .arrow::before {
-        border-bottom-color: rgb(221, 221, 221);
-    }
-
-    .arrow::after {
-        border-bottom-color: #f7f7f7;
-    }
+.popover-template {
+    display: none;
 }
 
 .notifications {
-    border: 0;
-    border-radius: 0;
-
-    .popover-header {
-        font-size: 100%;
-        padding: 0.3rem 1rem;
-        font-weight: 600;
-        text-transform: uppercase;
+    padding: 0;
+    min-width: 500px;
 
-        a {
-            color: $scipost-darkblue;
-        }
+    .header {
+        padding: 1rem 1rem 0.5rem 1rem;
+        background-color: #f9f9f9;
     }
 
-    .popover-body {
-        padding: 0;
+    li.item {
+        &[href]:hover {
+            background-color: #f9f9f9;
+        }
     }
 
-    &.popover .list-group-item {
+    .item {
         padding: 0.4rem 1rem;
         border-radius: 0;
         border-top: 1px solid #fff;
@@ -47,18 +32,42 @@
         justify-content: space-between;
         display: flex;
 
+        &[href] {
+            cursor: pointer;
+        }
+
+        &.active,
+        &.active[href]:hover {
+            background-color: $scipost-lightestblue;
+        }
+
+        > div {
+            white-space: normal;
+        }
+
         &:last-child {
             border-bottom: 0;
         }
     }
 
+    a.item,
+    .item a {
+        color: $scipost-lightblue;
+
+        &:hover {
+            text-decoration: underline;
+        }
+    }
+
     .actions {
-        display: block;
+        // display: none;
         opacity: 0.0;
         transition: opacity 0.1s;
         width: 20px;
         height: 100%;
-        padding-left: 0.25rem;
+        padding-left: 1rem;
+        padding-right: 0.5rem;
+        padding-bottom: 0.1rem;
 
         .fa[data-toggle="tooltip"] {
             font-size: 1em;
@@ -75,11 +84,7 @@
         }
     }
 
-    .list-group-item:hover .actions {
+    .item:hover .actions {
         opacity: 1.0;
     }
 }
-
-.popover-header {
-    font-size: initial;
-}
diff --git a/scipost/static/scipost/assets/css/_tables.scss b/scipost/static/scipost/assets/css/_tables.scss
index 851ef97467fe1cc81ff3c3a867511b57278a32ba..8873b93c394e15c1ef958f4509818a1f193e5c59 100644
--- a/scipost/static/scipost/assets/css/_tables.scss
+++ b/scipost/static/scipost/assets/css/_tables.scss
@@ -21,3 +21,10 @@ table.submission td {
         min-width: 150px;
     }
 }
+
+table.contributor-info {
+    td {
+        padding: 0 0.5rem;
+        vertical-align: top;
+    }
+}
diff --git a/scipost/static/scipost/assets/css/_type.scss b/scipost/static/scipost/assets/css/_type.scss
index 056fe6dcb5bb8c21f77aee7affb8a68a0cfec105..fbe387a334e6b23a3e336d1cedcb3930c08a2171 100644
--- a/scipost/static/scipost/assets/css/_type.scss
+++ b/scipost/static/scipost/assets/css/_type.scss
@@ -31,7 +31,7 @@ h4 {
     line-height: normal;
 }
 
-h5, h6 {
+h5 {
     font-weight: 300;
 }
 
diff --git a/scipost/static/scipost/assets/css/style.scss b/scipost/static/scipost/assets/css/style.scss
index be68d319863f3b5d962c823aca10301be2f28b73..5b2d4702fc9744c9f3ab74936d10a968850e6049 100644
--- a/scipost/static/scipost/assets/css/style.scss
+++ b/scipost/static/scipost/assets/css/style.scss
@@ -30,9 +30,9 @@
 @import "modal";
 @import "navbar";
 @import "nav";
+@import "notifications";
 @import "page_header";
 @import "pool";
-@import "popover";
 @import "tables";
 @import "tooltip";
 @import "type";
diff --git a/scipost/static/scipost/assets/js/notifications.js b/scipost/static/scipost/assets/js/notifications.js
index 49fbe7f24e3f96a1d8a2c82cd9191f42a3f1cfd6..a20d3a146c7ff98bf3ac7a5642dce137ecd2dd2b 100644
--- a/scipost/static/scipost/assets/js/notifications.js
+++ b/scipost/static/scipost/assets/js/notifications.js
@@ -1,195 +1,172 @@
-var notify_container_class = "notifications_container";
-var notify_badge_class = "live_notify_badge";
-var notify_menu_class = "live_notify_list";
-var notify_api_url_count = "/notifications/api/unread_count/";
-var notify_api_url_list = "/notifications/api/list/";
-var notify_api_url_toggle_read = "/notifications/mark-toggle/";
-var notify_api_url_mark_all_read = "/notifications/mark-all-as-read/";
-var notify_fetch_count = "5";
-var notify_refresh_period = 60000;
-var consecutive_misfires = 0;
-var registered_functions = [fill_notification_badge];
-
-
-function initiate_popover(reinitiate) {
-    if(typeof reinitiate == 'undefined') {
-        reinitiate = false;
+function fetch_api_data(callback, url, args) {
+    if (!url) {
+        var url = notify_api_url_count;
     }
 
-    var notification_template = '<div class="popover notifications" role="tooltip"><div class="arrow"></div><p class="popover-header"></p><div class="popover-body"></div></div>';
-
-    function get_notifications_title() {
-        return 'My inbox';
+    if (callback) {
+        //only fetch data if a function is setup
+        var r = new XMLHttpRequest();
+        r.addEventListener('readystatechange', function(event){
+            if (this.readyState === 4){
+                if (this.status === 200){
+                    consecutive_misfires = 0;
+                    var data = JSON.parse(r.responseText);
+                    return callback(data, args);
+                } else {
+                    consecutive_misfires++;
+                }
+            }
+        })
+        r.open("GET", url + '?max=5', true);
+        r.send();
     }
+}
 
-    function get_notifications() {
-        var _str = '<ul id="notification-list" class="update_notifications list-group"><div class="w-100 text-center py-4"><i class="fa fa-circle-o-notch fa-2x fa-spin fa-fw"></i><span class="sr-only">Loading...</span></div></ul>';
-        get_notification_list();
-        return _str;
-    }
 
-    $('.popover [data-toggle="tooltip"]').tooltip('dispose')
-    $('#notifications_badge').popover('dispose').popover({
-        animation: false,
-        trigger: 'click',
-        title: get_notifications_title,
-        template: notification_template,
-        content: get_notifications,
-        container: 'body',
-        offset: '0, 9px',
-        placement: "bottom",
-        html: true
-    }).on('inserted.bs.popover', function() {
-        // Bloody js
-        setTimeout(function() {
-            $('.popover [data-toggle="tooltip"]').tooltip({
-                animation: false,
-                delay: {"show": 500, "hide": 100},
-                fallbackPlacement: 'clockwise',
-                placement: 'bottom'
-            });
-            $('.popover .actions a').on('click', function() {
-                mark_toggle(this)
-            })
-            $('.popover a.mark_all_read').on('click', function() {
-                mark_all_read(this)
-            })
-        }, 1000);
-    });
-    if (reinitiate) {
-        $('#notifications_badge').popover('show')
-    }
-}
+function update_counter_callback(data, args) {
+    var counter = data['unread_count'];
+    var el = $(args['element']);
 
-function request_reinitiate(url) {
-    var r = new XMLHttpRequest();
-    r.addEventListener('readystatechange', function(event){
-        if (this.readyState == 4 && this.status == 200) {
-            fetch_api_data()
-            initiate_popover(reinitiate=true)
-        }
-    })
-    r.open("GET", url, true);
-    r.send();
-}
+    if (typeof counter == 'undefined') {
+        counter = 0;
+    }
 
-function mark_all_read(el) {
-    request_reinitiate(notify_api_url_mark_all_read + '?json=1')
+    el.html(counter);
+    if (counter > 0) {
+        el.parents('.notifications_container').addClass('positive_count')
+    } else {
+        el.parents('.notifications_container').removeClass('positive_count')
+    }
 }
 
-function mark_toggle(el) {
-    request_reinitiate(notify_api_url_toggle_read + $(el).data('slug') + '/?json=1')
-}
+function update_list_callback(data, args) {
+    var items = data['list'];
+    var el = $(args['element']);
 
+    var messages = items.map(function (item) {
+        // Notification content
+        var message = '',
+            link = '';
 
-function fill_notification_badge(data) {
-    var badges = document.getElementsByClassName(notify_badge_class);
-    var container = $('.' + notify_container_class);
-    if (badges) {
-        for(var i = 0; i < badges.length; i++){
-            badges[i].innerHTML = data.unread_count;
-            if (data.unread_count > 0) {
-                container.addClass('positive_count');
+        if(typeof item.actor !== 'undefined'){
+            message += '<strong>' + item.actor + '</strong>';
+        }
+        if(typeof item.verb !== 'undefined'){
+            message += " " + item.verb;
+        }
+        if(typeof item.target !== 'undefined'){
+            if(typeof item.forward_link !== 'undefined') {
+                link = item.forward_link;
+                message += " <a href='" + item.forward_link + "'>" + item.target + "</a>";
             } else {
-                container.removeClass('positive_count');
+                message += " " + item.target;
+            }
+        }
+        if(typeof item.timesince !== 'undefined'){
+            message += "<br><small>";
+            if(typeof item.forward_link !== 'undefined') {
+                message += " <a href='" + item.forward_link + "'>Direct link</a> &middot; ";
             }
+            message += "<span class='text-muted'>" + item.timesince + " ago</span></small>";
         }
+
+        // Notification actions
+        if(item.unread) {
+            var mark_toggle = '<a href="javascript:;" class="mark-toggle" data-slug="' + item.slug + '"><i class="fa fa-circle" data-toggle="tooltip" data-placement="auto" title="Mark as read" aria-hidden="true"></i></a>';
+        } else {
+            var mark_toggle = '<a href="javascript:;" class="mark-toggle" data-slug="' + item.slug + '"><i class="fa fa-circle-o" data-toggle="tooltip" data-placement="auto" title="Mark as unread" aria-hidden="true"></i></a>';
+        }
+
+        if(typeof item.forward_link !== 'undefined') {
+            mark_toggle += "<br><a href='" + item.forward_link + "' data-toggle='tooltip' data-placement='auto' title='Go to item'><i class='fa fa-share' aria-hidden='true'></i></a>";
+        }
+
+        // Complete list html
+        if(link !== '') {
+            return '<li href="' + link + '" class="item ' + (item.unread ? ' active' : '') + '"><div>' + message + '</div><div class="actions">' + mark_toggle + '</div></li>';
+        } else {
+            return '<li class="item ' + (item.unread ? ' active' : '') + '"><div>' + message + '</div><div class="actions">' + mark_toggle + '</div></li>';
+        }
+
+    }).join('');
+
+    if (messages == '') {
+        messages = '<li class="item px-5"><em>You have no new notifications</em></li>'
     }
+
+    // Fill DOM
+    el.find('.live_notify_list').html(messages).parents('body').trigger('refresh_notify_list');
 }
 
-function get_notification_list() {
-    fetch_api_data(notify_api_url_list, true, function(data) {
+function update_mark_callback(data, args) {
+    var el = $(args['element']);
+    $(el).parents('.item').toggleClass('active');
+    trigger_badge();
+}
 
-        var messages = data.list.map(function (item) {
-            var message = "<div>";
-            if(typeof item.actor !== 'undefined'){
-                message += '<strong>' + item.actor + '</strong>';
-            }
-            if(typeof item.verb !== 'undefined'){
-                message += " " + item.verb;
-            }
-            if(typeof item.target !== 'undefined'){
-                if(typeof item.forward_link !== 'undefined') {
-                    message += " <a href='" + item.forward_link + "'>" + item.target + "</a>";
-                } else {
-                    message += " " + item.target;
-                }
-            }
-            if(typeof item.timesince !== 'undefined'){
-                message += "<br><small class='text-muted'>" + item.timesince + " ago</small>";
-            }
-            message += "</div>";
 
-            if(item.unread) {
-                var mark_as_read = '<div class="actions"><a href="javascript:;" data-slug="' + item.slug + '"><i class="fa fa-circle" data-toggle="tooltip" data-placement="auto" title="Mark as read" aria-hidden="true"></i></a></div>';
-            } else {
-                var mark_as_read = '<div class="actions"><a href="javascript:;" data-slug="' + item.slug + '"><i class="fa fa-circle-o" data-toggle="tooltip" data-placement="auto" title="Mark as unread" aria-hidden="true"></i></a></div>';
-            }
-            return '<li class="list-group-item ' + (item.unread ? ' active' : '') + '">' + message + mark_as_read + '</li>';
-        }).join('');
+function update_counter(el) {
+    fetch_api_data(update_counter_callback, "/notifications/api/unread_count/", {'element': el});
+}
 
-        if (messages == '') {
-            messages = '<div class="text-center px-2 py-3"><i class="fa fa-star-o fa-2x" aria-hidden="true"></i><h3>You have no new notifications</h3></div>'
-        }
+function mark_toggle(el) {
+    var url = "/notifications/mark-toggle/" + $(el).data('slug') + "?json=1";
+    fetch_api_data(update_mark_callback, url, {'element': el});
+}
 
-        document.getElementById('notification-list').innerHTML = messages;
+function update_list(el) {
+    fetch_api_data(update_list_callback, "/notifications/api/list/?mark_as_read=1", {'element': el});
+}
+
+function trigger_badge() {
+    $('.live_notify_badge').trigger('notification_count_updated');
+}
+// Update Badge count every minute
+var badge_timer = setInterval(trigger_badge, 60000);
+
+function initiate_popover() {
+    var template = $('.notifications_container .popover-template').html();
+    $('.notifications_container a[data-toggle="popover"]').popover({
+        trigger: 'focus',
+        template: template,
+        placement: 'bottom',
+        title: 'empty-on-purpose'
+    })
+    .on('inserted.bs.popover', function() {
+        $('body').trigger('notification_open_list');
     });
 }
 
-function fetch_api_data(url, once, callback) {
-    if (!url) {
-        var url = notify_api_url_count;
-    }
-    if (!once) {
-        var once = false;
-    }
+$(function(){
+    $('body').on('notification_open_list', function() {
+        update_list(this);
+    })
 
-    if (registered_functions.length > 0) {
-        //only fetch data if a function is setup
-        var r = new XMLHttpRequest();
-        r.addEventListener('readystatechange', function(event){
-            if (this.readyState === 4){
-                if (this.status === 200){
-                    consecutive_misfires = 0;
-                    var data = JSON.parse(r.responseText);
-                    registered_functions.forEach(function (func) { func(data); });
-                    if (callback) {
-                        return callback(data);
-                    }
-                }else{
-                    consecutive_misfires++;
-                }
-            }
-        })
-        r.open("GET", url + '?max=' + notify_fetch_count, true);
-        r.send();
-    }
-    var timer = null;
-    if (!once) {
-        if (consecutive_misfires < 10 && !once) {
-            timer = setTimeout(fetch_api_data, notify_refresh_period);
-        } else {
-            var badges = document.getElementsByClassName(notify_badge_class);
-            if (badges) {
-                for (var i=0; i < badges.length; i++){
-                    badges[i].innerHTML = "!";
-                    badges[i].title = "Connection lost!"
-                }
-            }
-        }
-    }
+    $('.live_notify_badge').on('notification_count_updated', function() {
+        update_counter(this);
+    }).trigger('notification_count_updated');
 
-    return stop;
-    function stop() {
-        if (timer) {
-            clearTimeout(timer);
-            timer = 0;
-        }
-    }
-}
 
-setTimeout(fetch_api_data, 1000);
+    $('body').on('refresh_notify_list', function() {
+        // Bloody js
+        var list = $('.live_notify_list');
+        list.find('li.item').on('click', function(e) {
+            e.stopPropagation();
+        })
+        .filter('[href]').on('click', function(e) {
+            window.location.href = $(this).attr('href')
+        });
+        list.find('[data-toggle="tooltip"]').tooltip({
+            animation: false,
+            delay: {"show": 500, "hide": 100},
+            fallbackPlacement: 'clockwise',
+            placement: 'bottom'
+        });
+        list.find('.actions a.mark-toggle').on('click', function(e) {
+            e.stopPropagation();
+            mark_toggle(this);
+        });
+    });
 
-$(function(){
     initiate_popover();
 });
diff --git a/scipost/static/scipost/formset.js b/scipost/static/scipost/formset.js
new file mode 100644
index 0000000000000000000000000000000000000000..e944b74a779fecef708f2b55528da23b79f17139
--- /dev/null
+++ b/scipost/static/scipost/formset.js
@@ -0,0 +1,231 @@
+/**
+ * Copyright (c) 2009, Stanislaus Madueke
+ * All rights reserved.
+ *
+ * Licensed under the New BSD License
+ * See: http://www.opensource.org/licenses/bsd-license.php
+ *
+ *
+ * Modified version specific for SciPost.org
+ */
+;(function($) {
+    $.fn.formset = function(opts)
+    {
+        var options = $.extend({}, $.fn.formset.defaults, opts),
+            flatExtraClasses = options.extraClasses.join(' '),
+            totalForms = $('#id_' + options.prefix + '-TOTAL_FORMS'),
+            maxForms = $('#id_' + options.prefix + '-MAX_NUM_FORMS'),
+            minForms = $('#id_' + options.prefix + '-MIN_NUM_FORMS'),
+            childElementSelector = 'input,select,textarea,label,div',
+            $$ = $(this),
+
+            applyExtraClasses = function(row, ndx) {
+                if (options.extraClasses) {
+                    row.removeClass(flatExtraClasses);
+                    row.addClass(options.extraClasses[ndx % options.extraClasses.length]);
+                }
+            },
+
+            updateElementIndex = function(elem, prefix, ndx) {
+                var idRegex = new RegExp(prefix + '-(\\d+|__prefix__)-'),
+                    replacement = prefix + '-' + ndx + '-';
+                if (elem.attr("for")) elem.attr("for", elem.attr("for").replace(idRegex, replacement));
+                if (elem.attr('id')) elem.attr('id', elem.attr('id').replace(idRegex, replacement));
+                if (elem.attr('name')) elem.attr('name', elem.attr('name').replace(idRegex, replacement));
+            },
+
+            hasChildElements = function(row) {
+                return row.find(childElementSelector).length > 0;
+            },
+
+            showAddButton = function() {
+                return maxForms.length == 0 ||   // For Django versions pre 1.2
+                    (maxForms.val() == '' || (maxForms.val() - totalForms.val() > 0));
+            },
+
+            /**
+            * Indicates whether delete link(s) can be displayed - when total forms > min forms
+            */
+            showDeleteLinks = function() {
+                return minForms.length == 0 ||   // For Django versions pre 1.7
+                    (minForms.val() == '' || (totalForms.val() - minForms.val() > 0));
+            },
+
+            insertDeleteLink = function(row) {
+                var delCssSelector = $.trim(options.deleteCssClass).replace(/\s+/g, '.'),
+                    addCssSelector = $.trim(options.addCssClass).replace(/\s+/g, '.');
+
+                if (row.is('TR')) {
+                    // If the forms are laid out in table rows, insert
+                    // the remove button into the last table cell:
+                    row.children(':last').append('<a class="' + options.deleteCssClass +'" href="javascript:void(0)">' + options.deleteText + ' <i class="fa fa-trash" aria-hidden="true"></i></a>');
+                } else if (row.is('UL') || row.is('OL')) {
+                    // If they're laid out as an ordered/unordered list,
+                    // insert an <li> after the last list item:
+                    row.append('<li><a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText +' <i class="fa fa-trash" aria-hidden="true"></i></a></li>');
+                } else {
+                    // Otherwise, just insert the remove button as the
+                    // last child element of the form's container:
+                    row.append('<a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText +' <i class="fa fa-trash" aria-hidden="true"></i></a>');
+                }
+                // Check if we're under the minimum number of forms - not to display delete link at rendering
+                if (!showDeleteLinks()){
+                    row.find('a.' + delCssSelector).hide();
+                }
+
+                row.find('a.' + delCssSelector).click(function() {
+                    var row = $(this).parents('.' + options.formCssClass),
+                        del = row.find('input:hidden[id $= "-DELETE"]'),
+                        buttonRow = row.siblings("a." + addCssSelector + ', .' + options.formCssClass + '-add'),
+                        forms;
+                    if (del.length) {
+                        // We're dealing with an inline formset.
+                        // Rather than remove this form from the DOM, we'll mark it as deleted
+                        // and hide it, then let Django handle the deleting:
+                        del.val('on');
+                        row.addClass('to_be_deleted');
+                        forms = $('.' + options.formCssClass).not(':hidden');
+                    } else {
+                        row.remove();
+                        // Update the TOTAL_FORMS count:
+                        forms = $('.' + options.formCssClass).not('.formset-custom-template');
+                        totalForms.val(forms.length);
+                    }
+                    for (var i=0, formCount=forms.length; i<formCount; i++) {
+                        // Apply `extraClasses` to form rows so they're nicely alternating:
+                        applyExtraClasses(forms.eq(i), i);
+                        if (!del.length) {
+                            // Also update names and IDs for all child controls (if this isn't
+                            // a delete-able inline formset) so they remain in sequence:
+                            forms.eq(i).find(childElementSelector).each(function() {
+                                updateElementIndex($(this), options.prefix, i);
+                            });
+                        }
+                    }
+                    // Check if we've reached the minimum number of forms - hide all delete link(s)
+                    if (!showDeleteLinks()){
+                        $('a.' + delCssSelector).each(function(){$(this).hide();});
+                    }
+                    // Check if we need to show the add button:
+                    if (buttonRow.is(':hidden') && showAddButton()) buttonRow.show();
+                    // If a post-delete callback was provided, call it with the deleted form:
+                    if (options.removed) options.removed(row);
+                    return false;
+                });
+            };
+
+        $$.each(function(i) {
+            var row = $(this),
+                del = row.find('input:checkbox[id $= "-DELETE"]');
+
+            if (del.length) {
+                // If you specify "can_delete = True" when creating an inline formset,
+                // Django adds a checkbox to each form in the formset.
+                // Replace the default checkbox with a hidden field:
+                if (del.is(':checked')) {
+                    // If an inline formset containing deleted forms fails validation, make sure
+                    // we keep the forms hidden (thanks for the bug report and suggested fix Mike)
+                    row.prepend('<input type="hidden" name="' + del.attr('name') +'" id="' + del.attr('id') +'" value="on" />');
+                    row.addClass('to_be_deleted');
+                } else {
+                    row.prepend('<input type="hidden" name="' + del.attr('name') +'" id="' + del.attr('id') +'" />');
+                }
+                // Remove the old Bootstap row of the form
+                del.parents('.form-group').remove();
+            }
+            if (hasChildElements(row)) {
+                row.addClass(options.formCssClass);
+                if (row.is(':visible')) {
+                    insertDeleteLink(row);
+                    applyExtraClasses(row, i);
+                }
+            }
+        });
+
+        if ($$.length) {
+            var hideAddButton = !showAddButton(),
+                addButton, template;
+            if (options.formTemplate) {
+                // If a form template was specified, we'll clone it to generate new form instances:
+                template = (options.formTemplate instanceof $) ? options.formTemplate : $(options.formTemplate);
+                template.removeAttr('id').addClass(options.formCssClass + ' formset-custom-template');
+                template.find(childElementSelector).each(function() {
+                    updateElementIndex($(this), options.prefix, '__prefix__');
+                });
+                insertDeleteLink(template);
+            } else {
+                // Otherwise, use the last form in the formset; this works much better if you've got
+                // extra (>= 1) forms (thnaks to justhamade for pointing this out):
+                template = $('.' + options.formCssClass + ':last').clone(true).removeAttr('id');
+                template.find('input:hidden[id $= "-DELETE"]').remove();
+                // Clear all cloned fields, except those the user wants to keep (thanks to brunogola for the suggestion):
+                template.find(childElementSelector).not(options.keepFieldValues).each(function() {
+                    var elem = $(this);
+                    // If this is a checkbox or radiobutton, uncheck it.
+                    // This fixes Issue 1, reported by Wilson.Andrew.J:
+                    if (elem.is('input:checkbox') || elem.is('input:radio')) {
+                        elem.attr('checked', false);
+                    } else {
+                        elem.val('');
+                    }
+                });
+            }
+            // FIXME: Perhaps using $.data would be a better idea?
+            options.formTemplate = template;
+
+            if ($$.is('TR')) {
+                // If forms are laid out as table rows, insert the
+                // "add" button in a new table row:
+                var numCols = $$.eq(0).children().length,   // This is a bit of an assumption :|
+                    buttonRow = $('<tr><td colspan="' + numCols + '"><a class="' + options.addCssClass + '" href="javascript:void(0)">' + options.addText + ' <i class="fa fa-plus" aria-hidden="true"></i></a></tr>')
+                                .addClass(options.formCssClass + '-add');
+                $$.parent().append(buttonRow);
+                if (hideAddButton) buttonRow.hide();
+                addButton = buttonRow.find('a');
+            } else {
+                // Otherwise, insert it immediately after the last form:
+                $$.filter(':last').after('<div class="form-group row pt-2"><div class="col-12"><a class="' + options.addCssClass + '" href="javascript:void(0)">' + options.addText + ' <i class="fa fa-plus" aria-hidden="true"></i></a></div></div>');
+                addButton = $$.filter(':last').next();
+                if (hideAddButton) addButton.hide();
+            }
+            addButton.click(function() {
+                var formCount = parseInt(totalForms.val()),
+                    row = options.formTemplate.clone(true).removeClass('formset-custom-template'),
+                    buttonRow = $($(this).parents('tr.' + options.formCssClass + '-add').get(0) || this),
+                    delCssSelector = $.trim(options.deleteCssClass).replace(/\s+/g, '.');
+                applyExtraClasses(row, formCount);
+                row.insertBefore(buttonRow).show();
+                row.find(childElementSelector).each(function() {
+                    updateElementIndex($(this), options.prefix, formCount);
+                });
+                totalForms.val(formCount + 1);
+                // Check if we're above the minimum allowed number of forms -> show all delete link(s)
+                if (showDeleteLinks()){
+                    $('a.' + delCssSelector).each(function(){$(this).show();});
+                }
+                // Check if we've exceeded the maximum allowed number of forms:
+                if (!showAddButton()) buttonRow.hide();
+                // If a post-add callback was supplied, call it with the added form:
+                if (options.added) options.added(row);
+                return false;
+            });
+        }
+
+        return $$;
+    };
+
+    /* Setup plugin defaults */
+    $.fn.formset.defaults = {
+        prefix: 'form',                  // The form prefix for your django formset
+        formTemplate: null,              // The jQuery selection cloned to generate new form instances
+        addText: 'add another',          // Text for the add link
+        deleteText: 'remove',            // Text for the delete link
+        addCssClass: 'add-row',          // CSS class applied to the add link
+        deleteCssClass: 'delete-row',    // CSS class applied to the delete link
+        formCssClass: 'dynamic-form',    // CSS class applied to each form in a formset
+        extraClasses: [],                // Additional CSS classes, which will be applied to each form in turn
+        keepFieldValues: '',             // jQuery selector for fields whose values should be kept when the form is cloned
+        added: null,                     // Function called each time a new form is added
+        removed: null                    // Function called each time a form is deleted
+    };
+})(jQuery);
diff --git a/scipost/templates/scipost/_private_info_as_table.html b/scipost/templates/scipost/_private_info_as_table.html
index 04d525681719b0e696584c1584c9baeaaa12d04b..9eb68274e47459fb4068a8e92476bd75733ef994 100644
--- a/scipost/templates/scipost/_private_info_as_table.html
+++ b/scipost/templates/scipost/_private_info_as_table.html
@@ -1,13 +1,21 @@
-<table>
-    <tr><td>Title: </td><td>&nbsp;</td><td>{{ contributor.get_title_display }}</td></tr>
-    <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>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>
-    <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>
+<table class="contributor-info">
+    <tr><td>Title:</td><td>{{ contributor.get_title_display }}</td></tr>
+    <tr><td>First name:</td><td>{{ contributor.user.first_name }}</td></tr>
+    <tr><td>Last name: </td><td>{{ contributor.user.last_name }}</td></tr>
+    <tr><td>Email: </td><td>{{ contributor.user.email }}</td></tr>
+    <tr><td>ORCID id: </td><td>{{ contributor.orcid_id }}</td></tr>
+    <tr>
+        <td>Affiliation(s):</td>
+        <td>
+            {% for affiliation in contributor.affiliations.active %}
+                {% if not forloop.first %}
+                    <br>
+                {% endif %}
+                {{ affiliation.institute }}
+            {% endfor %}
+        </td>
+    </tr>
+    <tr><td>Address: </td><td>{{ contributor.address }}</td></tr>
+    <tr><td>Personal web page: </td><td>{{ contributor.personalwebpage }}</td></tr>
+    <tr><td>Accept SciPost emails: </td><td>{{ contributor.accepts_SciPost_emails }}</td></tr>
 </table>
diff --git a/scipost/templates/scipost/_public_info_as_table.html b/scipost/templates/scipost/_public_info_as_table.html
index e41461ee178b3bdd9e01b9b0ba92804abf6b04ca..6e80fb2c51545076559b06b46310b145ab17cae6 100644
--- a/scipost/templates/scipost/_public_info_as_table.html
+++ b/scipost/templates/scipost/_public_info_as_table.html
@@ -1,9 +1,18 @@
-<table>
-    <tr><td>Title: </td><td>&nbsp;</td><td>{{ contributor.get_title_display }}</td></tr>
-    <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>Personal web page: </td><td>&nbsp;</td><td>{{ contributor.personalwebpage|default:'-' }}</td></tr>
+<table class="contributor-info">
+    <tr><td>Title: </td><td>{{ contributor.get_title_display }}</td></tr>
+    <tr><td>First name: </td><td>{{ contributor.user.first_name }}</td></tr>
+    <tr><td>Last name: </td><td>{{ contributor.user.last_name }}</td></tr>
+    <tr><td>ORCID id: </td><td>{{ contributor.orcid_id|default:'-' }}</td></tr>
+    <tr>
+        <td>Affiliation(s):</td>
+        <td>
+            {% for affiliation in contributor.affiliations.active %}
+                {% if not forloop.first %}
+                    <br>
+                {% endif %}
+                {{ affiliation.institute }}
+            {% endfor %}
+        </td>
+    </tr>
+    <tr><td>Personal web page: </td><td>{{ contributor.personalwebpage|default:'-' }}</td></tr>
 </table>
diff --git a/scipost/templates/scipost/contributor_info.html b/scipost/templates/scipost/contributor_info.html
index 7ee2c8988a5f5d791b97c4ff50740fd03d0968cf..26423a9502582d3579c1ea106e3e1e9752c701fa 100644
--- a/scipost/templates/scipost/contributor_info.html
+++ b/scipost/templates/scipost/contributor_info.html
@@ -4,22 +4,13 @@
 
 {% block content %}
 
-<div class="row">
-    <div class="col-12">
-        <div class="card card-grey">
-            <div class="card-body">
-                <h1 class="card-title mb-0">Contributor info</h1>
-                <h3 class="card-subtitle text-muted">{{contributor.get_formal_display}}</h2>
-            </div>
-        </div>
-    </div>
-</div>
+<h1 class="highlight mb-4">Contributor info: {{ contributor.get_formal_display }}</h1>
 
 {% include "scipost/_public_info_as_table.html" with contributor=contributor %}
 
 <br>
 {% if contributor_publications %}
-    {# <hr>#}
+
     <div class="row">
         <div class="col-12">
             <h2 class="highlight">Publications <small><a href="javascript:;" class="ml-2" data-toggle="toggle" data-target="#mypublicationslist">View/hide publications</a></small></h2>
diff --git a/scipost/templates/scipost/navbar.html b/scipost/templates/scipost/navbar.html
index 93ccffea2081b2623fabd849169e3cf6546e36a8..6c0bea234470a3afe91bf080a4bf276f6978e84c 100644
--- a/scipost/templates/scipost/navbar.html
+++ b/scipost/templates/scipost/navbar.html
@@ -3,74 +3,74 @@
 {% load scipost_extras %}
 
 
-<nav class="navbar navbar-scroll navbar-light main-nav navbar-expand-lg">
-    <div class="navbar-scroll-inner">
-        <ul class="navbar-nav mr-auto">
-          <li class="nav-item{% if request.path == '/' %} active{% endif %}">
-            <a href="{% url 'scipost:index' %}" class="nav-link">Home</a>
-          </li>
-          <li class="nav-item{% if '/journals/' in request.path %} active{% endif %}">
-            <a href="{% url 'journals:journals' %}" class="nav-link">Journals</a>
-          </li>
-          <li class="nav-item{% if '/submissions/' in request.path %} active{% endif %}">
-            <a class="nav-link" href="{% url 'submissions:submissions' %}">Submissions</a>
-          </li>
-          <li class="nav-item{% if '/commentaries/' in request.path %} active{% endif %}">
-            <a class="nav-link" href="{% url 'commentaries:commentaries' %}">Commentaries</a>
+<nav class="navbar navbar-light main-nav navbar-expand-lg">
+    <ul id="menu-navbar" class="navbar-nav">
+      <li class="nav-item{% if request.path == '/' %} active{% endif %}">
+        <a href="{% url 'scipost:index' %}" class="nav-link">Home</a>
+      </li>
+      <li class="nav-item{% if '/journals/' in request.path %} active{% endif %}">
+        <a href="{% url 'journals:journals' %}" class="nav-link">Journals</a>
+      </li>
+      <li class="nav-item{% if '/submissions/' in request.path %} active{% endif %}">
+        <a class="nav-link" href="{% url 'submissions:submissions' %}">Submissions</a>
+      </li>
+      <li class="nav-item{% if '/commentaries/' in request.path %} active{% endif %}">
+        <a class="nav-link" href="{% url 'commentaries:commentaries' %}">Commentaries</a>
+      </li>
+      <li class="nav-item{% if '/theses/' in request.path %} active{% endif %}">
+        <a class="nav-link" href="{% url 'theses:theses' %}">Theses</a>
+      </li>
+      <li class="nav-item{% if '/about' in request.path %} active{% endif %}">
+        <a class="nav-link" href="{% url 'scipost:about' %}">About SciPost</a>
+      </li>
+
+
+
+      {% if user.is_authenticated %}
+        {% if request.user|is_in_group:'Testers' %}
+          <li class="nav-item highlighted navbar-counter">
+            <div class="nav-link notifications_container">
+                <a href="javascript:;" class="d-inline-block ml-1 badge_link" id="notifications_badge" data-toggle="popover">
+                    <span class="user">{% if user.last_name %}{% if user.contributor %}{{ user.contributor.get_title_display }} {% endif %}{{ user.first_name }} {{ user.last_name }}{% else %}{{ user.username }}{% endif %}</span>
+                    <i class="fa fa-inbox" aria-hidden="true"></i>
+                    {% live_notify_badge classes="badge badge-pill badge-primary" %}
+                </a>
+                {% live_notify_list %}
+            </div>
+
           </li>
-          <li class="nav-item{% if '/theses/' in request.path %} active{% endif %}">
-            <a class="nav-link" href="{% url 'theses:theses' %}">Theses</a>
+      {% else %}
+          <li class="nav-item highlighted">
+            <span class="nav-link">Logged in as {{ user.username }}</span>
           </li>
-          <li class="nav-item{% if '/about' in request.path %} active{% endif %}">
-            <a class="nav-link" href="{% url 'scipost:about' %}">About SciPost</a>
+      {% endif %}
+          <li class="nav-item">
+              <a class="nav-link" href="{% url 'scipost:logout' %}">Logout</a>
           </li>
-
-          {% if user.is_authenticated %}
-            {% if request.user|is_in_group:'Testers' %}
-              <li class="nav-item highlighted dropdown navbar-counter">
-                <div class="nav-link">
-                    <a href="javascript:;" class="d-inline-block ml-1 dropdown-toggle notifications_container" id="notifications_badge">
-                        <span class="user">{% if user.last_name %}{% if user.contributor %}{{ user.contributor.get_title_display }} {% endif %}{{ user.first_name }} {{ user.last_name }}{% else %}{{ user.username }}{% endif %}</span>
-                        <i class="fa fa-inbox" aria-hidden="true"></i>
-                        {% live_notify_badge classes="badge badge-pill badge-primary" %}
-                    </a>
-                    {% live_notify_list classes="update_notifications d-none" %}
-                </div>
-
-              </li>
-          {% else %}
-              <li class="nav-item highlighted">
-                <span class="nav-link">Logged in as {{ user.username }}</span>
+          {% if perms.scipost.can_view_production %}
+              <li class="nav-item{% if '/production' in request.path %} active{% endif %}">
+                <a class="nav-link" href="{% url 'production:production' %}">Production</a>
               </li>
           {% endif %}
-              <li class="nav-item">
-                  <a class="nav-link" href="{% url 'scipost:logout' %}">Logout</a>
+          {% if user.contributor %}
+              <li class="nav-item{% if '/personal_page' in request.path %} active{% endif %}">
+                <a class="nav-link" href="{% url 'scipost:personal_page' %}">Personal Page</a>
               </li>
-              {% if perms.scipost.can_view_production %}
-                  <li class="nav-item{% if '/production' in request.path %} active{% endif %}">
-                    <a class="nav-link" href="{% url 'production:production' %}">Production</a>
-                  </li>
-              {% endif %}
-              {% if user.contributor %}
-                  <li class="nav-item{% if '/personal_page' in request.path %} active{% endif %}">
-                    <a class="nav-link" href="{% url 'scipost:personal_page' %}">Personal Page</a>
-                  </li>
-              {% endif %}
-              {% if user.partner_contact %}
-                  <li class="nav-item{% if '/partners/dashboard' in request.path %} active{% endif %}">
-                    <a class="nav-link" href="{% url 'partners:dashboard' %}">Partner Page</a>
-                  </li>
-              {% endif %}
-          {% else %}
-              <li class="nav-item{% if request.path == '/login/' %} active{% endif %}">
-                <a class="nav-link" href="{% url 'scipost:login' %}">Login</a>
+          {% endif %}
+          {% if user.partner_contact %}
+              <li class="nav-item{% if '/partners/dashboard' in request.path %} active{% endif %}">
+                <a class="nav-link" href="{% url 'partners:dashboard' %}">Partner Page</a>
               </li>
           {% endif %}
+      {% else %}
+          <li class="nav-item{% if request.path == '/login/' %} active{% endif %}">
+            <a class="nav-link" href="{% url 'scipost:login' %}">Login</a>
+          </li>
+      {% endif %}
 
-        </ul>
-        <form action="{% url 'scipost:search' %}" method="get" class="form-inline search-nav-form">
-            <input class="form-control" id="id_q" maxlength="100" name="q" type="text" required="required" value="{{search_query|default:''}}">
-            <input class="btn btn-secondary" type="submit" value="Search">
-        </form>
-    </div>
+    </ul>
+    <form action="{% url 'scipost:search' %}" method="get" class="form-inline search-nav-form">
+        <input class="form-control" id="id_q" maxlength="100" name="q" type="text" required="required" value="{{search_query|default:''}}">
+        <input class="btn btn-secondary" type="submit" value="Search">
+    </form>
 </nav>
diff --git a/scipost/templates/scipost/personal_page.html b/scipost/templates/scipost/personal_page.html
index 19522be6222256824b92c3703e6088d23d5ef881..90218f977a81b0820918adf894cdf10e7f7e6835 100644
--- a/scipost/templates/scipost/personal_page.html
+++ b/scipost/templates/scipost/personal_page.html
@@ -314,6 +314,11 @@
                                     <li><a href="{% url 'mailing_lists:overview' %}">Manage mailing lists</a></li>
                                 {% endif %}
                             </ul>
+
+                            <h3>SciPost Administation</h3>
+                            <ul>
+                                <li><a href="{% url 'affiliations:institutes' %}">Manage Institutes database</a></li>
+                            </ul>
                         {% endif %}
 
                         {% if perms.scipost.can_view_timesheets %}
@@ -354,10 +359,10 @@
                               <li><a href="{% url 'submissions:pool' %}">Submissions Pool</a></li>
                               <li><a href="{% url 'submissions:treated_submissions_list' %}">Fully treated Submissions</a>{% if nr_treated_submissions_without_pdf %} ({{nr_treated_submissions_without_pdf}} unfinished){% endif %}</li>
                               <li><a href="{% url 'journals:harvest_citedby_list' %}">Harvest citedby data</a></li>
-                              <li><a href="{% url 'journals:manage_metadata' %}">Manage Publication metadata</a></li>
-                              <li><a href="{% url 'journals:manage_report_metadata' %}">Manage Report metadata</a></li>
                               <li><a href="{% url 'journals:manage_comment_metadata' %}">Manage Comment metadata</a></li>
                               <li><a href="{% url 'colleges:fellowships' %}">Manage Fellowships</a></li>
+                              <li><a href="{% url 'journals:manage_metadata' %}">Manage Publication metadata</a></li>
+                              <li><a href="{% url 'journals:manage_report_metadata' %}">Manage Report metadata</a></li>
                               <li><a href="{% url 'proceedings:proceedings' %}">Manage Proceedings Issues</a></li>
                             </ul>
                         {% endif %}
diff --git a/scipost/templates/scipost/update_personal_data.html b/scipost/templates/scipost/update_personal_data.html
index ab693489e3a48571ac07aba497bc6e9053c71445..42c0a5b383643ecb8169aea8ceb7ab6fa7dc51be 100644
--- a/scipost/templates/scipost/update_personal_data.html
+++ b/scipost/templates/scipost/update_personal_data.html
@@ -15,75 +15,28 @@
     <script>
 
     $(document).ready(function(){
+        $('select#id_discipline').on('change', function() {
+            var selection = $(this).val();
+            $("ul[id^='id_expertises_']").closest("li").hide();
 
-      switch ($('select#id_discipline').val()) {
-        case "physics":
-          $("#id_expertises_0").closest("li").show();
-          $("#id_expertises_1").closest("li").hide();
-          $("#id_expertises_2").closest("li").hide();
-          $("#id_expertises_3").closest("li").hide();
-          break;
-        case "astrophysics":
-          $("#id_expertises_0").closest("li").hide();
-          $("#id_expertises_1").closest("li").show();
-          $("#id_expertises_2").closest("li").hide();
-          $("#id_expertises_3").closest("li").hide();
-          break;
-        case "mathematics":
-          $("#id_expertises_0").closest("li").hide();
-          $("#id_expertises_1").closest("li").hide();
-          $("#id_expertises_2").closest("li").show();
-          $("#id_expertises_3").closest("li").hide();
-          break;
-        case "computerscience":
-          $("#id_expertises_0").closest("li").hide();
-          $("#id_expertises_1").closest("li").hide();
-          $("#id_expertises_2").closest("li").hide();
-          $("#id_expertises_3").closest("li").show();
-          break;
-        default:
-          $("#id_expertises_0").closest("li").show();
-          $("#id_expertises_1").closest("li").show();
-          $("#id_expertises_2").closest("li").show();
-          $("#id_expertises_3").closest("li").show();
-          break;
-      }
-
-      $('select#id_discipline').on('change', function() {
-        var selection = $(this).val();
-        switch(selection){
-        case "physics":
-          $("#id_expertises_0").closest("li").show();
-          $("#id_expertises_1").closest("li").hide();
-          $("#id_expertises_2").closest("li").hide();
-          $("#id_expertises_3").closest("li").hide();
-          break;
-        case "astrophysics":
-          $("#id_expertises_0").closest("li").hide();
-          $("#id_expertises_1").closest("li").show();
-          $("#id_expertises_2").closest("li").hide();
-          $("#id_expertises_3").closest("li").hide();
-          break;
-        case "mathematics":
-          $("#id_expertises_0").closest("li").hide();
-          $("#id_expertises_1").closest("li").hide();
-          $("#id_expertises_2").closest("li").show();
-          $("#id_expertises_3").closest("li").hide();
-          break;
-        case "computerscience":
-          $("#id_expertises_0").closest("li").hide();
-          $("#id_expertises_1").closest("li").hide();
-          $("#id_expertises_2").closest("li").hide();
-          $("#id_expertises_3").closest("li").show();
-          break;
-        default:
-          $("#id_expertises_0").closest("li").show();
-          $("#id_expertises_1").closest("li").show();
-          $("#id_expertises_2").closest("li").show();
-          $("#id_expertises_3").closest("li").show();
-          break;
-        }
-      });
+            switch(selection){
+                case "physics":
+                    $("#id_expertises_0").closest("li").show();
+                break;
+                case "astrophysics":
+                    $("#id_expertises_1").closest("li").show();
+                break;
+                case "mathematics":
+                    $("#id_expertises_2").closest("li").show();
+                break;
+                case "computerscience":
+                    $("#id_expertises_3").closest("li").show();
+                break;
+                default:
+                    $("ul[id^='id_expertises_']").closest("li").show();
+                break;
+            }
+        }).trigger('change');
 
     });
 
@@ -91,18 +44,43 @@
 {% endif %}
 
 
-<div class="row justify-content-center">
-    <div class="col-lg-10">
-        <h1 class="highlight">Update your personal data</h1>
-        <form action="{% url 'scipost:update_personal_data' %}" method="post">
-            {% csrf_token %}
-            {{user_form|bootstrap}}
+
+<form action="{% url 'scipost:update_personal_data' %}" method="post">
+    {% csrf_token %}
+    <div class="row justify-content-center">
+        <div class="col-lg-6">
+            <h1 class="mb-3">Update your personal data</h1>
+            {{ user_form|bootstrap }}
             {% if cont_form %}
-            	{{cont_form|bootstrap}}
+            	{{ cont_form|bootstrap }}
             {% endif %}
-            <input type="submit" class="btn btn-primary" value="Update" />
-        </form>
+        </div>
+        {% if institute_formset %}
+            <div class="col-lg-6">
+                <div id="institutes" class="formset-group">
+                    <h1 class="mb-3">Your Institutes</h1>
+                    {{ institute_formset.media }}
+                    {{ institute_formset|bootstrap }}
+                </div>
+                <div class="formset-form form-empty" style="display: none;">
+                    {{ institute_formset.empty_form|bootstrap }}
+                </div>
+            </div>
+        {% endif %}
+    </div>
+
+    <div class="text-center">
+        <input type="submit" class="btn btn-primary btn-lg px-3" value="Save changes" />
     </div>
-</div>
+</form>
 
+<script type="text/javascript">
+    $(function() {
+        $('form #institutes > .formset-form').formset({
+            addText: 'add new Institute',
+            deleteText: 'remove Institute',
+            formTemplate: 'form .form-empty',
+        })
+    })
+</script>
 {% endblock content %}
diff --git a/scipost/templates/tags/bootstrap/formset.html b/scipost/templates/tags/bootstrap/formset.html
index eb5722f819c06ea17cfc9bf104411aa73c30b2e6..0f323b43a26307842d431fa10cbc0ad1f8f25e32 100644
--- a/scipost/templates/tags/bootstrap/formset.html
+++ b/scipost/templates/tags/bootstrap/formset.html
@@ -1,11 +1,13 @@
 {{ formset.management_form }}
 
 {% for form in formset %}
-  {% if classes.label == 'sr-only' %}
-      <div class="form-inline">
-      {% include "tags/bootstrap/form.html" with form=form %}
-      </div>
-  {%else%}
-      {% include "tags/bootstrap/form.html" with form=form %}
-  {% endif %}
+    <div class="formset-form form-{{ forloop.counter }}">
+      {% if classes.label == 'sr-only' %}
+          <div class="form-inline">
+            {% include "tags/bootstrap/form.html" with form=form formset=formset %}
+          </div>
+      {% else %}
+          {% include "tags/bootstrap/form.html" with form=form formset=formset %}
+      {% endif %}
+    </div>
 {% endfor %}
diff --git a/scipost/views.py b/scipost/views.py
index 9b222806a95855071b71a348e0b44614e44c1699..e122018ec1a75591f46e303ff89214c56b984525 100644
--- a/scipost/views.py
+++ b/scipost/views.py
@@ -35,6 +35,7 @@ from .forms import AuthenticationForm, DraftInvitationForm, UnavailabilityPeriod
                    EmailGroupMembersForm, EmailParticularForm, SendPrecookedEmailForm
 from .utils import Utils, EMAIL_FOOTER, SCIPOST_SUMMARY_FOOTER, SCIPOST_SUMMARY_FOOTER_HTML
 
+from affiliations.forms import AffiliationsFormset
 from commentaries.models import Commentary
 from comments.models import Comment
 from journals.models import Publication, Journal
@@ -880,7 +881,7 @@ def _update_personal_data_user_only(request):
     if user_form.is_valid():
         user_form.save()
         messages.success(request, 'Your personal data has been updated.')
-        return redirect(reverse('partners:dashboard'))
+        return redirect(reverse('scipost:update_personal_data'))
     context = {
         'user_form': user_form
     }
@@ -891,19 +892,26 @@ def _update_personal_data_contributor(request):
     contributor = Contributor.objects.get(user=request.user)
     user_form = UpdateUserDataForm(request.POST or None, instance=request.user)
     cont_form = UpdatePersonalDataForm(request.POST or None, instance=contributor)
-    if user_form.is_valid() and cont_form.is_valid():
+    institute_formset = AffiliationsFormset(request.POST or None, contributor=contributor)
+    if user_form.is_valid() and cont_form.is_valid() and institute_formset.is_valid():
         user_form.save()
         cont_form.save()
         cont_form.sync_lists()
+        institute_formset.save()
         if 'orcid_id' in cont_form.changed_data:
             cont_form.propagate_orcid()
         messages.success(request, 'Your personal data has been updated.')
-        return redirect(reverse('scipost:personal_page'))
+        return redirect(reverse('scipost:update_personal_data'))
     else:
         user_form = UpdateUserDataForm(instance=contributor.user)
         cont_form = UpdatePersonalDataForm(instance=contributor)
-    return render(request, 'scipost/update_personal_data.html',
-                  {'user_form': user_form, 'cont_form': cont_form})
+
+    context = {
+        'user_form': user_form,
+        'cont_form': cont_form,
+        'institute_formset': institute_formset,
+    }
+    return render(request, 'scipost/update_personal_data.html', context)
 
 
 @login_required
diff --git a/submissions/templates/submissions/_report_tex_template.html b/submissions/templates/submissions/_report_tex_template.html
index ceb16ce3c1644e379d0b935a45f29b7034851fc6..264dbc3a367163d594c6c1cc759e570a9325fb8b 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 %}