Skip to content

Commit

Permalink
Add enrollment to courses
Browse files Browse the repository at this point in the history
  • Loading branch information
teemulehtinen committed Jan 25, 2016
1 parent 80a02fb commit a6d6477
Show file tree
Hide file tree
Showing 15 changed files with 281 additions and 81 deletions.
2 changes: 2 additions & 0 deletions a-plus/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
LOGIN_TITLE_TEXT = ''
LOGIN_BODY_TEXT = ''
LOGIN_BUTTON_TEXT = 'Maintenance login'
INTERNAL_USER_LABEL = 'Aalto'
EXTERNAL_USER_LABEL = 'MOOC'

WELCOME_TEXT_FI = 'A+ <small>verkkopohjainen oppimisympäristö</small>'
SHIBBOLETH_TITLE_TEXT_FI = 'Aalto-yliopiston käyttäjät'
Expand Down
3 changes: 3 additions & 0 deletions course/long_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@

# These need to be listed before the exercise URL routings.
urlpatterns = [
url(USER_URL_PREFIX + r'enroll/$',
views.Enroll.as_view(),
name='enroll'),
url(USER_URL_PREFIX + r'export-calendar/$',
views.CalendarExport.as_view(),
name='export-calendar'),
Expand Down
34 changes: 34 additions & 0 deletions course/migrations/0016_auto_20160124_2214.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations


class Migration(migrations.Migration):

dependencies = [
('course', '0015_auto_20160121_1544'),
]

operations = [
migrations.RemoveField(
model_name='courseinstance',
name='submission_access',
),
migrations.RemoveField(
model_name='courseinstance',
name='view_access',
),
migrations.AddField(
model_name='courseinstance',
name='enrollment_audience',
field=models.IntegerField(choices=[(1, 'Internal users'), (2, 'External users'), (3, 'Internal and external users')], default=1),
preserve_default=True,
),
migrations.AddField(
model_name='courseinstance',
name='view_content_to',
field=models.IntegerField(choices=[(1, 'Enrolled students'), (2, 'Enrollment audience'), (3, 'All registered users'), (4, 'Public to internet')], default=2),
preserve_default=True,
),
]
53 changes: 31 additions & 22 deletions course/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,17 +102,17 @@ class CourseInstance(models.Model):
validators=[RegexValidator(regex="^[\w\-\.]*$")],
help_text=_("Input an URL identifier for this course instance."))
visible_to_students = models.BooleanField(default=True)
view_access = models.IntegerField(choices=(
(0, _('Public to internet')),
(1, _('Internal and external users')),
(2, _('Only external users')),
(3, _('Only internal users')),
), default=3)
submission_access = models.IntegerField(choices=(
(1, _('Internal and external users')),
(2, _('Only external users')),
(3, _('Only internal users')),
), default=3)
enrollment_audience = models.IntegerField(choices=(
(1, _('Internal users')),
(2, _('External users')),
(3, _('Internal and external users')),
), default=1)
view_content_to = models.IntegerField(choices=(
(1, _('Enrolled students')),
(2, _('Enrollment audience')),
(3, _('All registered users')),
(4, _('Public to internet')),
), default=2)
starting_time = models.DateTimeField()
ending_time = models.DateTimeField()
image = models.ImageField(blank=True, null=True, upload_to=build_upload_dir)
Expand Down Expand Up @@ -170,16 +170,6 @@ def save(self, *args, **kwargs):
if self.image:
resize_image(self.image.path, (800,600))

def has_submission_access(self, user):
if not user or not user.is_authenticated():
return False, _("You need to login to submit exercises to this course.")
if self.submission_access == 2:
return user.userprofile.is_external, \
_("You need to login as external student to submit exercises to this course.")
if self.submission_access > 2:
return not user.userprofile.is_external, \
_("You need to login as internal student to submit exercises to this course.")

def is_assistant(self, user):
return user and user.is_authenticated() \
and self.assistants.filter(id=user.userprofile.id).exists()
Expand All @@ -190,12 +180,31 @@ def is_teacher(self, user):
def is_course_staff(self, user):
return self.is_teacher(user) or self.is_assistant(user)

