diff --git a/scipost_django/colleges/admin.py b/scipost_django/colleges/admin.py
index 00d684cb3131be55695c0e98d5ee838e6a3b7028..8ea8e1e36c7abb595d86ca6889a7f5bb5ec5d3ff 100644
--- a/scipost_django/colleges/admin.py
+++ b/scipost_django/colleges/admin.py
@@ -4,7 +4,13 @@ __license__ = "AGPL v3"
 
 from django.contrib import admin
 
-from .models import College, Fellowship, PotentialFellowship, PotentialFellowshipEvent
+from .models import (
+    College, Fellowship,
+    PotentialFellowship, PotentialFellowshipEvent,
+    FellowshipNomination, FellowshipNominationEvent,
+    FellowshipNominationVotingRound, FellowshipNominationVote,
+    FellowshipNominationDecision, FellowshipInvitation
+)
 
 
 admin.site.register(College)
@@ -57,3 +63,64 @@ class PotentialFellowshipAdmin(admin.ModelAdmin):
     ]
 
 admin.site.register(PotentialFellowship, PotentialFellowshipAdmin)
+
+
+class FellowshipNominationEventInline(admin.TabularInline):
+    model = FellowshipNominationEvent
+    extra = 0
+
+class FellowshipNominationVotingRoundInline(admin.TabularInline):
+    model = FellowshipNominationVotingRound
+    extra = 0
+
+
+class FellowshipNominationDecisionInline(admin.TabularInline):
+    model = FellowshipNominationDecision
+    extra = 0
+
+
+class FellowshipInvitationInline(admin.TabularInline):
+    model = FellowshipInvitation
+    extra = 0
+
+
+class FellowshipNominationAdmin(admin.ModelAdmin):
+    inlines = [
+        FellowshipNominationEventInline,
+        FellowshipNominationVotingRoundInline,
+        FellowshipNominationDecisionInline,
+        FellowshipInvitationInline,
+    ]
+    list_display = [
+        'college',
+        'profile',
+        'nominated_on'
+    ]
+    search_fields = [
+        'college',
+        'profile'
+    ]
+    autocomplete_fields = [
+        'profile',
+        'nominated_by',
+        'fellowship'
+    ]
+
+admin.site.register(FellowshipNomination, FellowshipNominationAdmin)
+
+
+class FellowshipNominationVoteInline(admin.TabularInline):
+    model = FellowshipNominationVote
+    extra = 0
+
+class FellowshipNominationVotingRoundAdmin(admin.ModelAdmin):
+    model = FellowshipNominationVotingRound
+    inlines = [
+        FellowshipNominationVoteInline,
+    ]
+    autocomplete_fields = [
+        'nomination',
+        'eligible_to_vote',
+    ]
+
+admin.site.register(FellowshipNominationVotingRound, FellowshipNominationVotingRoundAdmin)
diff --git a/scipost_django/colleges/forms.py b/scipost_django/colleges/forms.py
index 06f13cf2ba413f44503d15aa3dd1b410952e46ac..a55fd7cf9ac44c072c88cefbe79607d3b6cec6d7 100644
--- a/scipost_django/colleges/forms.py
+++ b/scipost_django/colleges/forms.py
@@ -8,19 +8,27 @@ from django import forms
 from django.db.models import Q
 
 from crispy_forms.helper import FormHelper
-from crispy_forms.layout import Layout, Field
+from crispy_forms.layout import Layout, Div, Field, Hidden, ButtonHolder, Submit
 from crispy_bootstrap5.bootstrap5 import FloatingField
 from dal import autocomplete
 
+from ontology.models import Specialty
 from proceedings.models import Proceedings
 from profiles.models import Profile
 from submissions.models import Submission
 from scipost.forms import RequestFormMixin
 from scipost.models import Contributor
 
-from .models import Fellowship, PotentialFellowship, PotentialFellowshipEvent
-from .constants import POTENTIAL_FELLOWSHIP_IDENTIFIED, POTENTIAL_FELLOWSHIP_NOMINATED,\
+from .models import (
+    College, Fellowship,
+    PotentialFellowship, PotentialFellowshipEvent,
+    FellowshipNomination,
+)
+from .constants import (
+    POTENTIAL_FELLOWSHIP_IDENTIFIED, POTENTIAL_FELLOWSHIP_NOMINATED,
     POTENTIAL_FELLOWSHIP_EVENT_DEFINED, POTENTIAL_FELLOWSHIP_EVENT_NOMINATED
+)
+from .utils import check_profile_eligibility_for_fellowship
 
 
 class FellowshipSelectForm(forms.Form):
@@ -34,7 +42,8 @@ class FellowshipSelectForm(forms.Form):
 class FellowshipDynSelForm(forms.Form):
     q = forms.CharField(max_length=32, label='Search (by name)')
     action_url_name = forms.CharField()
-    action_url_base_kwargs = forms.JSONField()
+    action_url_base_kwargs = forms.JSONField(required=False)
+    action_target_element_id = forms.CharField()
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
@@ -43,6 +52,7 @@ class FellowshipDynSelForm(forms.Form):
             FloatingField('q', autocomplete='off'),
             Field('action_url_name', type='hidden'),
             Field('action_url_base_kwargs', type='hidden'),
+            Field('action_target_element_id', type='hidden'),
         )
 
     def search_results(self):
@@ -270,3 +280,113 @@ class PotentialFellowshipEventForm(forms.ModelForm):
         self.fields['comments'].widget.attrs.update({
             'placeholder': 'NOTA BENE: careful, will be visible to all who have voting rights'
             })
+
+
+###############
+# Nominations #
+###############
+
+class FellowshipNominationForm(forms.ModelForm):
+
+    class Meta:
+        model = FellowshipNomination
+        fields = [
+            'nominated_by',    # hidden
+            'college', 'nominator_comments'  # visible
+        ]
+
+    def __init__(self, *args, **kwargs):
+        self.profile = kwargs.pop('profile')
+        super().__init__(*args, **kwargs)
+        self.fields['college'].queryset = College.objects.filter(
+            acad_field=self.profile.acad_field)
+        self.fields['college'].empty_label = None
+        self.fields['nominator_comments'].label = False
+        self.fields['nominator_comments'].widget.attrs['rows'] = 4
+        self.fields['nominator_comments'].widget.attrs[
+            'placeholder'] = 'Optional comments and/or recommendations'
+        self.helper = FormHelper()
+        self.helper.layout = Layout(
+            Field('profile_id', type='hidden'),
+            Field('nominated_by', type='hidden'),
+            Div(
+                Div(Field('nominator_comments'), css_class='col-lg-8'),
+                Div(
+                    FloatingField('college'),
+                    ButtonHolder(Submit('submit', 'Nominate', css_class='btn btn-success float-end')),
+                    css_class="col-lg-4"
+                ),
+                css_class='row pt-1'
+            ),
+        )
+
+    def clean(self):
+        data = super().clean()
+        failed_eligibility_criteria = check_profile_eligibility_for_fellowship(self.profile)
+        if failed_eligibility_criteria:
+            for critetion in failed_eligibility_criteria:
+                self.add_error(None, criterion)
+        if data['college'].acad_field != self.profile.acad_field:
+            self.add_error(
+                'college',
+                'Mismatch between college.acad_field and profile.acad_field.'
+            )
+        return data
+
+    def save(self):
+        nomination = super().save(commit=False)
+        nomination.profile = self.profile
+        nomination.save()
+        return nomination
+
+
+class FellowshipNominationSearchForm(forms.Form):
+    """Filter a FellowshipNomination queryset using basic search fields."""
+
+    college = forms.ModelChoiceField(
+        queryset=College.objects.all(),
+        required=False
+    )
+    specialty = forms.ModelChoiceField(
+        queryset=Specialty.objects.all(),
+        # widget=autocomplete.ModelSelect2(
+        #     url='/ontology/specialty-autocomplete',
+        #     attrs={'data-html': True}
+        # ),
+        label='Specialty',
+        required=False
+    )
+    name = forms.CharField(
+        max_length=128,
+        required=False
+    )
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.helper = FormHelper()
+        self.helper.layout = Layout(
+            Div(
+                Div(FloatingField('college'), css_class='col-lg-6'),
+                Div(FloatingField('specialty'), css_class='col-lg-6'),
+                css_class='row'
+            ),
+            Div(
+                Div(FloatingField('name', autocomplete='off'), css_class='col-lg-6'),
+                css_class='row'
+            ),
+        )
+
+    def search_results(self):
+        if self.cleaned_data.get('name'):
+            nominations = FellowshipNomination.objects.filter(
+                Q(profile__last_name__icontains=self.cleaned_data.get('name')) |
+                Q(profile__first_name__icontains=self.cleaned_data.get('name')))
+        else:
+            nominations = FellowshipNomination.objects.all()
+        if self.cleaned_data.get('college'):
+            nominations = nominations.filter(
+                college=self.cleaned_data.get('college'))
+        if self.cleaned_data.get('specialty'):
+            nominations = nominations.filter(
+                profile__specialties__in=[self.cleaned_data.get('specialty'),])
+        return nominations
diff --git a/scipost_django/colleges/managers.py b/scipost_django/colleges/managers.py
index 752aa1df188960b1677ad724c89010757788a876..dd0a5582005728b1bbfb47384a2f47f0aa6812c3 100644
--- a/scipost_django/colleges/managers.py
+++ b/scipost_django/colleges/managers.py
@@ -98,3 +98,14 @@ class PotentialFellowshipQuerySet(models.QuerySet):
             Q(in_agreement__in=[contributor]) |
             Q(in_abstain__in=[contributor]) |
             Q(in_disagreement__in=[contributor]))
