diff --git a/profiles/constants.py b/profiles/constants.py
index fb7c120de3db2c927f5fbab193a24b4adffff12c..b9ac6f92693bab316ddad1cc133615f6deb7a857 100644
--- a/profiles/constants.py
+++ b/profiles/constants.py
@@ -9,3 +9,32 @@ PROFILE_NON_DUPLICATE_REASONS = (
     (DIFFERENT_PEOPLE, 'These are different people'),
     (MULTIPLE_ALLOWED, 'Multiple Profiles allowed for this person'),
 )
+
+
+AFFILIATION_CATEGORY_EMPLOYED_PROF_FULL = 'employed_prof_full'
+AFFILIATION_CATEGORY_EMPLOYED_PROF_ASSOCIATE = 'employed_prof_associate'
+AFFILIATION_CATEGORY_EMPLOYED_PROF_ASSISTANT = 'employed_prof_assistant'
+AFFILIATION_CATEGORY_EMPLOYED_PROF_EMERITUS = 'employed_prof_emeritus'
+AFFILIATION_CATEGORY_EMPLOYED_PERMANENT_STAFF = 'employed_permanent_staff'
+AFFILIATION_CATEGORY_EMPLOYED_FIXED_TERM_STAFF = 'employed_fixed_term_staff'
+AFFILIATION_CATEGORY_EMPLOYED_TENURE_TRACK = 'employed_tenure_track'
+AFFILIATION_CATEGORY_EMPLOYED_POSTDOC = 'employed_postdoc'
+AFFILIATION_CATEGORY_EMPLOYED_PhD = 'employed_phd'
+AFFILIATION_CATEGORY_ASSOCIATE_SCIENTIST = 'associate_scientist'
+AFFILIATION_CATEGORY_CONSULTANT = 'consultant'
+AFFILIATION_CATEGORY_VISITOR = 'visitor'
+
+AFFILIATION_CATEGORIES = (
+    (AFFILIATION_CATEGORY_EMPLOYED_PROF_FULL, 'Full Professor'),
+    (AFFILIATION_CATEGORY_EMPLOYED_PROF_ASSOCIATE, 'Associate Professor'),
+    (AFFILIATION_CATEGORY_EMPLOYED_PROF_ASSISTANT, 'Assistant Professor'),
+    (AFFILIATION_CATEGORY_EMPLOYED_PROF_EMERITUS, 'Emeritus Professor'),
+    (AFFILIATION_CATEGORY_EMPLOYED_PERMANENT_STAFF, 'Permanent Staff'),
+    (AFFILIATION_CATEGORY_EMPLOYED_FIXED_TERM_STAFF, 'Fixed Term Staff'),
+    (AFFILIATION_CATEGORY_EMPLOYED_TENURE_TRACK, 'Tenure Tracker'),
+    (AFFILIATION_CATEGORY_EMPLOYED_POSTDOC, 'Postdoctoral Researcher'),
+    (AFFILIATION_CATEGORY_EMPLOYED_PhD, 'PhD candidate'),
+    (AFFILIATION_CATEGORY_ASSOCIATE_SCIENTIST, 'Associate Scientist'),
+    (AFFILIATION_CATEGORY_CONSULTANT, 'Consultant'),
+    (AFFILIATION_CATEGORY_VISITOR, 'Visotor'),
+)
diff --git a/profiles/migrations/0018_affiliation.py b/profiles/migrations/0018_affiliation.py
new file mode 100644
index 0000000000000000000000000000000000000000..83b3d0301775c1b37145ac5e706c477daba79e23
--- /dev/null
+++ b/profiles/migrations/0018_affiliation.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2019-03-27 14:14
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('organizations', '0010_auto_20190223_1406'),
+        ('profiles', '0017_auto_20190126_2058'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Affiliation',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('category', models.CharField(choices=[('employed_prof_full', 'Full Professor'), ('employed_prof_associate', 'Associate Professor'), ('employed_prof_assistant', 'Assistant Professor'), ('employed_prof_emeritus', 'Emeritus Professor'), ('employed_permanent_staff', 'Permanent Staff'), ('employed_fixed_term_staff', 'Fixed Term Staff'), ('employed_tenure_track', 'Tenure Tracker'), ('employed_postdoc', 'Postdoctoral Researcher'), ('employed_phd', 'PhD candidate'), ('associate_scientist', 'Associate Scientist'), ('consultant', 'Consultant'), ('visitor', 'Visotor')], help_text='Select the most suitable category', max_length=64)),
+                ('description', models.CharField(max_length=256)),
+                ('date_from', models.DateField(blank=True, null=True)),
+                ('date_until', models.DateField(blank=True, null=True)),
+                ('organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='affiliations', to='organizations.Organization')),
+                ('profile', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='affiliations', to='profiles.Profile')),
+            ],
+            options={
+                'ordering': ['profile__user__last_name', 'profile__user__first_name', 'date_until'],
+                'default_related_name': 'affiliations',
+            },
+        ),
+    ]
diff --git a/profiles/models.py b/profiles/models.py
index 2a7e9b5af27a07ebd2069a4c7ec0265d2082896c..fa47729d4545f403ca4f2ce84c053022d3419184 100644
--- a/profiles/models.py
+++ b/profiles/models.py
@@ -18,7 +18,7 @@ from journals.models import Publication, PublicationAuthorsTable
 from ontology.models import Topic
 from theses.models import ThesisLink
 
-from .constants import PROFILE_NON_DUPLICATE_REASONS
+from .constants import PROFILE_NON_DUPLICATE_REASONS, AFFILIATION_CATEGORIES
 from .managers import ProfileQuerySet
 
 
@@ -164,3 +164,46 @@ class ProfileNonDuplicates(models.Model):
     @property
     def full_name(self):
         return '%s%s' % (self.profiles.first().last_name, self.profiles.first().first_name)
+
+
+
+################
+# Affiliations #
+################
+
+class Affiliation(models.Model):
+    """
+    Link between a Profile and an Organization, for a specified time interval.
+
+    Fields:
+    * profile
+    * organization
+    * description
+    * date_from
+    * date_until
+
+    Affiliations can overlap in time.
+
+    Ideally, each Profile would have at least one valid Affiliation at each moment
+    of time during the whole duration of that person's career.
+    """
+    profile = models.ForeignKey('profiles.Profile', on_delete=models.CASCADE,
+                                related_name='affiliations')
+    organization = models.ForeignKey('organizations.Organization', on_delete=models.CASCADE,
+                                     related_name='affiliations')
+    category = models.CharField(max_length=64, choices=AFFILIATION_CATEGORIES,
+                                help_text='Select the most suitable category')
+    description = models.CharField(max_length=256)
+    date_from = models.DateField(blank=True, null=True)
+    date_until = models.DateField(blank=True, null=True)
+
+    class Meta:
+        default_related_name = 'affiliations'
+        ordering = ['profile__user__last_name', 'profile__user__first_name',
+                    'date_until']
+
+    def __str__(self):
+        return '{ profile }, { organization } [{ date_from } to { date_until }]'.format(
+            profile=self.profile, organization=self.organization,
+            date_from=self.date_from.strftime('Y-m-d'),
+            date_until=self.date_until.strftime('Y-m-d'))