diff --git a/production/admin.py b/production/admin.py
index 15b7b61d8241e0c94276da31c3f9a9af0c5b185b..6bf8d0f464ebced0f910e8a02a32e9af9af7f893 100644
--- a/production/admin.py
+++ b/production/admin.py
@@ -28,4 +28,5 @@ class ProductionStreamAdmin(admin.ModelAdmin):
     )
 
 
+admin.site.register(ProductionUser)
 admin.site.register(ProductionStream, ProductionStreamAdmin)
diff --git a/production/forms.py b/production/forms.py
index fec37e4b4ba232b9432bfc52c173229c640c709a..2f43929a213f7b7eb365ee34305cd9485b195e4e 100644
--- a/production/forms.py
+++ b/production/forms.py
@@ -1,6 +1,6 @@
 from django import forms
 
-from .models import ProductionEvent
+from .models import ProductionUser, ProductionStream, ProductionEvent
 
 
 class ProductionEventForm(forms.ModelForm):
@@ -15,3 +15,22 @@ class ProductionEventForm(forms.ModelForm):
             'comments': forms.Textarea(attrs={'rows': 4}),
             'duration': forms.TextInput(attrs={'placeholder': 'HH:MM:SS'})
         }
+
+
+class AssignOfficerForm(forms.ModelForm):
+    officer = forms.ModelChoiceField(queryset=ProductionUser.objects.all())
+
+    class Meta:
+        model = ProductionStream
+        fields = ()
+
+    def clean_officer(self):
+        officer = self.cleaned_data['officer']
+        if officer in self.instance.officers.all():
+            self.add_error('officer', 'Officer already assigned to Stream')
+        return officer
+
+    def save(self, commit=True):
+        officer = self.cleaned_data['officer']
+        self.instance.officers.add(officer)
+        return self.instance
diff --git a/production/managers.py b/production/managers.py
index ca9b5d0ba99fb56cdce96d96963337445f27d16e..ad3234c9c317d699167588756ac2b802b5cd03ea 100644
--- a/production/managers.py
+++ b/production/managers.py
@@ -3,13 +3,16 @@ from django.db import models
 from .constants import PRODUCTION_STREAM_COMPLETED, PRODUCTION_STREAM_ONGOING
 
 
-class ProductionStreamManager(models.Manager):
+class ProductionStreamQuerySet(models.QuerySet):
     def completed(self):
         return self.filter(status=PRODUCTION_STREAM_COMPLETED)
 
     def ongoing(self):
         return self.filter(status=PRODUCTION_STREAM_ONGOING)
 
+    def filter_for_user(self, production_user):
+        return self.filter(officers=production_user)
+
 
 class ProductionEventManager(models.Manager):
     def get_my_events(self, current_contributor):
diff --git a/production/migrations/0012_productionstream_officers.py b/production/migrations/0012_productionstream_officers.py
new file mode 100644
index 0000000000000000000000000000000000000000..37835f8f2b46f2c8f4b02aa52b031145ca3cc27a
--- /dev/null
+++ b/production/migrations/0012_productionstream_officers.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-09-14 18:20
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('production', '0011_productionuser'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='productionstream',
+            name='officers',
+            field=models.ManyToManyField(blank=True, to='production.ProductionUser'),
+        ),
+    ]
diff --git a/production/models.py b/production/models.py
index 3540573dd4b178915446f647ff2aaa7183a0a6ff..2dc2ef978ceecf4da037bc6690d542f6ade1f51f 100644
--- a/production/models.py
+++ b/production/models.py
@@ -4,7 +4,7 @@ from django.contrib.auth.models import User
 from django.utils import timezone
 
 from .constants import PRODUCTION_STREAM_STATUS, PRODUCTION_STREAM_ONGOING, PRODUCTION_EVENTS
-from .managers import ProductionStreamManager, ProductionEventManager
+from .managers import ProductionStreamQuerySet, ProductionEventManager
 
 
 class ProductionUser(models.Model):