+
+
+class FellowshipNominationVotingRoundQuerySet(models.QuerySet):
+
+    def ongoing(self):
+        now = timezone.now()
+        return self.filter(voting_opens__lte=now, voting_deadline__gte=now)
+
+    def closed(self):
+        now = timezone.now()
+        return self.filter(voting_deadline__lte=now)
diff --git a/scipost_django/colleges/migrations/0031_fellowshipinvitation_fellowshipnomination_fellowshipnominationdecision_fellowshipnominationevent_fel.py b/scipost_django/colleges/migrations/0031_fellowshipinvitation_fellowshipnomination_fellowshipnominationdecision_fellowshipnominationevent_fel.py
new file mode 100644
index 0000000000000000000000000000000000000000..2e2f529a463f6f708d3fac331bc0a238af31d7e0
--- /dev/null
+++ b/scipost_django/colleges/migrations/0031_fellowshipinvitation_fellowshipnomination_fellowshipnominationdecision_fellowshipnominationevent_fel.py
@@ -0,0 +1,101 @@
+# Generated by Django 3.2.5 on 2022-01-28 15:35
+
+from django.db import migrations, models
+import django.db.models.deletion
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('profiles', '0035_alter_profile_title'),
+        ('scipost', '0040_auto_20210310_2026'),
+        ('colleges', '0030_auto_20210326_1502'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='FellowshipNomination',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('nominated_on', models.DateTimeField(default=django.utils.timezone.now)),
+                ('college', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='nominations', to='colleges.college')),
+                ('fellowship', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='nomination', to='colleges.fellowship')),
+                ('nominated_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='fellowship_nominations_initiated', to='scipost.contributor')),
+                ('profile', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='fellowship_nominations', to='profiles.profile')),
+            ],
+            options={
+                'verbose_name_plural': 'Fellowship Nominations',
+                'ordering': ['profile', 'college'],
+            },
+        ),
+        migrations.CreateModel(
+            name='FellowshipNominationVotingRound',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('voting_opens', models.DateTimeField()),
+                ('voting_deadline', models.DateTimeField()),
+                ('eligible_to_vote', models.ManyToManyField(blank=True, related_name='voting_rounds_eligible_to_vote_in', to='colleges.Fellowship')),
+                ('nomination', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='voting_rounds', to='colleges.fellowshipnomination')),
+            ],
+            options={
+                'verbose_name_plural': 'Fellowship Nomination Voting Rounds',
+                'ordering': ['nomination__profile__last_name'],
+            },
+        ),
+        migrations.CreateModel(
+            name='FellowshipNominationVote',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('vote', models.CharField(choices=[('agree', 'Agree'), ('abstain', 'Abstain'), ('disagree', 'Disagree')], max_length=16)),
+                ('on', models.DateTimeField(blank=True, null=True)),
+                ('comments', models.TextField(blank=True)),
+                ('fellow', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='fellowship_nomination_votes', to='colleges.fellowship')),
+                ('voting_round', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='votes', to='colleges.fellowshipnominationvotinground')),
+            ],
+            options={
+                'verbose_name_plural': 'Fellowship Nomination Votes',
+                'ordering': ['voting_round'],
+            },
+        ),
+        migrations.CreateModel(
+            name='FellowshipNominationEvent',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('description', models.TextField()),
+                ('on', models.DateTimeField(default=django.utils.timezone.now)),
+                ('nomination', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='events', to='colleges.fellowshipnomination')),
+            ],
+            options={
+                'verbose_name_plural': 'Fellowhips Nomination Events',
+                'ordering': ['-on'],
+            },
+        ),
+        migrations.CreateModel(
+            name='FellowshipNominationDecision',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('outcome', models.CharField(choices=[('elected', 'Elected'), ('notelected', 'Not elected')], max_length=16)),
+                ('fixed_on', models.DateTimeField(default=django.utils.timezone.now)),
+                ('nomination', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='decision', to='colleges.fellowshipnomination')),
+            ],
+            options={
+                'verbose_name_plural': 'Fellowship Nomination Decisions',
+                'ordering': ['nomination'],
+            },
+        ),
+        migrations.CreateModel(
+            name='FellowshipInvitation',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('invited_on', models.DateTimeField(blank=True, null=True)),
+                ('response', models.CharField(blank=True, choices=[('notyetinvited', 'Not yet invited'), ('invited', 'Invited'), ('reinvited', 'Reinvited'), ('multireinvited', 'Multiply reinvited'), ('unresponsive', 'Unresponsive'), ('accepted', 'Accepted, for immediate start'), ('postponed', 'Accepted, but start date postponed'), ('declined', 'Declined')], max_length=16)),
+                ('postpone_start_to', models.DateField(blank=True)),
+                ('nomination', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='invitation', to='colleges.fellowshipnomination')),
+            ],
+            options={
+                'verbose_name_plural': 'Fellowship Invitations',
+                'ordering': ['nomination'],
+            },
+        ),
+    ]
diff --git a/scipost_django/colleges/migrations/0032_auto_20220129_0837.py b/scipost_django/colleges/migrations/0032_auto_20220129_0837.py
new file mode 100644
index 0000000000000000000000000000000000000000..b4fc8bfc203a406b882ad89958f945ecbe88403f
--- /dev/null
+++ b/scipost_django/colleges/migrations/0032_auto_20220129_0837.py
@@ -0,0 +1,37 @@
+# Generated by Django 3.2.5 on 2022-01-29 07:37
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('colleges', '0031_fellowshipinvitation_fellowshipnomination_fellowshipnominationdecision_fellowshipnominationevent_fel'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='fellowshipnominationevent',
+            options={'ordering': ['-on'], 'verbose_name_plural': 'Fellowhip Nomination Events'},
+        ),
+        migrations.AddField(
+            model_name='fellowshipinvitation',
+            name='comments',
+            field=models.TextField(blank=True, help_text='You can use plain text, Markdown or reStructuredText; see our <a href="/markup/help/" target="_blank">markup help</a> pages.'),
+        ),
+        migrations.AddField(
+            model_name='fellowshipnomination',
+            name='nominator_comments',
+            field=models.TextField(blank=True, help_text='You can use plain text, Markdown or reStructuredText; see our <a href="/markup/help/" target="_blank">markup help</a> pages.'),
+        ),
+        migrations.AddField(
+            model_name='fellowshipnominationdecision',
+            name='comments',
+            field=models.TextField(blank=True, help_text='You can use plain text, Markdown or reStructuredText; see our <a href="/markup/help/" target="_blank">markup help</a> pages.'),
+        ),
+        migrations.AlterField(
+            model_name='fellowshipnominationvote',
+            name='comments',
+            field=models.TextField(blank=True, help_text='You can use plain text, Markdown or reStructuredText; see our <a href="/markup/help/" target="_blank">markup help</a> pages.'),
+        ),
+    ]
diff --git a/scipost_django/colleges/models/__init__.py b/scipost_django/colleges/models/__init__.py
index 5e7aeb5facfaeb700d6dfb74b11fab5240d3a597..f2b237461d5d0c32b1432da730f9cafa90641307 100644
--- a/scipost_django/colleges/models/__init__.py
+++ b/scipost_django/colleges/models/__init__.py
@@ -6,4 +6,10 @@ from .college import College
 
 from .fellowship import Fellowship
 
+from .nomination import (
+    FellowshipNomination, FellowshipNominationEvent,
+    FellowshipNominationVotingRound, FellowshipNominationVote,
+    FellowshipNominationDecision, FellowshipInvitation
+)
+
 from .potential_fellowship import PotentialFellowship, PotentialFellowshipEvent
