From 2f9f3f9f4b9aaf3537b8e2e71b43c4a5fc799fa1 Mon Sep 17 00:00:00 2001
From: Jorran de Wit <jorrandewit@outlook.com>
Date: Thu, 14 Sep 2017 23:17:55 +0200
Subject: [PATCH] Production renovation

---
 production/admin.py                           |  2 +-
 production/apps.py                            |  5 +-
 production/forms.py                           |  8 ++
 production/managers.py                        |  4 +-
 .../migrations/0013_auto_20170914_2220.py     | 32 +++++++
 .../0014_productionevent_noted_by.py          | 22 +++++
 .../migrations/0015_auto_20170914_2237.py     | 48 ++++++++++
 ...ve_productionevent_noted_by_contributor.py | 19 ++++
 production/models.py                          |  6 +-
 production/permissions.py                     | 11 +++
 production/signals.py                         | 23 ++---
 .../templates/production/completed.html       |  2 +-
 .../partials/production_events.html           |  2 +-
 .../partials/production_stream_card.html      |  2 +-
 .../templates/production/production.html      | 92 ++++++++++++-------
 production/urls.py                            |  1 +
 production/views.py                           | 60 ++++++++----
 17 files changed, 264 insertions(+), 75 deletions(-)
 create mode 100644 production/migrations/0013_auto_20170914_2220.py
 create mode 100644 production/migrations/0014_productionevent_noted_by.py
 create mode 100644 production/migrations/0015_auto_20170914_2237.py
 create mode 100644 production/migrations/0016_remove_productionevent_noted_by_contributor.py
 create mode 100644 production/permissions.py

diff --git a/production/admin.py b/production/admin.py
index 6bf8d0f46..7814dafbd 100644
--- a/production/admin.py
+++ b/production/admin.py
@@ -4,7 +4,7 @@ from .models import ProductionStream, ProductionEvent, ProductionUser
 
 
 def event_count(obj):
-    return obj.productionevent_set.count()
+    return obj.events.count()
 
 
 class ProductionUserInline(admin.StackedInline):
diff --git a/production/apps.py b/production/apps.py
index 549c6bd6b..8c17f2a4c 100644
--- a/production/apps.py
+++ b/production/apps.py
@@ -8,7 +8,6 @@ class ProductionConfig(AppConfig):
     def ready(self):
         super().ready()
 
-        from .models import ProductionStream, ProductionEvent
-        from .signals import notify_new_stream, notify_new_event
-        post_save.connect(notify_new_stream, sender=ProductionStream)
+        from .models import ProductionEvent
+        from .signals import notify_new_event
         post_save.connect(notify_new_event, sender=ProductionEvent)
diff --git a/production/forms.py b/production/forms.py
index 2f43929a2..a81645229 100644
--- a/production/forms.py
+++ b/production/forms.py
@@ -34,3 +34,11 @@ class AssignOfficerForm(forms.ModelForm):
         officer = self.cleaned_data['officer']
         self.instance.officers.add(officer)
         return self.instance
+
+
+class UserToOfficerForm(forms.ModelForm):
+    class Meta:
+        model = ProductionUser
+        fields = (
+            'user',
+        )
diff --git a/production/managers.py b/production/managers.py
index ad3234c9c..93b64b4fa 100644
--- a/production/managers.py
+++ b/production/managers.py
@@ -15,5 +15,5 @@ class ProductionStreamQuerySet(models.QuerySet):
 
 
 class ProductionEventManager(models.Manager):