@@ -12,7 +12,8 @@ class ProductionUser(models.Model):
     Production Officers will have a ProductionUser object related to their account
     to relate all production related actions to.
     """
-    user = models.OneToOneField(User, on_delete=models.PROTECT, unique=True)
+    user = models.OneToOneField(User, on_delete=models.PROTECT, unique=True,
+                                related_name='production_user')
 
     # objects = ProductionUserQuerySet.as_manager()  -- Not implemented yet
 
@@ -26,8 +27,10 @@ class ProductionStream(models.Model):
     closed = models.DateTimeField(default=timezone.now)
     status = models.CharField(max_length=32,
                               choices=PRODUCTION_STREAM_STATUS, default=PRODUCTION_STREAM_ONGOING)
+    officers = models.ManyToManyField('production.ProductionUser', blank=True,
+                                      related_name='streams')
 
-    objects = ProductionStreamManager()
+    objects = ProductionStreamQuerySet.as_manager()
 
     def __str__(self):
         return '{arxiv}, {title}'.format(arxiv=self.submission.arxiv_identifier_w_vn_nr,
diff --git a/production/templates/production/partials/production_events.html b/production/templates/production/partials/production_events.html
index e7d168b13c52553b541c78e7768c03fb95d45049..664d620d8117a709041c44e4bc5f9f9a8bf4f6d3 100644
--- a/production/templates/production/partials/production_events.html
+++ b/production/templates/production/partials/production_events.html
@@ -21,3 +21,8 @@
         <li>No events were found.</li>
     {% endfor %}
 </ul>
+
+{% if stream.total_duration %}
+    <hr class="sm">
+    <p class="pl-4 ml-3">Total duration for this stream: <strong>{{ stream.total_duration|duration }}</strong></p>
+{% endif %}
diff --git a/production/templates/production/partials/production_stream_card.html b/production/templates/production/partials/production_stream_card.html
index 94827918f10e00bb528393ffcf70080d4c470209..f1902c96ed5c98f1f963e14c74d3c532abeec881 100644
--- a/production/templates/production/partials/production_stream_card.html
+++ b/production/templates/production/partials/production_stream_card.html
@@ -1,29 +1,51 @@
 {% load bootstrap %}
-{% load scipost_extras %}
 
 <div class="w-100" id="stream_{{stream.id}}">
     {% include 'submissions/_submission_card_content_sparse.html' with submission=stream.submission %}
 </div>
 <div class="card-body">
   <div class="row">
-    <div class="{% if form %}col-lg-7{% else %}col-12{% endif %}">
+    <div class="{% if prodevent_form %}col-lg-7{% else %}col-12{% endif %}">
+      <h3>Officers</h3>
+      <ul>
+          {% for officer in stream.officers.all %}
+            <li>{{ officer }}{% if perms.scipost.can_assign_production_officer %} &middot; <a href="{% url 'production:remove_officer' stream_id=stream.id officer_id=officer.id %}" class="text-danger">Remove from stream</a>{% endif %}</li>
+          {% empty %}
+            <li>No Officer assigned yet.</li>
+          {% endfor %}
+      </ul>
       <h3>Events</h3>
       {% include 'production/partials/production_events.html' with events=stream.productionevent_set.all %}
       <br/>
 
-      {% if stream.total_duration %}
-          <h3>Total duration for this stream: {{ stream.total_duration|duration }}</h3>
-      {% endif %}
-      {% if perms.scipost.can_publish_accepted_submission %}
-          <h3><a href="{% url 'production:mark_as_completed' stream_id=stream.id %}">Mark this stream as completed</a></h3>
+
+      {% if perms.scipost.can_publish_accepted_submission or perms.scipost.can_assign_production_officer %}
+      <h3>Actions</h3>
+          <ul class="">
+              {% if perms.scipost.can_assign_production_officer and assignment_form %}
+                <li>
+                    <a href="javascript:;" data-toggle="toggle" data-target="#add_officer_{{stream.id}}">Assign Production Officer to this stream</a>
+                    <div id="add_officer_{{stream.id}}" style="display: none;">
+                        <form class="my-3" action="{% url 'production:add_officer' stream_id=stream.id %}" method="post">
+                          	{% csrf_token %}
+                          	{{ assignment_form|bootstrap_inline }}
+                          	<input type="submit" class="btn btn-outline-primary" name="submit" value="Add officer">
+                        </form>
+                    </div>
+                </li>
+              {% endif %}
+              {% if perms.scipost.can_publish_accepted_submission %}
+                <li><a href="{% url 'production:mark_as_completed' stream_id=stream.id %}">Mark this stream as completed</a></li>
+              {% endif %}
+          </ul>
       {% endif %}
     </div>
-    {% if form %}
+    {% if prodevent_form %}
         <div class="col-lg-5 mt-4 mt-lg-0">
           <h3>Add an event to this production stream:</h3>
           <form action="{% url 'production:add_event' stream_id=stream.id %}" method="post">
         	{% csrf_token %}
-        	{{ form|bootstrap }}
+        	{{ prodevent_form|bootstrap }}
         	<input type="submit" class="btn btn-secondary" name="submit" value="Submit">
           </form>
         </div>
diff --git a/production/templates/production/production.html b/production/templates/production/production.html
index 263bd72112613f0521e42b38e401a1d51e0dd7d6..7578c131db28dc0c6dde2a2111a9e6d95fa7ffaa 100644
--- a/production/templates/production/production.html
+++ b/production/templates/production/production.html
@@ -71,7 +71,7 @@
         	</tr>
         	<tr id="collapse{{ stream.id }}" class="collapse" role="tabpanel" aria-labelledby="heading{{ stream.id }}" style="background-color: #fff;">
         	  <td colspan="5">
-                    {% include 'production/partials/production_stream_card.html' with stream=stream form=prodevent_form %}
+                    {% include 'production/partials/production_stream_card.html' with stream=stream prodevent_form=prodevent_form assignment_form=assignment_form %}
         	  </td>
         	</tr>
     	{% empty %}
diff --git a/production/urls.py b/production/urls.py
index cc5bf527b92c39690aabca17762514101ff57daf..95559d171c4eab56ddd74d36570683952face65b 100644
--- a/production/urls.py
+++ b/production/urls.py
@@ -7,6 +7,10 @@ urlpatterns = [
     url(r'^completed$', production_views.completed, name='completed'),
     url(r'^streams/(?P<stream_id>[0-9]+)/events/add$',
         production_views.add_event, name='add_event'),
+    url(r'^streams/(?P<stream_id>[0-9]+)/officer/add$',
+        production_views.add_officer, name='add_officer'),
+    url(r'^streams/(?P<stream_id>[0-9]+)/officer/(?P<officer_id>[0-9]+)/remove$',
+        production_views.remove_officer, name='remove_officer'),
     url(r'^streams/(?P<stream_id>[0-9]+)/mark_completed$',
         production_views.mark_as_completed, name='mark_as_completed'),
     url(r'^events/(?P<event_id>[0-9]+)/edit',
diff --git a/production/views.py b/production/views.py
index 517c09c116ecdd90c20cc0dcdc5ba199f194a202..5c07863b41a8ba8002a4f53a7934248c0e074642 100644
--- a/production/views.py
+++ b/production/views.py
@@ -1,7 +1,6 @@
 import datetime
 
 from django.contrib import messages
-from django.contrib.auth.models import Group
 from django.core.urlresolvers import reverse
 from django.shortcuts import get_object_or_404, render, redirect
 from django.utils import timezone
@@ -11,8 +10,8 @@ from django.views.generic.edit import UpdateView, DeleteView
 from guardian.decorators import permission_required
 
 from .constants import PRODUCTION_STREAM_COMPLETED
-from .models import ProductionStream, ProductionEvent
-from .forms import ProductionEventForm
+from .models import ProductionUser, ProductionStream, ProductionEvent
+from .forms import ProductionEventForm, AssignOfficerForm
 from .signals import notify_stream_completed
 
 from scipost.models import Contributor
@@ -28,14 +27,19 @@ def production(request):
     Overview page for the production process.
     All papers with accepted but not yet published status are included here.
     """