diff --git a/scipost_django/colleges/models/nomination.py b/scipost_django/colleges/models/nomination.py
new file mode 100644
index 0000000000000000000000000000000000000000..c40687151cfb9688aa64c1ba358cf7c4ac97fb07
--- /dev/null
+++ b/scipost_django/colleges/models/nomination.py
@@ -0,0 +1,242 @@
+__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
+__license__ = "AGPL v3"
+
+
+from django.db import models
+from django.utils import timezone
+
+from ..managers import FellowshipNominationVotingRoundQuerySet
+
+
+class FellowshipNomination(models.Model):
+
+    college = models.ForeignKey(
+        'colleges.College',
+        on_delete=models.PROTECT,
+        related_name='nominations'
+    )
+
+    profile = models.ForeignKey(
+        'profiles.Profile',
+        on_delete=models.CASCADE,
+        related_name='fellowship_nominations'
+    )
+
+    nominated_by = models.ForeignKey(
+        'scipost.Contributor',
+        on_delete=models.CASCADE,
+        related_name='fellowship_nominations_initiated'
+    )
+
+    nominated_on = models.DateTimeField(default=timezone.now)
+
+    nominator_comments = models.TextField(
+        help_text=('You can use plain text, Markdown or reStructuredText; see our '
+                   '<a href="/markup/help/" target="_blank">markup help</a> pages.'),
+        blank=True
+    )
+
+    fellowship = models.OneToOneField(
+        'colleges.Fellowship',
+        on_delete=models.CASCADE,
+        related_name='nomination',
+        blank=True, null=True
+    )
+
+    class Meta:
+        ordering = [
+            'profile',
+            'college',
+        ]
+        verbose_name_plural = 'Fellowship Nominations'
+
+    def __str__(self):
+        return (f'{self.profile} to {self.college} '
+                f'on {self.nominated_on.strftime("%Y-%m-%d")}')
+
+
+class FellowshipNominationEvent(models.Model):
+
+    nomination = models.ForeignKey(
+        'colleges.FellowshipNomination',
+        on_delete=models.CASCADE,
+        related_name='events'
+    )
+
+    description = models.TextField()
+
+    on = models.DateTimeField(default=timezone.now)
+
+    class Meta:
+        ordering = [
+            '-on'
+            ]
+        verbose_name_plural = 'Fellowhip Nomination Events'
+
+    def __str__(self):
+        return f'Event for {self.nomination}'
+
+
+class FellowshipNominationVotingRound(models.Model):
+
+    nomination = models.ForeignKey(
+        'colleges.FellowshipNomination',
+        on_delete=models.CASCADE,
+        related_name='voting_rounds'
+    )
+
+    eligible_to_vote = models.ManyToManyField(
+        'colleges.Fellowship',
+        related_name='voting_rounds_eligible_to_vote_in',
+        blank=True
+    )
+
+    voting_opens = models.DateTimeField()
+
+    voting_deadline = models.DateTimeField()
+
+    objects = FellowshipNominationVotingRoundQuerySet.as_manager()
+
+    class Meta:
+        ordering = [
+            'nomination__profile__last_name'
+        ]
+        verbose_name_plural = 'Fellowship Nomination Voting Rounds'
+
+    def __str__(self):
+        return (f'Voting round ({self.voting_opens.strftime("%Y-%m-%d")} -'
+                f' {self.voting_deadline.strftime("%Y-%m-%d")}) for {self.nomination}')
+
+
+class FellowshipNominationVote(models.Model):
+
+    VOTE_AGREE = 'agree'
+    VOTE_ABSTAIN = 'abstain'
+    VOTE_DISAGREE = 'disagree'
+    VOTE_CHOICES = (
+        (VOTE_AGREE, 'Agree'),
+        (VOTE_ABSTAIN, 'Abstain'),
+        (VOTE_DISAGREE, 'Disagree')
+    )
+
+    voting_round = models.ForeignKey(
+        'colleges.FellowshipNominationVotingRound',
+        on_delete=models.CASCADE,
+        related_name='votes'
+    )
+
+    fellow = models.ForeignKey(
+        'colleges.Fellowship',
+        on_delete=models.CASCADE,
+        related_name='fellowship_nomination_votes'
+    )
+
+    vote = models.CharField(
+        max_length=16,
+        choices=VOTE_CHOICES
+    )
+
+    on = models.DateTimeField(blank=True, null=True)
+
+    comments = models.TextField(
+        help_text=('You can use plain text, Markdown or reStructuredText; see our '
+                   '<a href="/markup/help/" target="_blank">markup help</a> pages.'),
+        blank=True
+    )
+
+    class Meta:
+        ordering = ['voting_round',]
+        verbose_name_plural = 'Fellowship Nomination Votes'
+
+
+class FellowshipNominationDecision(models.Model):
+
+    nomination = models.OneToOneField(
+        'colleges.FellowshipNomination',
+        on_delete=models.CASCADE,
+        related_name='decision'
+    )
+
+    OUTCOME_ELECTED = 'elected'
+    OUTCOME_NOT_ELECTED = 'notelected'
+    OUTCOME_CHOICES = (
+        (OUTCOME_ELECTED, 'Elected'),
+        (OUTCOME_NOT_ELECTED, 'Not elected')
+    )
+    outcome = models.CharField(
+        max_length=16,
+        choices=OUTCOME_CHOICES
+    )
+
+    fixed_on = models.DateTimeField(default=timezone.now)
+
+    comments = models.TextField(
+        help_text=('You can use plain text, Markdown or reStructuredText; see our '
+                   '<a href="/markup/help/" target="_blank">markup help</a> pages.'),
+        blank=True
+    )
+
+    class Meta:
+        ordering = ['nomination',]
+        verbose_name_plural = 'Fellowship Nomination Decisions'
+
+    def __str__(self):
+        return f'Decision for {self.nomination}: {self.get_outcome_display()}'
+
+    @property
+    def elected(self):
+        return self.outcome == self.OUTCOME_ELECTED
+
+
+class FellowshipInvitation(models.Model):
+
+    nomination = models.OneToOneField(
+        'colleges.FellowshipNomination',
+        on_delete=models.CASCADE,
+        related_name='invitation'
+    )
+
+    invited_on = models.DateTimeField(blank=True, null=True)
+
+    RESPONSE_NOT_YET_INVITED = 'notyetinvited'
+    RESPONSE_INVITED = 'invited'
+    RESPONSE_REINVITED = 'reinvited'
+    RESPONSE_MULTIPLY_REINVITED = 'multireinvited'
+    RESPONSE_UNRESPONSIVE = 'unresponsive'
+    RESPONSE_ACCEPTED = 'accepted'
+    RESPONSE_POSTPONED = 'postponed'
+    RESPONSE_DECLINED = 'declined'
+    RESPONSE_CHOICES = (
+        (RESPONSE_NOT_YET_INVITED, 'Not yet invited'),
+        (RESPONSE_INVITED, 'Invited'),
+        (RESPONSE_REINVITED, 'Reinvited'),
+        (RESPONSE_MULTIPLY_REINVITED, 'Multiply reinvited'),
+        (RESPONSE_UNRESPONSIVE, 'Unresponsive'),
+        (RESPONSE_ACCEPTED, 'Accepted, for immediate start'),
+        (RESPONSE_POSTPONED, 'Accepted, but start date postponed'),
+        (RESPONSE_DECLINED, 'Declined')
+    )
+    response = models.CharField(
+        max_length=16,
+        choices=RESPONSE_CHOICES,
+        blank=True
+    )
+
+    postpone_start_to = models.DateField(blank=True)
+
+    comments = models.TextField(
+        help_text=('You can use plain text, Markdown or reStructuredText; see our '
+                   '<a href="/markup/help/" target="_blank">markup help</a> pages.'),
+        blank=True
+    )
+
+    class Meta:
+        ordering = ['nomination',]
+        verbose_name_plural = 'Fellowship Invitations'
+
+    def __str__(self):
+        return f'Invitation for {self.nomination}'
+
+    @property
+    def declined(self):
+        return self.response == self.RESPONSE_DECLINED
diff --git a/scipost_django/colleges/permissions.py b/scipost_django/colleges/permissions.py
index f27cf1a4fc52643f759feb5e43aff2dd6c30224f..394b35c342659cbc54e3cec5661b9ec8656f8a89 100644
--- a/scipost_django/colleges/permissions.py
+++ b/scipost_django/colleges/permissions.py
@@ -6,6 +6,7 @@ from django.contrib.auth.decorators import user_passes_test
 from django.core.exceptions import PermissionDenied
 
 from scipost.permissions import is_in_group
