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/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/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..7d95293ec745e6f93c4b23a0f7bea5ca9dff79be
--- /dev/null
+++ b/scipost_django/colleges/models/nomination.py
@@ -0,0 +1,208 @@
+__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
+__license__ = "AGPL v3"
+
+
+from django.db import models
+from django.utils import timezone
+
+
+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)
+
+    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'Nomination of {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 = 'Fellowhips Nomination Events'
+
+    def __str__(self):
+        return f'Event for {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()
+
+    class Meta:
+        ordering = [
+            'nomination__profile__last_name'
+        ]
+        verbose_name_plural = 'Fellowship Nomination Voting Rounds'
+
+    def __str__(self):
+        return (f'Voting round ({voting_opens.strftime("%Y-%m-%d")} -'
+                f' {voting_deadline.strftime("%Y-%m-%d")}) for {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(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)
+
+    class Meta:
+        ordering = ['nomination',]
+        verbose_name_plural = 'Fellowship Nomination Decisions'
+
+    def __str__(self):
+        return f'Decision for {nomination}: {self.get_outcome_display()}'
+
+
+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)
+
+    class Meta:
+        ordering = ['nomination',]
+        verbose_name_plural = 'Fellowship Invitations'
+
+    def __str__(self):
+        return f'Invitation for {nomination}'