-    def get_my_events(self, current_contributor):
-        return self.filter(noted_by=current_contributor)
+    def get_my_events(self, production_user):
+        return self.filter(noted_by=production_user)
diff --git a/production/migrations/0013_auto_20170914_2220.py b/production/migrations/0013_auto_20170914_2220.py
new file mode 100644
index 000000000..8eaa1babf
--- /dev/null
+++ b/production/migrations/0013_auto_20170914_2220.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-09-14 20:20
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('production', '0012_productionstream_officers'),
+    ]
+
+    operations = [
+        migrations.RenameField(
+            model_name='productionevent',
+            old_name='noted_by',
+            new_name='noted_by_contributor',
+        ),
+        migrations.AlterField(
+            model_name='productionstream',
+            name='officers',
+            field=models.ManyToManyField(blank=True, related_name='streams', to='production.ProductionUser'),
+        ),
+        migrations.AlterField(
+            model_name='productionuser',
+            name='user',
+            field=models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='production_user', to=settings.AUTH_USER_MODEL),
+        ),
+    ]
diff --git a/production/migrations/0014_productionevent_noted_by.py b/production/migrations/0014_productionevent_noted_by.py
new file mode 100644
index 000000000..1f728d34d
--- /dev/null
+++ b/production/migrations/0014_productionevent_noted_by.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-09-14 20:20
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('production', '0013_auto_20170914_2220'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='productionevent',
+            name='noted_by',
+            field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='production.ProductionUser'),
+            preserve_default=False,
+        ),
+    ]
diff --git a/production/migrations/0015_auto_20170914_2237.py b/production/migrations/0015_auto_20170914_2237.py
new file mode 100644
index 000000000..adb4fcbfb
--- /dev/null
+++ b/production/migrations/0015_auto_20170914_2237.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-09-14 20:37
+from __future__ import unicode_literals
+
+from django.contrib.auth.models import Group, User
+from django.db import migrations
+
+
+def contributor_to_officer(apps, schema_editor):
+    # Create ProductionUser for all current Officers
+    ProductionUser = apps.get_model('production', 'ProductionUser')
+    officers = Group.objects.get(name='Production Officers')
+    for user in officers.user_set.all():
+        ProductionUser.objects.get_or_create(user__id=user.id)
+    print('\n  - Production Officers transfered to ProductionUser')
+
+    # Transfer all Events
+    ProductionEvent = apps.get_model('production', 'ProductionEvent')
+    for event in ProductionEvent.objects.all():
+        user = User.objects.get(contributor__id=event.noted_by_contributor.id)
+        event.noted_by.id = user.production_user.id
+        event.save()
+    print('  - ProductionEvents updated')
+
+    return
+
+
+def officer_to_contributor(apps, schema_editor):
+    # Transfer all Events
+    ProductionEvent = apps.get_model('production', 'ProductionEvent')
+    for event in ProductionEvent.objects.all():
+        user = User.objects.get(production_user__id=event.noted_by.id)
+        event.noted_by_contributor.id = user.contributor.id
+        event.save()
+    print('\n  - ProductionEvents updated')
+
+    return
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('production', '0014_productionevent_noted_by'),
+    ]
+
+    operations = [
+        migrations.RunPython(contributor_to_officer, officer_to_contributor)
+    ]
diff --git a/production/migrations/0016_remove_productionevent_noted_by_contributor.py b/production/migrations/0016_remove_productionevent_noted_by_contributor.py
new file mode 100644
index 000000000..67736d491
--- /dev/null
+++ b/production/migrations/0016_remove_productionevent_noted_by_contributor.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-09-14 20:46
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('production', '0015_auto_20170914_2237'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='productionevent',
+            name='noted_by_contributor',
+        ),
+    ]
diff --git a/production/models.py b/production/models.py
index 2dc2ef978..1f6784006 100644
--- a/production/models.py
+++ b/production/models.py
@@ -42,16 +42,16 @@ class ProductionStream(models.Model):
         return reverse('production:completed') + '#stream_' + str(self.id)
 
     def total_duration(self):
-        totdur = self.productionevent_set.aggregate(models.Sum('duration'))
+        totdur = self.events.aggregate(models.Sum('duration'))
         return totdur['duration__sum']
 
 
 class ProductionEvent(models.Model):
-    stream = models.ForeignKey(ProductionStream, on_delete=models.CASCADE)
+    stream = models.ForeignKey(ProductionStream, on_delete=models.CASCADE, related_name='events')
     event = models.CharField(max_length=64, choices=PRODUCTION_EVENTS)
     comments = models.TextField(blank=True, null=True)
     noted_on = models.DateTimeField(default=timezone.now)