+from colleges.models import Fellowship
 
 
 def fellowship_required():
@@ -20,7 +21,7 @@ def fellowship_required():
 
 
 def fellowship_or_admin_required():
-    """Require user to have any Fellowship or Administrational permissions."""
+    """Require user to have any Fellowship or Administrative permissions."""
     def test(u):
         if u.is_authenticated:
             if hasattr(u, 'contributor') and u.contributor.fellowships.exists():
@@ -32,3 +33,22 @@ def fellowship_or_admin_required():
                 return True
         raise PermissionDenied
     return user_passes_test(test)
+
+
+def is_edadmin_or_advisory_or_active_regular_or_senior_fellow(user):
+    return (user.groups.filter(name='Editorial Administrators').exists() or
+            user.groups.filter(name='Advisory Board').exists() or
+            Fellowship.objects.active().regular_or_senior().filter(
+                contributor__user=user).exists())
+
+
+def is_edadmin_or_active_regular_or_senior_fellow(user):
+    return (user.groups.filter(name='Editorial Administrators').exists() or
+            Fellowship.objects.active().regular_or_senior().filter(
+                contributor__user=user).exists())
+
+
+def is_edadmin_or_senior_fellow(user):
+    return (user.groups.filter(name='Editorial Administrators').exists() or
+            Fellowship.objects.active().senior().filter(
+                contributor__user=user).exists())
diff --git a/scipost_django/colleges/templates/colleges/_hx_failed_eligibility_criteria.html b/scipost_django/colleges/templates/colleges/_hx_failed_eligibility_criteria.html
new file mode 100644
index 0000000000000000000000000000000000000000..185718efd63c41d4d39ea91157c4e5e95edcb075
--- /dev/null
+++ b/scipost_django/colleges/templates/colleges/_hx_failed_eligibility_criteria.html
@@ -0,0 +1,8 @@
+<div class="border border-danger text-danger bg-danger bg-opacity-10 mb-2 p-2">
+  <p><strong>{{ profile }}</strong>&emsp;cannot be nominated at this time:</p>
+  <ul>
+    {% for criterion in failed_eligibility_criteria %}
+      <li class="text-danger">{{ criterion }}</li>
+    {% endfor %}
+  </ul>
+</div>
diff --git a/scipost_django/colleges/templates/colleges/_hx_fellowship_dynsel_list.html b/scipost_django/colleges/templates/colleges/_hx_fellowship_dynsel_list.html
index 7077f095c04ce46d2160797ba104196447be9d69..85ef7102fd0fb76acce34d3c27835118b39cf5b0 100644
--- a/scipost_django/colleges/templates/colleges/_hx_fellowship_dynsel_list.html
+++ b/scipost_django/colleges/templates/colleges/_hx_fellowship_dynsel_list.html
@@ -8,7 +8,7 @@
       <li class="m-1">
 	<a
 	    hx-get="{% fellowship_dynsel_action_url fellowship %}"
-	    hx-target="#fellowships"
+	    hx-target="#{{ action_target_element_id }}"
 	>
 	  {{ fellowship }}
 	</a>