def is_student(self, user):
return user and user.is_authenticated() \
and self.students.filter(id=user.userprofile.id).exists()

def is_enrollable(self, user):
if user and user.is_authenticated():
if self.enrollment_audience == 1:
return not user.userprofile.is_external
if self.enrollment_audience == 2:
return user.userprofile.is_external
return True
return False

def enroll_student(self, user):
if user and user.is_authenticated() and not self.is_course_staff(user):
self.students.add(user.userprofile)

def get_course_staff_profiles(self):
return UserProfile.objects.filter(Q(teaching_courses=self.course) | Q(assisting_courses=self))\
.distinct()

def get_student_profiles(self):
# TODO: enrollment should be designed
return self.students.all()

def get_submitted_profiles(self):
return UserProfile.objects.filter(submissions__exercise__course_module__course_instance=self)\
.distinct()\
.exclude(assisting_courses=self)\
Expand Down
7 changes: 7 additions & 0 deletions course/templates/course/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ <h3>
<!--{{ instance.instance_name }}<br />-->
{{ instance.starting_time|date:"N j, Y" }} &ndash;
{{ instance.ending_time|date:"N j, Y" }}
<br />
{% if instance.enrollment_audience == 1 or instance.enrollment_audience == 3 %}
<span class="label label-success">{{ internal_user_label|safe }}</span>
{% endif %}
{% if instance.enrollment_audience == 2 or instance.enrollment_audience == 3 %}
<span class="label label-info">{{ external_user_label|safe }}</span>
{% endif %}
</p>
<div class="buttons caption">
<a class="btn btn-primary btn-block" role="button" href="{{ instance_url }}">
Expand Down
24 changes: 24 additions & 0 deletions course/templates/course/toc.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,31 @@
{% block breadcrumb %}{% endblock %}

{% block coursecontent %}

{% if not enrolled %}
<div class="alert alert-info">
{% if enrollable %}
<form method="post" action="{{ instance|url:'enroll' }}">
{% csrf_token %}
{% if instance.view_content_to > 1 %}
{% trans "In order to submit exercises, you must enroll to the course." %}
{% else %}
{% trans "In order to submit exercises and see material, you must enroll to the course." %}
{% endif %}
<input type="submit" value="{% trans 'Enroll' %}" class="btn btn-info">
</form>
{% elif profile %}
{% trans "Unfortunately, you cannot enroll to this course, but you may explore the course material." %}
{% else %}
{% trans "Login is required to submit exercises, but you may anonymously explore the course material." %}
{% endif %}
</div>
{% endif %}

<div>
{{ instance.description|safe }}
</div>

{% if instance.index_mode == 1 %}
<ul class="toc">
{% for module in instance.course_modules.all %}
Expand Down Expand Up @@ -39,9 +61,11 @@ <h3>
{% endif %}
{% endfor %}
</ul>

{% else %}
{% user_results %}
{% endif %}

<div>
{{ instance.footer|safe }}
</div>
Expand Down
2 changes: 1 addition & 1 deletion course/tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def children(self, parent_id=None, show_hidden=False):
if show_hidden:
return [o for o in self.objects if o.parent_id == parent_id]
return [o for o in self.objects if o.parent_id == parent_id \
and o.status != 'hidden' and o.status != 'unlisted']
and o.status in ('ready', 'maintenance')]

def parent(self, ref):
if ref.parent_id:
Expand Down
35 changes: 26 additions & 9 deletions course/viewbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,13 @@ def get_resource_objects(self):
self.is_course_staff = self.is_teacher or self.is_assistant
self.note("instance", "is_assistant", "is_course_staff")

def access_control(self):

# Loosen the access mode if instance is public.
if self.instance.view_access == 0 and self.access_mode == ACCESS.STUDENT:
if self.instance.view_content_to == 4 and \
self.access_mode in (ACCESS.STUDENT, ACCESS.ENROLL):
self.access_mode = ACCESS.ANONYMOUS