-    noted_by = models.ForeignKey('scipost.Contributor', on_delete=models.CASCADE)
+    noted_by = models.ForeignKey('production.ProductionUser', on_delete=models.CASCADE)
     duration = models.DurationField(blank=True, null=True)
 
     objects = ProductionEventManager()
diff --git a/production/permissions.py b/production/permissions.py
new file mode 100644
index 000000000..54012de7d
--- /dev/null
+++ b/production/permissions.py
@@ -0,0 +1,11 @@
+from django.contrib.auth.decorators import user_passes_test
+
+
+def is_production_user():
+    """Requires user to be a ProductionUser."""
+    def test(u):
+        if u.is_authenticated():
+            if hasattr(u, 'production_user') and u.production_user:
+                return True
+        return False
+    return user_passes_test(test)
diff --git a/production/signals.py b/production/signals.py
index 6a3102f92..7cdd7872c 100644
--- a/production/signals.py
+++ b/production/signals.py
@@ -1,17 +1,12 @@
-from django.contrib.auth.models import Group, User
-
 from notifications.signals import notify
 
 
-def notify_new_stream(sender, instance, created, **kwargs):
+def notify_new_stream(sender, instance, recipient, **kwargs):
     """
     Notify the production team about a new Production Stream created.
     """
-    if created:
-        production_officers = User.objects.filter(groups__name='Production Officers')
-        editorial_college = Group.objects.get(name='Editorial College')
-        for user in production_officers:
-            notify.send(sender=sender, recipient=user, actor=editorial_college, verb=' accepted a submission. A new productionstream has been started.', target=instance)
+    notify.send(sender=sender, recipient=recipient, actor=sender,
+                verb=' assigned you to a Production Stream.', target=instance)
 
 
 def notify_new_event(sender, instance, created, **kwargs):