diff --git a/scipost_django/colleges/templates/colleges/_hx_nomination_form.html b/scipost_django/colleges/templates/colleges/_hx_nomination_form.html
new file mode 100644
index 0000000000000000000000000000000000000000..876a069a9f2c2ae4277baabce60cc2fc8aeb3f7a
--- /dev/null
+++ b/scipost_django/colleges/templates/colleges/_hx_nomination_form.html
@@ -0,0 +1,12 @@
+{% load crispy_forms_tags %}
+<div class="m-2 p-4 border border-warning">
+  <h3>Nomination to Fellowship:&emsp;<span class="bg-success bg-opacity-25 p-2"><em>{{ profile }}</em></span></h3>
+  <form
+      hx-post="{% url 'colleges:_hx_nomination_form' profile_id=profile.pk %}"
+      hx-target="#nomination_form_response"
+      hx-indicator="#nomination_form_response-indicator"
+  >
+    {% csrf_token %}
+    {% crispy nomination_form %}
+  </form>
+</div>
diff --git a/scipost_django/colleges/templates/colleges/_hx_nomination_li.html b/scipost_django/colleges/templates/colleges/_hx_nomination_li.html
new file mode 100644
index 0000000000000000000000000000000000000000..473ba6d789171a8218599a18cdfd195dd767ab6f
--- /dev/null
+++ b/scipost_django/colleges/templates/colleges/_hx_nomination_li.html
@@ -0,0 +1,97 @@
+<div class="border border-dark">
+  <details>
+    <summary class="bg-light p-2">
+      {{ nomination.profile }}
+      <span class="float-end">
+	{{ nomination.college }}
+	<span class="ms-4">Outcome:</span>
+	{% if nomination.decision %}
+	  {{ nomination.decision.get_outcome_display }}
+	{% else %}
+	  pending
+	{% endif %}
+      </span>
+    </summary>
+    <div class="p-2">
+      <p>Nominated by {{ nomination.nominated_by }} on {{ nomination.nominated_on|date:"Y-m-d" }}</p>
+      {% if nomination.nominator_comments %}
+	<div class="row">
+	  <div class="col-lg-2">
+	    Nominator comments:
+	  </div>
+	  <div class="col-lg-10">
+	    <em>{{ nomination.nominator_comments }}</em>
+	  </div>
+	</div>
+      {% endif %}
+      <div class="row">
+	<div class="col">
+	  <div class="card">
+	    <div class="card-header">
+	      Details
+	    </div>
+	    <div class="card-body">
+	      <table class="table">
+		<tr>
+		  <td>Field</td><td>{{ nomination.profile.acad_field }}</td>
+		</tr>
+		<tr>
+		  <td>Specialties</td>
+		  <td>
+		    {% for specialty in nomination.profile.specialties.all %}
+    		      <div class="single d-inline" data-specialty="{{ specialty }}" data-bs-toggle="tooltip" data-bs-placement="bottom" title="{{ specialty }}">{{ specialty.code }}</div>
+		    {% empty %}
+		      undefined
+		    {% endfor %}
+		  </td>
+		</tr>
+		<tr>
+		  <td>ORCID ID</td>
+		  <td>
+		    {% if nomination.profile.orcid_id %}
+		      <a href="//orcid.org/{{ nomination.profile.orcid_id }}" target="_blank" rel="noopener">{{ nomination.profile.orcid_id }}</a>
+		    {% else %}
+		      unknown
+		    {% endif %}
+		  </td>
+		</tr>
+		<tr><td>Webpage</td>
+		  <td>
+		    {% if profile.webpage %}
+		      <a href="{{ profile.webpage }}" target="_blank" rel="noopener">{{ profile.webpage }}</a>
+		    {% else %}
+		      unknown
+		    {% endif %}
+		  </td>
+		</tr>
+	      </table>
+	    </div>
+	  </div>
+	</div>
+	<div class="col">
+	  <div class="card">
+	    <div class="card-header">
+	      Publications in SciPost Journals
+	    </div>
+	    <div class="card-body">
+	      <ul>
+		{% for pub in profile.publications.all|slice:":10" %}
+		  <li><a href="{{ pub.get_absolute_url }}">{{ pub.citation }}</a></li>
+		{% empty %}
+		  <li>No Publication found</li>
+		{% endfor %}
+	      </ul>
+	    </div>
+	  </div>
+	</div>
+      </div>
+      <table class="table">
+	<tr>
+	  <td>Affiliations</td>
+	  <td>
+	    {% include 'profiles/_affiliations_table.html' with profile=nomination.profile actions=False %}</td>
+	</tr>
+      </table>
+    </div>
+  </details>
+</div>
diff --git a/scipost_django/colleges/templates/colleges/_hx_nomination_voting_rounds.html b/scipost_django/colleges/templates/colleges/_hx_nomination_voting_rounds.html
new file mode 100644
index 0000000000000000000000000000000000000000..a8657af4b1c2d6372c8a5966280c3757ec541f2f
--- /dev/null
+++ b/scipost_django/colleges/templates/colleges/_hx_nomination_voting_rounds.html
@@ -0,0 +1,9 @@
+<ul>
+  {% for round in voting_rounds %}
+    <li class="p-2 mb-2" id="voting_round_{{ round.id }}">
+      {{ round }}
+    </li>
+  {% empty %}
+    <li>No voting round found</li>
+  {% endfor %}
+</ul>
diff --git a/scipost_django/colleges/templates/colleges/_hx_nominations.html b/scipost_django/colleges/templates/colleges/_hx_nominations.html
new file mode 100644
index 0000000000000000000000000000000000000000..6ba5b622f4669c21b423326e4ffcc27911415ecd
--- /dev/null
+++ b/scipost_django/colleges/templates/colleges/_hx_nominations.html
@@ -0,0 +1,22 @@
+{% for nomination in page_obj %}
+  <li class="p-2 mb-2" id="nomination_{{ nomination.id }}">
+    {% include 'colleges/_hx_nomination_li.html' with nomination=nomination %}
+  </li>
+{% empty %}
+  <li>No Nomination could be found</li>
+{% endfor %}
+{% if page_obj.has_next %}
+  <li hx-post="{% url 'colleges:_hx_nominations' %}?page={{ page_obj.next_page_number }}"
+      hx-include="#search-nominations-form"
+      hx-trigger="revealed"
+      hx-swap="afterend"
+      hx-indicator="#indicator-search-page-{{ page_obj.number }}"
+  >
+    <div id="indicator-search-page-{{ page_obj.number }}" class="htmx-indicator p-2">
+      <button class="btn btn-warning" type="button" disabled>
+	<strong>Loading page {{ page_obj.next_page_number }} out of {{ page_obj.paginator.num_pages }}</strong>
+	<div class="spinner-grow spinner-grow-sm ms-2" role="status" aria-hidden="true"></div>
+      </button>
+    </div>
+  </li>
+{% endif %}
diff --git a/scipost_django/colleges/templates/colleges/nominations.html b/scipost_django/colleges/templates/colleges/nominations.html
new file mode 100644
index 0000000000000000000000000000000000000000..893d52f4fe9f806b5e4cbf5db7feb54317ac819e
--- /dev/null
+++ b/scipost_django/colleges/templates/colleges/nominations.html
@@ -0,0 +1,158 @@
+{% extends 'colleges/base.html' %}
+
+{% load user_groups %}
+{% load crispy_forms_tags %}
+
+{% block breadcrumb_items %}
+  {{ block.super }}
+  <a href="{% url 'colleges:colleges' %}" class="breadcrumb-item">Colleges</a>
+  <span class="breadcrumb-item">Nominations</span>
+{% endblock %}
+
+{% block meta_description %}{{ block.super }} Nominations{% endblock meta_description %}
+{% block pagetitle %}: Nominations{% endblock pagetitle %}
+
+{% block content %}
+
+  {% is_ed_admin request.user as is_ed_admin %}
+
+  <h1 class="highlight">Fellowship Nominations<span class="text-danger ms-4">{% include 'bi/cone-striped.html' %}<em>in construction</em>&nbsp;{% include 'bi/cone-striped.html' %}</span></h1>
+
+  <p>Consult the
+    <a href="{% url 'submissions:monitor' %}" target="_blank">Submissions Monitor</a> page.
+    Any <span class="text-danger">red-highlighted</span>
+    specialty is in need of more Fellows&nbsp;
+    {% include 'bi/arrow-right.html' %}
+    &nbsp;<strong>Help out by nominating candidates!</strong>
+  </p>
+
+  <details class="border border-warning border-2 mt-4">
+    <summary class="bg-warning bg-opacity-10 p-2">
+      <h2 class="ms-2">Nominate</h2>
+    </summary>
+    <div class="p-2">
+      <div class="row">
+	<div class="col-lg-6">
+	  <h3>Procedure</h3>
+	  <ul>
+	    <li>Type your search query in the search form</li>
+	    <li>When the name you're looking for appears in the
+	      <em>Matching profiles</em> list, double-click on it</li>
+	    <li>The nomination form will appear below</li>
+	    <li>Non-eligibility flags (if any) will appear</li>
+	    <li>If eligible, fill the form in (comments are optional)</li>
+	    <li>Submit! (the vote will be arranged by EdAdmin)</li>
+	  </ul>
+	  <div class="row">
+	    <div class="col-8">
+	      <form
+		  hx-post="{% url 'profiles:_hx_profile_dynsel_list' %}"
+		  hx-trigger="keyup delay:200ms, change"
+		  hx-target="#profile_dynsel_results"
+		  hx-indicator="#profile_dynsel_results-indicator"
+	      >
+		<div id="profile_dynsel_form">{% crispy profile_dynsel_form %}</div>
+	      </form>
+	    </div>
+	    <div class="col-2">
+	      <div id="nomination_form_response-indicator" class="htmx-indicator">
+		<button class="btn btn-sm btn-warning" type="button" disabled>
+		  <strong>Loading form...</strong>
+		  <div class="spinner-grow spinner-grow-sm ms-2" role="status" aria-hidden="true"></div>
+		</button>
+	      </div>
+	    </div>
+	    <div class="col-2">
+	      <div id="profile_dynsel_results-indicator" class="htmx-indicator">
+		<button class="btn btn-sm btn-warning" type="button" disabled>
+		  <strong>Loading results...</strong>
+		  <div class="spinner-grow spinner-grow-sm ms-2" role="status" aria-hidden="true"></div>
+		</button>
+	      </div>
+	    </div>
+	  </div>
+	  <h3 class="mb-2">Not found?</h3>
+	  <p>Then add to our database by <a href="{% url 'profiles:profile_create' %}" target="_blank">creating a new Profile</a> (opens in new window).</p>
+	</div>
+	<div class="col-lg-6">
+	  <h3>Matching profiles</h3>
+	  <div id="profile_dynsel_results" class="border border-light m-2 p-1"></div>
+	</div>
+      </div>
+      <div id="nomination_form_response"></div>
+    </div>
+  </details>
+
+  <details class="border border-success border-2 mt-4">
+    <summary class="bg-success bg-opacity-10 p-2">
+      <h2 class="ms-2">Vote</h2>
+    </summary>
+    <div class="p-2 mt-2">
+      {% if is_ed_admin %}
+	<h3>Ongoing elections</h3>
+	<div id="voting_rounds_ongoing"
+	     hx-get="{% url 'colleges:_hx_nomination_voting_rounds' %}?filters=ongoing"
+	     hx-trigger="revealed"
+	>
+	</div>
+	<h3>Closed elections</h3>
+	<div id="voting_rounds_closed"
+	     hx-get="{% url 'colleges:_hx_nomination_voting_rounds' %}?filters=closed"
+	     hx-trigger="revealed"
+	>
+	</div>
+      {% else %}
+	<h3>Cast your vote (election ongoing)</h3>
+	<div id="voting_rounds_ongoing_vote_required"
+	     hx-get="{% url 'colleges:_hx_nomination_voting_rounds' %}?filters=ongoing,vote_required"
+	     hx-trigger="revealed"
+	>
+	</div>
+	<h3>Votes you have cast (election ongoing)</h3>
+	<div id="voting_rounds_ongoing_voted"
+	     hx-get="{% url 'colleges:_hx_nomination_voting_rounds' %}?filters=ongoing,voted"
+	     hx-trigger="revealed"
+	>
+	</div>
+	<h3>Votes you have cast (election closed)</h3>
+	<div id="voting_rounds_closed_voted"
+	     hx-get="{% url 'colleges:_hx_nomination_voting_rounds' %}?filters=closed,voted"
+	     hx-trigger="revealed"
+	>
+	</div>
+      {% endif %}
+    </div>
+  </details>
+
+  <details class="border border-2 mt-4">
+    <summary class="bg-light p-2">
+      <h2 class="ms-2">List / filter</h2>
+    </summary>
+    <div class="p-2 mt-2">
+      <form
+	  hx-post="{% url 'colleges:_hx_nominations' %}"
+	  hx-trigger="load, keyup delay:500ms, change"
+	  hx-target="#search-nominations-results"
+	  hx-indicator="#indicator-search"
+      >
+	<div id="search-nominations-form">{% crispy search_nominations_form %}</div>
+      </form>
+
+      <div class="row">
+	<div class="col">
+	  <h3>Nominations list</h3>
+	</div>
+	<div class="col">
+	  <div id="indicator-search-nominations" class="htmx-indicator">
+	    <button class="btn btn-sm btn-warning" type="button" disabled>
+	      <strong>Loading...</strong>
+	      <div class="spinner-grow spinner-grow-sm ms-2" role="status" aria-hidden="true"></div>
+	    </button>
+	  </div>
+	</div>
+      </div>
+      <ul id="search-nominations-results" class="list-unstyled mt-2"></ul>
+    </div>
+  </details>
+
+{% endblock content %}
diff --git a/scipost_django/colleges/urls.py b/scipost_django/colleges/urls.py
index a5bee2f2fff627df6ead9f9d38e116b17e9b2ee6..62d830942991b69632b38f0af8f9fcc03993f194 100644
--- a/scipost_django/colleges/urls.py
+++ b/scipost_django/colleges/urls.py
@@ -158,4 +158,26 @@ urlpatterns = [
         views.PotentialFellowshipListView.as_view(),
         name='potential_fellowships'
     ),