def access_control(self):
super().access_control()
if self.access_mode >= ACCESS.ASSISTANT:
if not self.is_course_staff:
Expand All @@ -65,19 +67,34 @@ def access_control(self):
_("The resource is not currently visible."))
raise PermissionDenied()

# View access.
if self.instance.view_access == 2 and not self.profile.is_external:
messages.error(self.request, _("This course is only for external students."))
raise PermissionDenied()
if self.instance.view_access > 2 and self.profile.is_external:
messages.error(self.request, _("This course is only for internal students."))
raise PermissionDenied()
# View content access.
if not self.is_course_staff:
if self.instance.view_content_to == 1 and self.access_mode > ACCESS.ENROLL:
if not self.instance.is_student(self.request.user):
messages.error(self.request, _("Only enrolled students shall pass."))
raise PermissionDenied()
elif self.instance.view_content_to < 3:
if self.instance.enrollment_audience == 1 and self.profile.is_external:
messages.error(self.request, _("This course is only for internal students."))
raise PermissionDenied()
if self.instance.enrollment_audience == 2 and not self.profile.is_external:
messages.error(self.request, _("This course is only for external students."))
raise PermissionDenied()


class CourseInstanceBaseView(CourseInstanceMixin, BaseTemplateView):
pass


class EnrollableViewMixin(CourseInstanceMixin):
access_mode = ACCESS.ENROLL

def get_common_objects(self):
self.enrolled = self.profile and self.instance.is_student(self.profile.user)
self.enrollable = self.profile and self.instance.is_enrollable(self.profile.user)
self.note('enrolled', 'enrollable')


class CourseModuleMixin(CourseInstanceMixin):
module_kw = "module"

Expand Down
29 changes: 25 additions & 4 deletions course/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@
from django.utils.translation import ugettext_lazy as _
from django.views.generic.base import View

from exercise.models import LearningObject
from lib.helpers import settings_text
from lib.viewbase import BaseRedirectView
from lib.viewbase import BaseTemplateView, BaseRedirectView
from userprofile.viewbase import ACCESS, UserProfileView
from .viewbase import CourseBaseView, CourseInstanceBaseView, \
CourseModuleBaseView, CourseInstanceMixin
CourseModuleBaseView, CourseInstanceMixin, EnrollableViewMixin
from .models import CourseInstance


Expand All @@ -29,8 +30,10 @@ class HomeView(UserProfileView):
def get_common_objects(self):
super().get_common_objects()
self.welcome_text = settings_text(self.request, 'WELCOME_TEXT')
self.internal_user_label = settings_text(self.request, 'INTERNAL_USER_LABEL')
self.external_user_label = settings_text(self.request, 'EXTERNAL_USER_LABEL')
self.instances = CourseInstance.objects.get_active(self.request.user)
self.note("welcome_text", "instances")
self.note("welcome_text", "internal_user_label", "external_user_label", "instances")


class ArchiveView(UserProfileView):
Expand All @@ -51,10 +54,28 @@ def get_common_objects(self):
self.note("instances")


class InstanceView(CourseInstanceBaseView):
class InstanceView(EnrollableViewMixin, BaseTemplateView):
template_name = "course/toc.html"


class Enroll(EnrollableViewMixin, BaseRedirectView):

def post(self, request, *args, **kwargs):
self.handle()

if self.enrolled or not self.enrollable:
messages.error(self.request, _("You cannot enroll, or have already enrolled, to this course."))
raise PermissionDenied()

# Support enrollment questionnaires.
exercise = LearningObject.objects.find_enrollment_exercise(self.instance)
if exercise:
return self.redirect(exercise.get_absolute_url())

self.instance.enroll_student(self.request.user)
return self.redirect(self.instance.get_absolute_url())


class ModuleView(CourseModuleBaseView):
template_name = "course/module.html"

Expand Down
4 changes: 2 additions & 2 deletions edit_course/course_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ class Meta:
'language',
'starting_time',
'ending_time',
'view_access',
'submission_access',
'enrollment_audience',
'view_content_to',
'assistants',
'technical_error_emails',
]
Expand Down
Loading

0 comments on commit a6d6477

Please sign in to comment.