@@ -19,15 +14,15 @@ def notify_new_event(sender, instance, created, **kwargs):
     Notify the production team about a new Production Event created.
     """
     if created:
-        production_officers = User.objects.filter(groups__name='Production Officers')
-        for user in production_officers:
-            notify.send(sender=sender, recipient=user, actor=instance.noted_by.user, verb=' created a new Production Event ', target=instance)
+        for officer in instance.stream.officers.all():
+            notify.send(sender=sender, recipient=officer.user, actor=instance.noted_by.user,
+                        verb=' created a new Production Event.', target=instance)
 
 
 def notify_stream_completed(sender, instance, **kwargs):
     """
     Notify the production team about a Production Stream being completed.
     """
-    production_officers = User.objects.filter(groups__name='Production Officers')
-    for user in production_officers:
-        notify.send(sender=sender, recipient=user, actor=sender, verb=' marked Production Stream as completed.', target=instance)
+    for officer in instance.officers.all():
+        notify.send(sender=sender, recipient=officer.user, actor=sender,
+                    verb=' marked Production Stream as completed.', target=instance)
diff --git a/production/templates/production/completed.html b/production/templates/production/completed.html
index 38de0e2ce..bf3507da6 100644
--- a/production/templates/production/completed.html
+++ b/production/templates/production/completed.html
@@ -24,7 +24,7 @@
             <div class="w-100" id="stream_{{stream.id}}">{% include 'submissions/_submission_card_content_sparse.html' with submission=stream.submission %}</div>
             <div class="card-body">
                 <h3>Events</h3>
-                {% include 'production/partials/production_events.html' with events=stream.productionevent_set.all %}
+                {% include 'production/partials/production_events.html' with events=stream.events.all %}
             </div>
         </li>
       {% empty %}
diff --git a/production/templates/production/partials/production_events.html b/production/templates/production/partials/production_events.html
index 664d620d8..dd91d748f 100644
--- a/production/templates/production/partials/production_events.html
+++ b/production/templates/production/partials/production_events.html
@@ -4,7 +4,7 @@
   {% for event in events %}
         <li id="event_{{ event.id }}">
           <p class="mb-0 font-weight-bold">{{ event.get_event_display }}
-              {% if event.noted_by == request.user.contributor %}
+              {% if event.noted_by == request.user.production_user %}
                   &middot; <a href="{% url 'production:update_event' event.id %}">Edit</a>
                   &middot; <a class="text-danger" href="{% url 'production:delete_event' event.id %}">Delete</a>
               {% endif %}
diff --git a/production/templates/production/partials/production_stream_card.html b/production/templates/production/partials/production_stream_card.html
index f1902c96e..baaee1ab5 100644
--- a/production/templates/production/partials/production_stream_card.html
+++ b/production/templates/production/partials/production_stream_card.html
@@ -15,7 +15,7 @@
           {% endfor %}
       </ul>
       <h3>Events</h3>
-      {% include 'production/partials/production_events.html' with events=stream.productionevent_set.all %}
+      {% include 'production/partials/production_events.html' with events=stream.events.all %}
       <br/>
 
 
diff --git a/production/templates/production/production.html b/production/templates/production/production.html
index 7578c131d..841d159b7 100644
--- a/production/templates/production/production.html
+++ b/production/templates/production/production.html
@@ -34,6 +34,11 @@
     	    <a href="#teamtimesheets" class="nav-link" data-toggle="tab">Team Timesheets</a>
     	  </li>
 	  {% endif %}
+      {% if perms.scipost.can_promote_user_to_production_officer %}
+          <li class="nav-item btn btn-secondary">
+            <a href="#officers" class="nav-link" data-toggle="tab">Production Officers</a>
+          </li>
+      {% endif %}
 	</ul>
       </div>
     </div>
@@ -66,8 +71,8 @@
         	  <td>{{ stream.submission.author_list }}</td>
         	  <td>{{ stream.submission.title }}</td>
         	  <td>{{ stream.submission.acceptance_date|date:"Y-m-d" }}</td>
-        	  <td>{{ stream.productionevent_set.last.get_event_display }}</td>
-        	  <td>{{ stream.productionevent_set.last.noted_on }}</td>
+        	  <td>{{ stream.events.last.get_event_display }}</td>
+        	  <td>{{ stream.events.last.noted_on }}</td>
         	</tr>
         	<tr id="collapse{{ stream.id }}" class="collapse" role="tabpanel" aria-labelledby="heading{{ stream.id }}" style="background-color: #fff;">
         	  <td colspan="5">
@@ -119,39 +124,60 @@
   </div>
 
   {% if perms.scipost.can_view_timesheets %}
-  <div class="tab-pane" id="teamtimesheets" role="tabpanel">
-    <div class="row">
-      <div class="col-12">
-	<h2 class="highlight">Team Timesheets</h2>
-      </div>
-    </div>
-
-    <table class="table table-hover mb-5">
-      <thead class="thead-default">
-	<tr>
-	  <th>Name</th>
-	</tr>
-      </thead>
+      <div class="tab-pane" id="teamtimesheets" role="tabpanel">
+        <div class="row">
+          <div class="col-12">
+    	<h2 class="highlight">Team Timesheets</h2>
+          </div>
+        </div>
+
+        <table class="table table-hover mb-5">
+          <thead class="thead-default">
+    	<tr>
+    	  <th>Name</th>
+    	</tr>
+          </thead>
+
+          <tbody id="accordion" role="tablist" aria-multiselectable="true">
+        	{% for member in production_team.all %}
+            	<tr data-toggle="collapse" data-parent="#accordion" href="#collapse{{ member.id }}" aria-expanded="true" aria-controls="collapse{{ member.id }}" style="cursor: pointer;">
+            	  <td>{{ member }}</td>
+            	</tr>
+            	<tr id="collapse{{ member.id }}" class="collapse" role="tabpanel" aria-labelledby="heading{{ member.id }}" style="background-color: #fff;">
+            	  <td>
+            	    {% include 'production/partials/production_timesheet_card.html' with events=member.productionevent_set.all %}
+            	  </td>
+            	</tr>
+        	{% empty %}
+            	<tr>
+            	  <td>No Team Member found.</td>
+            	</tr>
+        	{% endfor %}
+          </tbody>
+        </table>
 
-      <tbody id="accordion" role="tablist" aria-multiselectable="true">
-	{% for member in production_team.all %}
-	<tr data-toggle="collapse" data-parent="#accordion" href="#collapse{{ member.id }}" aria-expanded="true" aria-controls="collapse{{ member.id }}" style="cursor: pointer;">
-	  <td>{{ member }}</td>
-	</tr>
-	<tr id="collapse{{ member.id }}" class="collapse" role="tabpanel" aria-labelledby="heading{{ member.id }}" style="background-color: #fff;">
-	  <td>
-	    {% include 'production/partials/production_timesheet_card.html' with events=member.productionevent_set.all %}
-	  </td>
-	</tr>
-	{% empty %}
-	<tr>
-	  <td>No Team Member found.</td>
-	</tr>
-	{% endfor %}
-      </tbody>
-    </table>
+      </div>
+  {% endif %}
 
-  </div>
+  {% if perms.scipost.can_promote_user_to_production_officer %}
+      <div class="tab-pane" id="officers" role="tabpanel">
+          <h2 class="highlight">Production Officers</h2>
+          <h3>Current Production Officer</h3>
+          <ul>
+              {% for officer in production_officers %}
+                <li>{{ officer }}</li>
+              {% endfor %}
+          </ul>
+
+          {% if new_officer_form %}
+              <h3>Promote user to Production Officer</h3>
+              <form action="{% url 'production:user_to_officer' %}" method="post">
+                {% csrf_token %}
+                {{ new_officer_form|bootstrap }}
+                <input type="submit" class="btn btn-primary" value="Promote to Production Officer">
+            </form>
+          {% endif %}
+      </div>
   {% endif %}
 
 </div>
diff --git a/production/urls.py b/production/urls.py
index 95559d171..116a3a093 100644
--- a/production/urls.py
+++ b/production/urls.py
@@ -5,6 +5,7 @@ from production import views as production_views
 urlpatterns = [
     url(r'^$', production_views.production, name='production'),
     url(r'^completed$', production_views.completed, name='completed'),
+    url(r'^officers/new$', production_views.user_to_officer, name='user_to_officer'),
     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$',
diff --git a/production/views.py b/production/views.py
index 5c07863b4..4dddd7984 100644
--- a/production/views.py
+++ b/production/views.py
@@ -1,6 +1,7 @@
 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,30 +12,32 @@ from guardian.decorators import permission_required
 
 from .constants import PRODUCTION_STREAM_COMPLETED
 from .models import ProductionUser, ProductionStream, ProductionEvent
-from .forms import ProductionEventForm, AssignOfficerForm
-from .signals import notify_stream_completed
-
-from scipost.models import Contributor
+from .forms import ProductionEventForm, AssignOfficerForm, UserToOfficerForm
+from .permissions import is_production_user
+from .signals import notify_stream_completed, notify_new_stream
 
 
 ######################
 # Production process #
 ######################
 
+@is_production_user()
 @permission_required('scipost.can_view_production', return_403=True)
 def production(request):
     """
     Overview page for the production process.
     All papers with accepted but not yet published status are included here.
     """
-    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')
+    streams = ProductionStream.objects.ongoing()
+    if not request.user.has_perm('scipost.can_assign_production_officer'):
+        # Restrict stream queryset if user is not supervisor
+        streams = streams.filter_for_user(request.user.production_user)
+    streams = streams.order_by('opened')
+
     prodevent_form = ProductionEventForm()
     assignment_form = AssignOfficerForm()
     ownevents = ProductionEvent.objects.filter(
-        noted_by=request.user.contributor,
+        noted_by=request.user.production_user,
         duration__gte=datetime.timedelta(minutes=1)).order_by('-noted_on')
     context = {
         'streams': streams,
@@ -43,11 +46,15 @@ def production(request):
         'ownevents': ownevents,
     }
     if request.user.has_perm('scipost.can_view_timesheets'):
-        context['production_team'] = Contributor.objects.filter(
-            user__groups__name='Production Officers').order_by('user__last_name')
+        context['production_team'] = ProductionUser.objects.all()
+
+    if request.user.has_perm('scipost.can_promote_user_to_production_officer'):
+        context['production_officers'] = ProductionUser.objects.all()
+        context['new_officer_form'] = UserToOfficerForm()
     return render(request, 'production/production.html', context)
 
 
+@is_production_user()
 @permission_required('scipost.can_view_production', return_403=True)
 def completed(request):
     """