+
+    # Nominations
+    path(
+        'nominations',
+        views.nominations,
+        name='nominations'
+    ),
+    path(
+        '_hx_nomination_form/<int:profile_id>',
+        views._hx_nomination_form,
+        name='_hx_nomination_form'
+    ),
+    path(
+        '_hx_nominations',
+        views._hx_nominations,
+        name='_hx_nominations'
+    ),
+    path(
+        '_hx_nomination_voting_rounds',
+        views._hx_nomination_voting_rounds,
+        name='_hx_nomination_voting_rounds'
+    ),
 ]
diff --git a/scipost_django/colleges/utils.py b/scipost_django/colleges/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..f686180eeec13c8dad6b25233d492052e872e45d
--- /dev/null
+++ b/scipost_django/colleges/utils.py
@@ -0,0 +1,49 @@
+__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
+__license__ = "AGPL v3"
+
+
+from .models import College, Fellowship, FellowshipNomination
+
+
+def check_profile_eligibility_for_fellowship(profile):
+    """
+    Returns a list of failed eligibility criteria (if any).
+
+    Requirements:
+
+    - Profile has a known acad_field
+    - There is an active College in the Profile's acad_field
+    - no current Fellowship exists
+    - no current FellowshipNomination exists
+    - no 'not elected' decision in last 2 years
+    - no invitation was turned down in the last 2 years
+    """
+    blocks = []
+    if not profile.acad_field:
+        blocks.append('No academic field is specified for this profile. '
+                      'Contact EdAdmin or techsupport.')
+    elif not College.objects.filter(acad_field=profile.acad_field).exists():
+        blocks.append('There is currently no College in {profile.acad_field}. '
+                      'Contact EdAdmin or techsupport to get one started.')
+    if Fellowship.objects.active().regular_or_senior().filter(
+            contributor__profile=profile).exists():
+        blocks.append('This Profile is associated to an active Fellowship.')
+    latest_nomination = FellowshipNomination.objects.filter(
+        profile=profile).first()
+    if latest_nomination:
+        try:
+            if (latest_nomination.decision.fixed_on +
+                datetime.timedelta(days=730)) > timezone.now():
+                if latest_nomination.decision.elected:
+                    try:
+                        if latest_nomination.invitation.declined:
+                            blocks.append('Invitation declined less that 2 years ago. '
+                                          'Wait to try again.')
+                        else:
+                             blocks.append('Already elected, invitation in process.')
+                    except AttributeError:
+                        blocks.append('Already elected, invitation pending.')
+                blocks.append('Election failed less that 2 years ago. Must wait.')
+        except AttributeError: # no decision yet
+            blocks.append('This Profile is associated to an ongoing Nomination process.')
+    return blocks if len(blocks) > 0 else None
diff --git a/scipost_django/colleges/views.py b/scipost_django/colleges/views.py
index c76603285e736c37c197ba0a9c024caa0805f177..e96e3d3ad63e0d2d6eb1eaa2af3d545d7faa4d9a 100644
--- a/scipost_django/colleges/views.py
+++ b/scipost_django/colleges/views.py
@@ -9,27 +9,39 @@ from dal import autocomplete
 from django.contrib import messages
 from django.contrib.auth.models import Group
 from django.contrib.auth.decorators import login_required, permission_required, user_passes_test
+from django.core.paginator import Paginator
 from django.urls import reverse, reverse_lazy
-from django.http import Http404
+from django.http import HttpResponse, Http404
 from django.shortcuts import get_object_or_404, render, redirect
 from django.utils import timezone
 from django.views.generic.detail import DetailView
 from django.views.generic.edit import CreateView, UpdateView, DeleteView
 from django.views.generic.list import ListView
 
+from colleges.permissions import (
+    is_edadmin_or_senior_fellow, is_edadmin_or_advisory_or_active_regular_or_senior_fellow
+)
+from colleges.utils import check_profile_eligibility_for_fellowship
 from submissions.models import Submission
-from submissions.permissions import is_edadmin_or_senior_fellow
 
 from .constants import (
     POTENTIAL_FELLOWSHIP_STATUSES, POTENTIAL_FELLOWSHIP_EVENT_STATUSUPDATED,
     POTENTIAL_FELLOWSHIP_INVITED, POTENTIAL_FELLOWSHIP_ACTIVE_IN_COLLEGE,
     potential_fellowship_statuses_dict,
     POTENTIAL_FELLOWSHIP_EVENT_VOTED_ON, POTENTIAL_FELLOWSHIP_EVENT_EMAILED)
-from .forms import FellowshipDynSelForm, FellowshipForm, FellowshipRemoveSubmissionForm,\
-    FellowshipAddSubmissionForm, SubmissionAddFellowshipForm,\
-    FellowshipRemoveProceedingsForm, FellowshipAddProceedingsForm, \
-    PotentialFellowshipForm, PotentialFellowshipStatusForm, PotentialFellowshipEventForm
-from .models import College, Fellowship, PotentialFellowship, PotentialFellowshipEvent
+from .forms import (
+    FellowshipDynSelForm, FellowshipForm,
+    FellowshipRemoveSubmissionForm, FellowshipAddSubmissionForm,
+    SubmissionAddFellowshipForm,
+    FellowshipRemoveProceedingsForm, FellowshipAddProceedingsForm,
+    PotentialFellowshipForm, PotentialFellowshipStatusForm, PotentialFellowshipEventForm,
+    FellowshipNominationForm, FellowshipNominationSearchForm,
+)
+from .models import (
+    College, Fellowship,
+    PotentialFellowship, PotentialFellowshipEvent,
+    FellowshipNominationVotingRound
+)
 
 from scipost.forms import EmailUsersForm, SearchTextForm
 from scipost.mixins import PermissionsMixin, PaginationMixin, RequestViewMixin
@@ -38,6 +50,8 @@ from scipost.models import Contributor
 from common.utils import Q_with_alternative_spellings
 from mails.views import MailView
 from ontology.models import Branch
+from profiles.models import Profile
+from profiles.forms import ProfileDynSelForm
 
 
 class CollegeListView(ListView):
@@ -181,6 +195,7 @@ def _hx_fellowship_dynsel_list(request):
         'fellowships': fellowships,
         'action_url_name': form.cleaned_data['action_url_name'],
         'action_url_base_kwargs': form.cleaned_data['action_url_base_kwargs'],
+        'action_target_element_id': form.cleaned_data['action_target_element_id'],
     }
     return render(request, 'colleges/_hx_fellowship_dynsel_list.html', context)
 
@@ -513,3 +528,101 @@ class PotentialFellowshipEventCreateView(PermissionsMixin, CreateView):
         form.instance.noted_by = self.request.user.contributor
         messages.success(self.request, 'Event added successfully')
         return super().form_valid(form)
