From 2d310cb889a20100d6041dc8e512d15710c35f95 Mon Sep 17 00:00:00 2001
From: Andreas Kloeckner <inform@tiker.net>
Date: Wed, 7 Jan 2015 20:43:38 -0600
Subject: [PATCH] Make admin site multi-course capable

---
 TODO                                          |   4 -
 course/admin.py                               | 168 +++++++++++++++++-
 ...d_participations_related_name_on_course.py |  20 +++
 course/models.py                              |   2 +-
 4 files changed, 182 insertions(+), 12 deletions(-)
 create mode 100644 course/migrations/0038_add_participations_related_name_on_course.py

diff --git a/TODO b/TODO
index ac1834e3..c67c7771 100644
--- a/TODO
+++ b/TODO
@@ -31,10 +31,6 @@ Ideas
 
 - Session reset UI
 
-- Make admin site multi-course capable
-  https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.has_add_permission
-  https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.get_queryset
-
 - Manual grade entry/override
 
 - Flow results analytics
diff --git a/course/admin.py b/course/admin.py
index 11eb6acf..155ce0d2 100644
--- a/course/admin.py
+++ b/course/admin.py
@@ -34,6 +34,32 @@ from course.models import (
         GradingOpportunity, GradeChange, InstantMessage)
 from django import forms
 from course.enrollment import (approve_enrollment, deny_enrollment)
+from course.constants import participation_role
+
+
+def _filter_courses_for_user(queryset, user):
+    if user.is_superuser:
+        return queryset
+    return queryset.filter(
+            participations__user=user,
+            participations__role=participation_role.instructor)
+
+
+def _filter_course_linked_obj_for_user(queryset, user):
+    if user.is_superuser:
+        return queryset
+    return queryset.filter(
+            course__participations__user=user,
+            course__participations__role=participation_role.instructor)
+
+
+def _filter_participation_linked_obj_for_user(queryset, user):
+    if user.is_superuser:
+        return queryset
+    irole = participation_role.instructor
+    return queryset.filter(
+        participation__course__participations__user=user,
+        participation__course__participations__role=irole)
 
 
 # {{{ user status
@@ -112,6 +138,10 @@ class CourseAdmin(admin.ModelAdmin):
         # These are created only through the course creation form.
         return False
 
+    def get_queryset(self, request):
+        qs = super(CourseAdmin, self).get_queryset(request)
+        return _filter_courses_for_user(qs, request.user)
+
     # }}}
 
 admin.site.register(Course, CourseAdmin)
@@ -136,6 +166,21 @@ class EventAdmin(admin.ModelAdmin):
 
     list_editable = ("ordinal", "time", "end_time", "shown_in_calendar")
 
+    # {{{ permissions
+
+    def get_queryset(self, request):
+        qs = super(EventAdmin, self).get_queryset(request)
+        return _filter_course_linked_obj_for_user(qs, request.user)
+
+    def formfield_for_foreignkey(self, db_field, request, **kwargs):
+        if db_field.name == "course":
+            kwargs["queryset"] = _filter_courses_for_user(
+                    Course.objects, request.user)
+        return super(EventAdmin, self).formfield_for_foreignkey(
+                db_field, request, **kwargs)
+
+    # }}}
+
 admin.site.register(Event, EventAdmin)
 
 # }}}
@@ -175,17 +220,55 @@ class ParticipationAdmin(admin.ModelAdmin):
 
     actions = [approve_enrollment, deny_enrollment]
 
+    # {{{ permissions
+
+    def get_queryset(self, request):
+        qs = super(ParticipationAdmin, self).get_queryset(request)
+        return _filter_course_linked_obj_for_user(qs, request.user)
+
+    def formfield_for_foreignkey(self, db_field, request, **kwargs):
+        if db_field.name == "course":
+            kwargs["queryset"] = _filter_courses_for_user(
+                    Course.objects, request.user)
+        return super(ParticipationAdmin, self).formfield_for_foreignkey(
+                db_field, request, **kwargs)
+
+    # }}}
+
 admin.site.register(Participation, ParticipationAdmin)
 
 
 class ParticipationPreapprovalAdmin(admin.ModelAdmin):
-    list_display = ["email", "course", "role"]
-    list_filter = ["course", "role"]
+    list_display = ("email", "course", "role", "creation_time", "creator")
+    list_filter = ("course", "role")
 
     search_fields = (
             "email",
             )
 
+    # {{{ permissions
+
+    def get_queryset(self, request):
+        qs = super(ParticipationPreapprovalAdmin, self).get_queryset(request)
+        if request.user.is_superuser:
+            return qs
+        return _filter_course_linked_obj_for_user(qs, request.user)
+
+    exclude = ("creator", "creation_time")
+
+    def save_model(self, request, obj, form, change):
+        obj.creator = request.user
+        obj.save()
+
+    def formfield_for_foreignkey(self, db_field, request, **kwargs):
+        if db_field.name == "course":
+            kwargs["queryset"] = _filter_courses_for_user(
+                    Course.objects, request.user)
+        return super(ParticipationPreapprovalAdmin, self).formfield_for_foreignkey(
+                db_field, request, **kwargs)
+
+    # }}}
+
 admin.site.register(ParticipationPreapproval, ParticipationPreapprovalAdmin)
 
 # }}}
@@ -258,9 +341,20 @@ class FlowSessionAdmin(admin.ModelAdmin):
     # {{{ permissions
 
     def has_add_permission(self, request):