@@ -58,6 +65,21 @@ def completed(request):
     return render(request, 'production/completed.html', context)
 
 
+@is_production_user()
+@permission_required('scipost.can_promote_user_to_production_officer')
+def user_to_officer(request):
+    form = UserToOfficerForm(request.POST or None)
+    if form.is_valid():
+        officer = form.save()
+
+        # Add permission group
+        group = Group.objects.get(name='Production Officers')
+        officer.user.groups.add(group)
+        messages.success(request, '{user} promoted to Production Officer'.format(user=officer))
+    return redirect(reverse('production:production'))
+
+
+@is_production_user()
 @permission_required('scipost.can_view_production', return_403=True)
 def add_event(request, stream_id):
     stream = get_object_or_404(ProductionStream.objects.ongoing(), pk=stream_id)
@@ -65,27 +87,30 @@ def add_event(request, stream_id):
     if prodevent_form.is_valid():
         prodevent = prodevent_form.save(commit=False)
         prodevent.stream = stream
-        prodevent.noted_by = request.user.contributor
+        prodevent.noted_by = request.user.production_user
         prodevent.save()
     else:
         messages.warning(request, 'The form was invalidly filled.')
     return redirect(reverse('production:production'))
 
 
+@is_production_user()
 @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')))
+        officer = form.cleaned_data.get('officer')
+        messages.success(request, 'Officer {officer} has been assigned.'.format(officer=officer))
+        notify_new_stream(request.user, stream, officer.user)
     else:
         for key, error in form.errors.items():
             messages.warning(request, error[0])
     return redirect(reverse('production:production'))
 
 
