Skip to content

Commit

Permalink
Restyle userprofile and notifications.
Browse files Browse the repository at this point in the history
  • Loading branch information
teemulehtinen committed Jun 1, 2015
1 parent a4b475e commit bb07fc6
Show file tree
Hide file tree
Showing 17 changed files with 324 additions and 395 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ The system has since been developed by various contributors at Aalto University,
Requirements
------------

A+ is a Django 1.7+ and Python 3 application which has been run in production using Postgresql database, Apache 2 and uwsgi. Consider using `virtualenv` and `pip3 install -r requirements.txt`. Create `local_settings.py` in the same directory with `settings.py` and override necessary Django settings. At least `DEBUG`, `SECRET_KEY` and `DATABASES` must be set in case of deployment. The server process needs write access to the `media` directory.
A+ is a Django 1.7+ and Python 3 application which has been run in production using Postgresql database, Apache 2 and uwsgi. Consider using `virtualenv` and `pip3 install -r requirements.txt`. Create `local_settings.py` and override necessary Django settings from `a-plus/settings.py`. At least `DEBUG`, `SECRET_KEY` and `DATABASES` must be set in case of deployment. The server process needs write access to the `media` directory.

Testing environment
-------------------
Expand All @@ -25,7 +25,7 @@ Code Organization
-----------------

[a-plus/](a-plus) : Django main settings
[course/](course) : The course instances
[course/](course) : The courses and course instances
[exercise/](exercise) : Learning modules and exercises for the course instances
[userprofile/](userprofile) : Additional user information and groups
[django_shibboleth/](django_shibboleth) : Handles users for Apache Shibboleth request headers
Expand All @@ -34,4 +34,4 @@ Code Organization
[external_services/](external_services) : Linking to external services, optionally LTI authenticated
[apps/](apps) : Provides plugins that can integrate additional content to course instances
[api/](api) : An HTTP service API for accessing A+ data
[lib/](lib) : More general libraries
[lib/](lib) : More general library code
43 changes: 1 addition & 42 deletions api/permissions.py
Original file line number Diff line number Diff line change
@@ -1,51 +1,10 @@
# Tastypie
from tastypie.resources import ModelResource
from tastypie.authentication import Authentication
from tastypie.authorization import Authorization


class CIStaffAuthorization(Authorization):
""" Limits the access to only users, who have authenticated
and to CourseInstances that they are either assistants
or teachers in. """

def is_authorized(self, request, object=None):
return request.user.is_authenticated

def apply_limits(self, request, object_list):
if request.user.is_superuser:
# Super users have access to all course instances
return object_list
else:
# Other users have access to course instances they are
# teachers or assistants at
userprofile = request.user.userprofile
return userprofile.get_courseinstance_staff_queryset.all()


class SuperuserAuthorization(Authorization):

def is_authorized(self, request, object=None):
return request.user.is_superuser

def apply_limits(self, request, object_list):
return object_list


class StaffAuthentication(Authentication):
def is_authenticated(self, request, **kwargs):
return request.user.is_staff


class CourseAuthentication(Authentication):
pass


class CourseAuthorization(Authorization):
def is_authorized(self, request, object=None):
return request.user.is_authenticated

def apply_limits(self, request, object_list):
if request and hasattr(request, 'user'):
return object_list.filter(author__username=request.user.username)
return object_list.none()

8 changes: 5 additions & 3 deletions api/urls.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from django.conf.urls import patterns, include
from tastypie.api import Api
from course.api import CourseResource, CourseInstanceResource
from userprofile.api import UserProfileResource

from course.api import CourseInstanceSummaryResource
from course.api import CourseResource, CourseInstanceResource
from exercise.api import ExerciseResource, CourseModuleResource, \
SubmissionResource, SubmissionContentResource, LearningObjectResource
from tastypie.api import Api
from userprofile.api import UserProfileResource


api = Api(api_name='v1')
api.register(CourseResource())
Expand Down
19 changes: 8 additions & 11 deletions course/admin.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
from django.contrib import admin
from course.models import Course, CourseInstance, CourseHook
from django.db.models import Q


class CourseAdmin(admin.ModelAdmin):
list_display_links = ["id"]
list_display_links = ["id"]