-        # These are created only automatically.
+        # These are only created automatically.
         return False
 
+    def get_queryset(self, request):
+        qs = super(FlowSessionAdmin, self).get_queryset(request)
+        return _filter_course_linked_obj_for_user(qs, request.user)
+
+    def formfield_for_foreignkey(self, db_field, request, **kwargs):
+        if db_field.name == "course":
+            kwargs["queryset"] = _filter_courses_for_user(
+                    Course.objects, request.user)
+        return super(FlowSessionAdmin, self).formfield_for_foreignkey(
+                db_field, request, **kwargs)
+
     # }}}
 
 admin.site.register(FlowSession, FlowSessionAdmin)
@@ -377,6 +471,14 @@ class FlowPageVisitAdmin(admin.ModelAdmin):
         # These are created only automatically.
         return False
 
+    def get_queryset(self, request):
+        qs = super(FlowPageVisitAdmin, self).get_queryset(request)
+        if request.user.is_superuser:
+            return qs
+        return qs.filter(
+            flow_session__course__participations__user=request.user,
+            flow_session__course__participations__role=participation_role.instructor)
+
     # }}}
 
 admin.site.register(FlowPageVisit, FlowPageVisitAdmin)
@@ -419,10 +521,25 @@ class FlowRuleExceptionAdmin(admin.ModelAdmin):
 
     raw_id_fields = ("participation",)
 
+    # {{{ permissions
+
     def has_add_permission(self, request):
-        # These are created only automatically.
+        # These are only created automatically.
         return False
 
+    def get_queryset(self, request):
+        qs = super(FlowRuleExceptionAdmin, self).get_queryset(request)
+        return _filter_participation_linked_obj_for_user(qs, request.user)
+
+    exclude = ("creator", "creation_time")
+
+    def save_model(self, request, obj, form, change):
+        obj.creator = request.user
+        obj.save()
+
+    # }}}
+
+
 admin.site.register(FlowRuleException, FlowRuleExceptionAdmin)
 
 # }}}
@@ -432,10 +549,10 @@ admin.site.register(FlowRuleException, FlowRuleExceptionAdmin)
 
 class GradingOpportunityAdmin(admin.ModelAdmin):
     list_display = (
-            "course",
             "name",
-            "due_time",
+            "course",
             "identifier",
+            "due_time",
             "shown_in_grade_book",
             "shown_in_student_grade_book",
             )
@@ -451,6 +568,23 @@ class GradingOpportunityAdmin(admin.ModelAdmin):
             "shown_in_student_grade_book",
             )
 
+    # {{{ permissions
+
+    exclude = ("creation_time",)
+
+    def get_queryset(self, request):
+        qs = super(GradingOpportunityAdmin, self).get_queryset(request)
+        return _filter_course_linked_obj_for_user(qs, request.user)
+
+    def formfield_for_foreignkey(self, db_field, request, **kwargs):
+        if db_field.name == "course":
+            kwargs["queryset"] = _filter_courses_for_user(
+                    Course.objects, request.user)
+        return super(GradingOpportunityAdmin, self).formfield_for_foreignkey(
+                db_field, request, **kwargs)
+
+    # }}}
+
 admin.site.register(GradingOpportunity, GradingOpportunityAdmin)
 
 
@@ -510,7 +644,21 @@ class GradeChangeAdmin(admin.ModelAdmin):
             "state",
             )
 
-    raw_id_fields = ("participation", "flow_session",)
+    raw_id_fields = ("participation", "flow_session", "opportunity")
+
+    # {{{ permission
+
+    def get_queryset(self, request):
+        qs = super(GradeChangeAdmin, self).get_queryset(request)
+        return _filter_participation_linked_obj_for_user(qs, request.user)
+
+    exclude = ("creator", "grade_time")
+
+    def save_model(self, request, obj, form, change):
+        obj.creator = request.user
+        obj.save()
+
+    # }}}
 
 admin.site.register(GradeChange, GradeChangeAdmin)
 
@@ -547,12 +695,18 @@ class InstantMessageAdmin(admin.ModelAdmin):
             "participation__user__last_name",
             )
 
+    raw_id_fields = ("participation",)
+
     # {{{ permissions
 
     def has_add_permission(self, request):
         # These are created only automatically.
         return False
 
+    def get_queryset(self, request):
+        qs = super(InstantMessageAdmin, self).get_queryset(request)
+        return _filter_participation_linked_obj_for_user(qs, request.user)
+
     # }}}
 
 admin.site.register(InstantMessage, InstantMessageAdmin)
diff --git a/course/migrations/0038_add_participations_related_name_on_course.py b/course/migrations/0038_add_participations_related_name_on_course.py
new file mode 100644
index 00000000..c2ced3af
--- /dev/null
+++ b/course/migrations/0038_add_participations_related_name_on_course.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('course', '0037_flowruleexception'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='participation',
+            name='course',
+            field=models.ForeignKey(related_name='participations', to='course.Course'),
+            preserve_default=True,
+        ),
+    ]
diff --git a/course/models.py b/course/models.py
index 9b00d4de..5642d720 100644
--- a/course/models.py
+++ b/course/models.py
@@ -195,7 +195,7 @@ class Event(models.Model):
 
 class Participation(models.Model):
     user = models.ForeignKey(User)
-    course = models.ForeignKey(Course)
+    course = models.ForeignKey(Course, related_name="participations")
 
     enroll_time = models.DateTimeField(default=now)
     role = models.CharField(max_length=50,
-- 
GitLab