+@is_production_user()
 @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)
@@ -99,6 +124,7 @@ def remove_officer(request, stream_id, officer_id):
     return redirect(reverse('production:production'))
 
 
+@method_decorator(is_production_user(), name='dispatch')
 @method_decorator(permission_required('scipost.can_view_production', raise_exception=True),
                   name='dispatch')
 class UpdateEventView(UpdateView):
@@ -108,13 +134,14 @@ class UpdateEventView(UpdateView):
     slug_url_kwarg = 'event_id'
 
     def get_queryset(self):
-        return self.model.objects.get_my_events(self.request.user.contributor)
+        return self.model.objects.get_my_events(self.request.user.production_user)
 
     def form_valid(self, form):
         messages.success(self.request, 'Event updated succesfully')
         return super().form_valid(form)
 
 
+@method_decorator(is_production_user(), name='dispatch')
 @method_decorator(permission_required('scipost.can_view_production', raise_exception=True),
                   name='dispatch')
 class DeleteEventView(DeleteView):
@@ -123,7 +150,7 @@ class DeleteEventView(DeleteView):
     slug_url_kwarg = 'event_id'
 
     def get_queryset(self):
-        return self.model.objects.get_my_events(self.request.user.contributor)
+        return self.model.objects.get_my_events(self.request.user.production_user)
 
     def form_valid(self, form):
         messages.success(self.request, 'Event deleted succesfully')
@@ -133,6 +160,7 @@ class DeleteEventView(DeleteView):
         return self.object.get_absolute_url()
 
 
+@is_production_user()
 @permission_required('scipost.can_publish_accepted_submission', return_403=True)
 def mark_as_completed(request, stream_id):
     stream = get_object_or_404(ProductionStream.objects.ongoing(), pk=stream_id)
-- 
GitLab