-    streams = ProductionStream.objects.ongoing().order_by('opened')
+    if request.user.has_perm('scipost.can_assign_production_officer'):
+        streams = ProductionStream.objects.ongoing().order_by('opened')
+    else:
+        streams = ProductionStream.objects.ongoing().filter_for_user(request.user.production_user).order_by('opened')
     prodevent_form = ProductionEventForm()
+    assignment_form = AssignOfficerForm()
     ownevents = ProductionEvent.objects.filter(
         noted_by=request.user.contributor,
         duration__gte=datetime.timedelta(minutes=1)).order_by('-noted_on')
     context = {
         'streams': streams,
         'prodevent_form': prodevent_form,
+        'assignment_form': assignment_form,
         'ownevents': ownevents,
     }
     if request.user.has_perm('scipost.can_view_timesheets'):
@@ -68,6 +72,33 @@ def add_event(request, stream_id):
     return redirect(reverse('production:production'))
 
 
+@permission_required('scipost.can_assign_production_officer', return_403=True)
+def add_officer(request, stream_id):
+    stream = get_object_or_404(ProductionStream.objects.ongoing(), pk=stream_id)
+    form = AssignOfficerForm(request.POST or None, instance=stream)
+    if form.is_valid():
+        form.save()
+        messages.success(request, 'Officer {officer} has been assigned.'.format(
+            officer=form.cleaned_data.get('officer')))
+    else:
+        for key, error in form.errors.items():
+            messages.warning(request, error[0])
+    return redirect(reverse('production:production'))
+
+
+@permission_required('scipost.can_assign_production_officer', return_403=True)
+def remove_officer(request, stream_id, officer_id):
+    stream = get_object_or_404(ProductionStream.objects.ongoing(), pk=stream_id)
+    try:
+        officer = stream.officers.get(pk=officer_id)
+    except ProductionUser.DoesNotExist:
+        return redirect(reverse('production:production'))
+
+    stream.officers.remove(officer)
+    messages.success(request, 'Officer {officer} has been removed.'.format(officer=officer))
+    return redirect(reverse('production:production'))
+
+
 @method_decorator(permission_required('scipost.can_view_production', raise_exception=True),
                   name='dispatch')
 class UpdateEventView(UpdateView):