+
+
+
+###############
+# Nominations #
+###############
+
+
+@user_passes_test(is_edadmin_or_advisory_or_active_regular_or_senior_fellow)
+def nominations(request):
+    """
+    List Nominations.
+    """
+    profile_dynsel_form = ProfileDynSelForm(
+        initial={
+            'action_url_name': 'colleges:_hx_nomination_form',
+            'action_url_base_kwargs': { },
+            'action_target_element_id': 'nomination_form_response'
+        }
+    )
+    context = {
+        'profile_dynsel_form': profile_dynsel_form,
+        'search_nominations_form': FellowshipNominationSearchForm(),
+    }
+    return render(request, 'colleges/nominations.html', context)
+
+
+@user_passes_test(is_edadmin_or_advisory_or_active_regular_or_senior_fellow)
+def _hx_nomination_form(request, profile_id):
+    profile = get_object_or_404(Profile, pk=profile_id)
+    failed_eligibility_criteria = check_profile_eligibility_for_fellowship(profile)
+    if failed_eligibility_criteria:
+        return render(
+            request,
+            'colleges/_hx_failed_eligibility_criteria.html',
+            {
+                'profile': profile,
+                'failed_eligibility_criteria': failed_eligibility_criteria
+            }
+        )
+    nomination_form = FellowshipNominationForm(
+        request.POST or None,
+        profile=profile
+    )
+    if nomination_form.is_valid():
+        nomination = nomination_form.save()
+        return HttpResponse(
+            f'<div class="bg-success text-white p-2 ">{nomination.profile} '
+            f'successfully nominated to {nomination.college}.</div>')
+    nomination_form.fields['nominated_by'].initial = request.user.contributor
+    context = {
+        'profile': profile,
+        'nomination_form': nomination_form,
+    }
+    return render(request, 'colleges/_hx_nomination_form.html', context)
+
+
+@user_passes_test(is_edadmin_or_advisory_or_active_regular_or_senior_fellow)
+def _hx_nominations(request):
+    form = FellowshipNominationSearchForm(request.POST or None)
+    if form.is_valid():
+        nominations = form.search_results()
+    else:
+        nominations = FellowshipNomination.objects.all()
+    paginator = Paginator(nominations, 16)
+    page_nr = request.GET.get('page')
+    page_obj = paginator.get_page(page_nr)
+    context = { 'page_obj': page_obj }
+    return render(request, 'colleges/_hx_nominations.html', context)
+
+
+@user_passes_test(is_edadmin_or_advisory_or_active_regular_or_senior_fellow)
+def _hx_nomination_voting_rounds(request):
+    fellowship = request.user.contributor.session_fellowship(request)
+    filters = request.GET.get('filters', None)
+    if filters:
+        filters = filters.split(',')
+    if not filters: # if no filters present, return empty response
+        voting_rounds = FellowshipNominationVotingRound.objects.none()
+    else:
+        voting_rounds = FellowshipNominationVotingRound.objects.all()
+        for filter in filters:
+            if filter == 'ongoing':
+                voting_rounds = voting_rounds.ongoing()
+            if filter == 'closed':
+                voting_rounds = voting_rounds.closed()
+            if filter == 'vote_required':
+                # show all voting rounds to edadmin; for Fellow, filter
+                if not request.user.contributor.is_ed_admin:
+                    voting_rounds = voting_rounds.filter(
+                        eligible_to_vote=fellowship
+                    ).exclude(votes__fellow=fellowship)
+            if filter == 'voted':
+                voting_rounds = voting_rounds.filter(votes__fellow=fellowship)
+    context = {
+        'voting_rounds': voting_rounds,
+    }
+    return render(request, 'colleges/_hx_nomination_voting_rounds.html', context)
diff --git a/scipost_django/journals/forms.py b/scipost_django/journals/forms.py
index 8a327f7add5bce6b5bb25be3a397a55bc447c4b5..2c3678d50ea9b078eba1d0eabb675573b845d401 100644
--- a/scipost_django/journals/forms.py
+++ b/scipost_django/journals/forms.py
@@ -874,7 +874,8 @@ OrgPubFractionsFormSet = modelformset_factory(OrgPubFraction,
 class PublicationDynSelForm(forms.Form):
     q = forms.CharField(max_length=32, label='Search (by title, author names)')
     action_url_name = forms.CharField()
-    action_url_base_kwargs = forms.JSONField()
+    action_url_base_kwargs = forms.JSONField(required=False)
+    action_target_element_id = forms.CharField()
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
@@ -883,6 +884,7 @@ class PublicationDynSelForm(forms.Form):
             FloatingField('q', autocomplete='off'),
             Field('action_url_name', type='hidden'),
             Field('action_url_base_kwargs', type='hidden'),
+            Field('action_target_element_id', type='hidden'),
         )
 
     def search_results(self):
diff --git a/scipost_django/journals/templates/journals/_hx_publication_dynsel_list.html b/scipost_django/journals/templates/journals/_hx_publication_dynsel_list.html
index 285383d7cb74fe76d113fe6a3b1ff87f307009a1..8c26b988b7ee14e38603ed532604d004bf343d5a 100644
--- a/scipost_django/journals/templates/journals/_hx_publication_dynsel_list.html
+++ b/scipost_django/journals/templates/journals/_hx_publication_dynsel_list.html
@@ -8,7 +8,7 @@
       <li class="m-1">
 	<a
 	    hx-get="{% publication_dynsel_action_url publication %}"
-	    hx-target="#publications"
+	    hx-target="#{{ action_target_element_id }}"
 	>
 	  {{ publication }}
 	</a>
diff --git a/scipost_django/journals/views.py b/scipost_django/journals/views.py
index 2731f5c7ce87ccb5fbbf93c2957b60f64cbf83b1..0277b9597673f62ab29a6cc2b1f362bb37ab2c37 100644
--- a/scipost_django/journals/views.py
+++ b/scipost_django/journals/views.py
@@ -102,6 +102,7 @@ def _hx_publication_dynsel_list(request):
         'publications': publications,
         'action_url_name': form.cleaned_data['action_url_name'],
         'action_url_base_kwargs': form.cleaned_data['action_url_base_kwargs'],
+        'action_target_element_id': form.cleaned_data['action_target_element_id'],
     }
     return render(request, 'journals/_hx_publication_dynsel_list.html', context)
 
diff --git a/scipost_django/proceedings/views.py b/scipost_django/proceedings/views.py
index dffd8a24aff2ef49fa48043aff2c0ca6460a7291..ea7c79d0b306f0d9fd0c7723c41e873dbe1530ad 100644
--- a/scipost_django/proceedings/views.py
+++ b/scipost_django/proceedings/views.py
@@ -64,7 +64,8 @@ def _hx_proceedings_fellowships(request, id):
     form = FellowshipDynSelForm(
         initial={
             'action_url_name': 'proceedings:_hx_proceedings_fellowship_action',
-            'action_url_base_kwargs': {'id': proceedings.id, 'action': 'add'}
+            'action_url_base_kwargs': {'id': proceedings.id, 'action': 'add'},
+            'action_target_element_id': 'fellowships',
         }
     )
     context = {
diff --git a/scipost_django/profiles/forms.py b/scipost_django/profiles/forms.py
index 9b26bebf94c45119013b86441475dcc9f1e8353c..c5e1b948870552d539a61ecfae1425d5a733f3ae 100644
--- a/scipost_django/profiles/forms.py
+++ b/scipost_django/profiles/forms.py
@@ -201,7 +201,8 @@ class ProfileSelectForm(forms.Form):
 class ProfileDynSelForm(forms.Form):
     q = forms.CharField(max_length=32, label='Search (by name)')
     action_url_name = forms.CharField()
-    action_url_base_kwargs = forms.JSONField()
+    action_url_base_kwargs = forms.JSONField(required=False)
+    action_target_element_id = forms.CharField()
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
@@ -210,6 +211,7 @@ class ProfileDynSelForm(forms.Form):
             FloatingField('q', autocomplete='off'),
             Field('action_url_name', type='hidden'),
             Field('action_url_base_kwargs', type='hidden'),
+            Field('action_target_element_id', type='hidden')
         )
 
     def search_results(self):
diff --git a/scipost_django/profiles/models.py b/scipost_django/profiles/models.py
index ca3b4193e7669763017e5a4d89e4dd7fd29ff113..b7ee85663d2f71f3d3d5f574ae1cd8e3b3ad67e6 100644
--- a/scipost_django/profiles/models.py
+++ b/scipost_django/profiles/models.py
@@ -85,6 +85,11 @@ class Profile(models.Model):
     class Meta:
         ordering = ['last_name']
 
+    def __str__(self):
+        return '%s, %s %s' % (self.last_name,
+                              self.get_title_display() if self.title != None else '',
+                              self.first_name)
+
     @property
     def roles(self):
         try:
@@ -92,7 +97,7 @@ class Profile(models.Model):
         except (KeyError, Contributor.DoesNotExist):
             return None
 