list_display = ["id",
list_display = ["id",
"name",
"code"]

list_editable = ["name",
list_editable = ["name",
"code"]

filter_horizontal = ["teachers"]
filter_horizontal = ["teachers"]

def get_queryset(self, request):
if not request.user.is_superuser:
return request.user.userprofile.teaching_courses
else:
# TODO: test that the manager works
# Previously: return self.model._default_manager.filter()
return self.model.objects.filter()


def instance_url(obj):
""" This method returns the URL to the given object. This method is used as
a callable that is included in the admin views. """
Expand Down Expand Up @@ -49,11 +48,9 @@ class CourseInstanceAdmin(admin.ModelAdmin):

def get_queryset(self, request):
if not request.user.is_superuser:
return request.user.userprofile.get_courseinstance_staff_queryset()
return self.model.objects.where_staff_includes(request.user.userprofile)
else:
# TODO: test that the manager works
# Previously: return self.model._default_manager.filter()
return self.model.objects.filter()
return self.model.objects.all()

admin.site.register(Course, CourseAdmin)
admin.site.register(CourseInstance, CourseInstanceAdmin)
Expand Down
5 changes: 1 addition & 4 deletions course/api.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
from django.conf.urls import url
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse

from course.models import Course, CourseInstance
from exercise.exercise_summary import UserCourseSummary
from tastypie import fields
from tastypie.authentication import Authentication # , OAuthAuthentication
from tastypie.authentication import Authentication
from tastypie.authorization import DjangoAuthorization, ReadOnlyAuthorization
from tastypie.bundle import Bundle
from tastypie.resources import ModelResource, Resource
from userprofile.models import UserProfile


# A+
class CourseResource(ModelResource):
instances = fields.ToManyField('course.api.CourseInstanceResource', 'instances')

Expand Down
86 changes: 32 additions & 54 deletions course/models.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,29 @@
# Python
import logging
import urllib.request, urllib.parse, urllib.error, urllib.request, urllib.error, urllib.parse
from datetime import datetime

# Django
from django.db import models
from django.contrib.contenttypes import generic
from django.core.urlresolvers import reverse
from django.db.models import Q
from django.core.validators import RegexValidator
from django.db import models
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from django.contrib.contenttypes import generic
import logging
import urllib.request, urllib.parse

# A+
from userprofile.models import UserProfile
from apps.models import BaseTab, BasePlugin
from userprofile.models import UserProfile


# Course class
class Course(models.Model):
'''
Course model represents a course in a university. A course has a name and an
identification number. It also has a URL which is included in the addresses
of pages under the course.
'''

# Basic information
name = models.CharField(max_length=255)
code = models.CharField(max_length=255)

# A portion that is included in the addresses under this course
url = models.CharField(
unique=True,
max_length=255,
blank=False,
name = models.CharField(max_length=255)
code = models.CharField(max_length=255)
url = models.CharField(unique=True, max_length=255, blank=False,
validators=[RegexValidator(regex="^[\w\-\.]*$")],
help_text="Input an identifier for this course's URL.")

# Relations
teachers = models.ManyToManyField(UserProfile, related_name="teaching_courses", blank=True)
teachers = models.ManyToManyField(UserProfile, related_name="teaching_courses", blank=True)

def get_absolute_url(self):
'''
Expand Down Expand Up @@ -82,38 +69,32 @@ def __str__(self):
return self.code + " " + self.name


class CourseInstanceManager(models.Manager):

def where_staff_includes(self, profile):
return self.filter(Q(assistants=profile) | Q(course__teachers=profile))


class CourseInstance(models.Model):
"""
CourseInstance class represent an instance of a course. A single course may have
several instances either at the same time or during different years. All instances
have the same teacher, but teaching assistants and students are connected to individual
instances.
"""

# Basic information
instance_name = models.CharField(max_length=255)
website = models.URLField(max_length=255, blank=True)

url = models.CharField(
unique=False,
max_length=255,
blank=False,
instance_name = models.CharField(max_length=255)
website = models.URLField(max_length=255, blank=True)
url = models.CharField(unique=False, max_length=255, blank=False,
validators=[RegexValidator(regex="^[\w\-\.]*$")],
help_text="Input an URL identifier for this course.")

starting_time = models.DateTimeField()
ending_time = models.DateTimeField()

visible_to_students = models.BooleanField(default=True)

# Relations
assistants = models.ManyToManyField(UserProfile,
related_name="assisting_courses",
blank=True)
course = models.ForeignKey(Course, related_name="instances")

plugins = generic.GenericRelation(BasePlugin, object_id_field="container_pk", content_type_field="container_type")
tabs = generic.GenericRelation(BaseTab, object_id_field="container_pk", content_type_field="container_type")
starting_time = models.DateTimeField()
ending_time = models.DateTimeField()
visible_to_students = models.BooleanField(default=True)
assistants = models.ManyToManyField(UserProfile, related_name="assisting_courses", blank=True)
course = models.ForeignKey(Course, related_name="instances")
plugins = generic.GenericRelation(BasePlugin, object_id_field="container_pk", content_type_field="container_type")
tabs = generic.GenericRelation(BaseTab, object_id_field="container_pk", content_type_field="container_type")
objects = CourseInstanceManager()

def is_assistant(self, profile):
"""
Expand Down Expand Up @@ -159,12 +140,9 @@ def is_open(self):
return self.starting_time <= datetime.now() <= self.ending_time

def is_visible_to(self, profile=None):
if profile:
return (self.visible_to_students
or self.is_staff(profile)
or profile.is_staff())
else:
return self.visible_to_students
if self.visible_to_students:
return True
return profile and (self.is_staff(profile) or profile.user.is_superuser)

def get_absolute_url(self):
'''
Expand All @@ -185,7 +163,7 @@ def get_breadcrumb(self):
Returns a list of tuples containing the names and url
addresses of parent objects and self.
"""
crumb = self.course.get_breadcrumb()
crumb = self.course.get_breadcrumb()
crumb_tuple = (self.instance_name, self.get_absolute_url())
crumb.append(crumb_tuple)
return crumb
Expand Down
35 changes: 24 additions & 11 deletions course/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ def setUp(self):
self.grader.set_password("graderPassword")
self.grader.save()

self.staff_member = User(username="staff", is_staff=True)
self.staff_member.set_password("staffPassword")
self.staff_member.save()
self.superuser = User(username="staff", is_staff=True, is_superuser=True)
self.superuser.set_password("staffPassword")
self.superuser.save()

self.course = Course.objects.create(
name="test course",
Expand Down Expand Up @@ -139,7 +139,7 @@ def test_course_staff(self):
self.assertTrue(self.current_course_instance.is_teacher(self.user.userprofile))
self.assertTrue(self.current_course_instance.is_staff(self.user.userprofile))
self.assertEquals(1, len(self.current_course_instance.get_course_staff()))
self.assertEquals("testUser", self.current_course_instance.get_course_staff()[0].get_shortname())
self.assertEquals("testUser", self.current_course_instance.get_course_staff()[0].shortname)

self.current_course_instance.assistants.clear()

Expand Down Expand Up @@ -198,7 +198,7 @@ def test_course_views(self):
def test_course_instance_students(self):
students = self.current_course_instance.get_students()
self.assertEquals(1, len(students))
self.assertEquals("testUser", students[0].get_shortname())
self.assertEquals("testUser", students[0].shortname)

submission2 = Submission.objects.create(
exercise=self.base_exercise,
Expand All @@ -207,7 +207,7 @@ def test_course_instance_students(self):

students = self.current_course_instance.get_students()
self.assertEquals(1, len(students))
self.assertEquals("testUser", students[0].get_shortname())
self.assertEquals("testUser", students[0].shortname)

submission3 = Submission.objects.create(
exercise=self.base_exercise,
Expand All @@ -216,16 +216,16 @@ def test_course_instance_students(self):

students = self.current_course_instance.get_students()
self.assertEquals(2, len(students))
self.assertEquals("testUser", students[0].get_shortname())
self.assertEquals("grader", students[1].get_shortname())
self.assertEquals("testUser", students[0].shortname)
self.assertEquals("grader", students[1].shortname)

def test_course_instance_visibility(self):
self.assertTrue(self.current_course_instance.is_visible_to())
self.assertFalse(self.hidden_course_instance.is_visible_to())
self.assertTrue(self.current_course_instance.is_visible_to(self.user.userprofile))
self.assertFalse(self.hidden_course_instance.is_visible_to(self.user.userprofile))
self.assertTrue(self.current_course_instance.is_visible_to(self.staff_member.userprofile))
self.assertTrue(self.hidden_course_instance.is_visible_to(self.staff_member.userprofile))
self.assertTrue(self.current_course_instance.is_visible_to(self.superuser.userprofile))
self.assertTrue(self.hidden_course_instance.is_visible_to(self.superuser.userprofile))

def test_course_instance_label(self):
self.assertEquals("Dashboard", self.current_course_instance.get_label())
Expand All @@ -241,7 +241,7 @@ def test_course_instance_visible_open_list(self):
self.assertTrue(self.current_course_instance in open_course_instances)
self.assertTrue(self.future_course_instance in open_course_instances)

open_course_instances = get_visible_open_course_instances(self.staff_member.userprofile)
open_course_instances = get_visible_open_course_instances(self.superuser.userprofile)
self.assertEqual(3, len(open_course_instances))
self.assertTrue(self.current_course_instance in open_course_instances)
self.assertTrue(self.future_course_instance in open_course_instances)
Expand All @@ -253,3 +253,16 @@ def test_course_instance_unicode_string(self):

def test_course_hook_unicode_string(self):
self.assertEquals("123456: Fall 2011 day 1 -> test_hook_url", str(self.course_hook))

def test_userprofile_courseinstance_staff_queryset(self):
self.course.teachers.add(self.grader.userprofile)
self.current_course_instance.assistants.add(self.superuser.userprofile)
student_staff_courseinstances = CourseInstance.objects.where_staff_includes(self.user.userprofile)
self.assertEqual(0, len(student_staff_courseinstances))
grader_staff_courseinstances = CourseInstance.objects.where_staff_includes(self.superuser.userprofile)
self.assertEqual(1, len(grader_staff_courseinstances))
self.assertEqual(self.current_course_instance, grader_staff_courseinstances[0])
teacher_staff_courseinstances = CourseInstance.objects.where_staff_includes(self.grader.userprofile)
self.assertEqual(4, len(teacher_staff_courseinstances))
self.assertTrue(self.past_course_instance in teacher_staff_courseinstances)
self.assertTrue(self.current_course_instance in teacher_staff_courseinstances)
Loading

0 comments on commit bb07fc6

Please sign in to comment.