-    def __str__(self):
+    def str_with_roles(self):
         r = self.roles
         return '%s, %s %s%s' % (self.last_name,
                                 self.get_title_display() if self.title != None else '',
diff --git a/scipost_django/profiles/templates/profiles/_hx_profile_dynsel_list.html b/scipost_django/profiles/templates/profiles/_hx_profile_dynsel_list.html
index d5f6043919d2c1400149f4985d5451470d4b0e54..1a850fd67a444afef387ccd5a0d68a9ba27cfb88 100644
--- a/scipost_django/profiles/templates/profiles/_hx_profile_dynsel_list.html
+++ b/scipost_django/profiles/templates/profiles/_hx_profile_dynsel_list.html
@@ -1,6 +1,6 @@
 {% load profiles_extras %}
 
-<ul class="list list-unstyled">
+<ul class="list list-unstyled dynsel-list">
   {% for profile in profiles|slice:":11" %}
     {% if forloop.counter == 11 %}
       <li>&emsp;...</li>
@@ -8,7 +8,8 @@
       <li class="m-1">
 	<a
 	    hx-get="{% profile_dynsel_action_url profile %}"
-	    hx-target="#profiles"
+	    hx-target="#{{ action_target_element_id }}"
+	    hx-indicator="#{{ action_target_element_id }}-indicator"
 	>
 	  {{ profile }}
 	</a>
diff --git a/scipost_django/profiles/views.py b/scipost_django/profiles/views.py
index a5f695e888f225dcdd7e4325d686705a78e02df1..94e383477ac2c8cc26a7f145e5c5a315f8d48904 100644
--- a/scipost_django/profiles/views.py
+++ b/scipost_django/profiles/views.py
@@ -300,7 +300,9 @@ def _hx_profile_dynsel_list(request):
     context = {
         'profiles': profiles,
         'action_url_name': form.cleaned_data['action_url_name'],
-        'action_url_base_kwargs': form.cleaned_data['action_url_base_kwargs'],
+        'action_url_base_kwargs': (form.cleaned_data['action_url_base_kwargs']
+                                   if 'action_url_base_kwargs' in form.cleaned_data else {}),
+        'action_target_element_id': form.cleaned_data['action_target_element_id'],
     }
     return render(request, 'profiles/_hx_profile_dynsel_list.html', context)
 
diff --git a/scipost_django/scipost/models.py b/scipost_django/scipost/models.py
index bd4678e12be716f7917b36d33a92a0f114e602fe..6b9311eb36a13bfeefc2ff1937fdd00fdc52ba31 100644
--- a/scipost_django/scipost/models.py
+++ b/scipost_django/scipost/models.py
@@ -159,6 +159,22 @@ class Contributor(models.Model):
     def is_active_senior_fellow(self):
         return self.fellowships.active().senior().exists()
 
+    def session_fellowship(self, request):
+        """Return session's fellowship, if any; if Fellow, set session_fellowship_id if not set."""
+        fellowships = self.fellowships.active()
+        if fellowships.exists():
+            if request.session['session_fellowship_id']:
+                from colleges.models import Fellowship
+                try:
+                    return self.fellowships.active().get(pk=request.session['session_fellowship_id'])
+                except Fellowship.DoesNotExist:
+                    return None
+            # set the session's fellowship_id to default
+            fellowship = fellowships.first()
+            request.session['session_fellowship_id'] = fellowship.id
+            return fellowship
+        return None
+
     @property
     def is_vetting_editor(self):
         """Check if Contributor is a Vetting Editor."""
diff --git a/scipost_django/scipost/static/scipost/assets/css/_dynsel.scss b/scipost_django/scipost/static/scipost/assets/css/_dynsel.scss
new file mode 100644
index 0000000000000000000000000000000000000000..c9f0b364c54df9e953db482aa1e39681077b06e7
--- /dev/null
+++ b/scipost_django/scipost/static/scipost/assets/css/_dynsel.scss
@@ -0,0 +1,14 @@
+
+.dynsel-list {
+    border: 2px gray;
+    background-color: #e0e0e0;
+    padding: 1rem;
+}
+
+.dynsel-list > li > a {
+    padding: 0.2rem;
+}
+
+.dynsel-list > li > a:hover {
+    background-color: #b0b0b0;
+}
diff --git a/scipost_django/scipost/static/scipost/assets/css/style.scss b/scipost_django/scipost/static/scipost/assets/css/style.scss
index 0f6dcd2e6d0e3511447365b53a9f3de757750dd7..604b806a485b3fa758c8a34ea1a7669cff8e5669 100644
--- a/scipost_django/scipost/static/scipost/assets/css/style.scss
+++ b/scipost_django/scipost/static/scipost/assets/css/style.scss
@@ -48,6 +48,7 @@
 @import "general";
 @import "colleges";
 @import "comments";
+@import "dynsel";
 @import "icons";
 @import "journals";
 @import "personal_page";
diff --git a/scipost_django/scipost/views.py b/scipost_django/scipost/views.py
index d62b6df9631f0a08bbb0f56c3b2c2cbcbcb189f2..348aefd3d091ea138e832d2602bc8963b9835556 100644
--- a/scipost_django/scipost/views.py
+++ b/scipost_django/scipost/views.py
@@ -730,10 +730,13 @@ class SciPostLoginView(LoginView):
         return self.request.GET
 
     def get_success_url(self):
-        """Add the `acad_field_view` item to session."""
+        """Add items to session variables."""
         self.request.session['session_acad_field_slug'] = \
             self.request.user.contributor.profile.acad_field.slug if \
             self.request.user.contributor.profile.acad_field else ''
+        if self.request.user.contributor.fellowships.active():
+            self.request.session['session_fellowship_id'] = \
+                self.request.user.contributor.fellowships.active().first().id
         return super().get_success_url()
 
     def get_redirect_url(self):
diff --git a/scipost_django/series/views.py b/scipost_django/series/views.py
index 544b1825b2b4f34eabdaf592acf36caaac2eff36..c91bf6b93745dec94824e5e584a55f0bad637266 100644
--- a/scipost_django/series/views.py
+++ b/scipost_django/series/views.py
@@ -49,7 +49,8 @@ def _hx_collection_expected_authors(request, slug):
     form = ProfileDynSelForm(
         initial={
             'action_url_name': 'series:_hx_collection_expected_author_action',
-            'action_url_base_kwargs': {'slug': collection.slug, 'action': 'add'}
+            'action_url_base_kwargs': {'slug': collection.slug, 'action': 'add'},
+            'action_target_element_id': 'profiles'
         }
     )
     context = {
@@ -84,7 +85,8 @@ def _hx_collection_publications(request, slug):
     form = PublicationDynSelForm(
         initial={
             'action_url_name': 'series:_hx_collection_publication_action',
-            'action_url_base_kwargs': {'slug': collection.slug, 'action': 'add'}
+            'action_url_base_kwargs': {'slug': collection.slug, 'action': 'add'},
+            'action_target_element_id': 'publications'
         }
     )
     context = {
diff --git a/scipost_django/submissions/permissions.py b/scipost_django/submissions/permissions.py
deleted file mode 100644
index b9cd3d3f0a00ab133d82a10d05f417c341186651..0000000000000000000000000000000000000000
--- a/scipost_django/submissions/permissions.py
+++ /dev/null
@@ -1,14 +0,0 @@
-__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
-__license__ = "AGPL v3"
-
-
-from colleges.models import Fellowship
-
-def is_edadmin_or_senior_fellow(user):
-    if not user.has_perm('scipost.can_run_pre_screening'):
-        try:
-            fellow = Fellowship.objects.get(contributor__user=user)
-            return fellow.senior
-        except:
-            return False
-    return True
diff --git a/scipost_django/submissions/views.py b/scipost_django/submissions/views.py
index cb61a2649ada965cdcb8bf7bad96e24eff80b1c9..7c414b900e7728134f73dd562fddaa99ac2b351c 100644
--- a/scipost_django/submissions/views.py
+++ b/scipost_django/submissions/views.py
@@ -60,11 +60,13 @@ from .forms import (
     SubmissionTargetJournalForm, SubmissionTargetProceedingsForm, SubmissionPreprintFileForm,
     SubmissionPrescreeningForm,
     PreassignEditorsFormSet, SubmissionReassignmentForm)
-from .permissions import is_edadmin_or_senior_fellow
 from .utils import SubmissionUtils
 
 from colleges.models import PotentialFellowship, Fellowship
-from colleges.permissions import fellowship_required, fellowship_or_admin_required
+from colleges.permissions import (
+    fellowship_required, fellowship_or_admin_required,
+    is_edadmin_or_senior_fellow
+)
 from comments.forms import CommentForm
 from common.helpers import get_new_secrets_key
 from common.utils import workdays_between
diff --git a/scipost_django/templates/bi/cone-striped.html b/scipost_django/templates/bi/cone-striped.html
new file mode 100644
index 0000000000000000000000000000000000000000..e6e66d232742fbe9c4182fb525b1fcb41e2c540d
--- /dev/null
+++ b/scipost_django/templates/bi/cone-striped.html
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-cone-striped" viewBox="0 0 16 16">
+  <path d="m9.97 4.88.953 3.811C10.159 8.878 9.14 9 8 9c-1.14 0-2.158-.122-2.923-.309L6.03 4.88C6.635 4.957 7.3 5 8 5s1.365-.043 1.97-.12zm-.245-.978L8.97.88C8.718-.13 7.282-.13 7.03.88L6.275 3.9C6.8 3.965 7.382 4 8 4c.618 0 1.2-.036 1.725-.098zm4.396 8.613a.5.5 0 0 1 .037.96l-6 2a.5.5 0 0 1-.316 0l-6-2a.5.5 0 0 1 .037-.96l2.391-.598.565-2.257c.862.212 1.964.339 3.165.339s2.303-.127 3.165-.339l.565 2.257 2.391.598z"/>
+</svg>