Commit 2293ee20 by Bill DeRusha Committed by GitHub

Merge pull request #195 from edx/bderusha/course-list

Add course listing page
parents 0f66ff0d 543d8db4
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import sortedm2m.fields
class Migration(migrations.Migration):
dependencies = [
('publisher', '0002_auto_20160729_1027'),
]
operations = [
migrations.AlterField(
model_name='course',
name='organizations',
field=models.ManyToManyField(blank=True, related_name='publisher_courses', to='course_metadata.Organization', verbose_name='Partner Name'),
),
migrations.AlterField(
model_name='courserun',
name='course',
field=models.ForeignKey(related_name='publisher_course_runs', to='publisher.Course'),
),
migrations.AlterField(
model_name='courserun',
name='staff',
field=sortedm2m.fields.SortedManyToManyField(blank=True, related_name='publisher_course_runs_staffed', help_text=None, to='course_metadata.Person'),
),
]
...@@ -28,7 +28,7 @@ class Course(TimeStampedModel, ChangedByMixin): ...@@ -28,7 +28,7 @@ class Course(TimeStampedModel, ChangedByMixin):
) )
full_description = models.TextField(default=None, null=True, blank=True, verbose_name=_('About this course')) full_description = models.TextField(default=None, null=True, blank=True, verbose_name=_('About this course'))
organizations = models.ManyToManyField( organizations = models.ManyToManyField(
Organization, null=True, blank=True, related_name='publisher_courses', verbose_name=_('Partner Name') Organization, blank=True, related_name='publisher_courses', verbose_name=_('Partner Name')
) )
level_type = models.ForeignKey( level_type = models.ForeignKey(
LevelType, default=None, null=True, blank=True, related_name='publisher_courses', verbose_name=_('Course level') LevelType, default=None, null=True, blank=True, related_name='publisher_courses', verbose_name=_('Course level')
...@@ -70,7 +70,7 @@ class CourseRun(TimeStampedModel, ChangedByMixin): ...@@ -70,7 +70,7 @@ class CourseRun(TimeStampedModel, ChangedByMixin):
(PRIORITY_LEVEL_5, _('Level 5')), (PRIORITY_LEVEL_5, _('Level 5')),
) )
course = models.ForeignKey(Course) course = models.ForeignKey(Course, related_name='publisher_course_runs')
lms_course_id = models.CharField(max_length=255, unique=True, null=True, blank=True) lms_course_id = models.CharField(max_length=255, unique=True, null=True, blank=True)
start = models.DateTimeField(null=True, blank=True) start = models.DateTimeField(null=True, blank=True)
...@@ -81,7 +81,7 @@ class CourseRun(TimeStampedModel, ChangedByMixin): ...@@ -81,7 +81,7 @@ class CourseRun(TimeStampedModel, ChangedByMixin):
pacing_type = models.CharField( pacing_type = models.CharField(
max_length=255, choices=CourseMetadataCourseRun.PACING_CHOICES, db_index=True, null=True, blank=True max_length=255, choices=CourseMetadataCourseRun.PACING_CHOICES, db_index=True, null=True, blank=True
) )
staff = SortedManyToManyField(Person, null=True, blank=True, related_name='publisher_course_runs_staffed') staff = SortedManyToManyField(Person, blank=True, related_name='publisher_course_runs_staffed')
min_effort = models.PositiveSmallIntegerField( min_effort = models.PositiveSmallIntegerField(
null=True, blank=True, null=True, blank=True,
help_text=_('Estimated minimum number of hours per week needed to complete a course run.')) help_text=_('Estimated minimum number of hours per week needed to complete a course run.'))
......
# pylint: disable=no-member
from django.test import TestCase
from unittest import mock
from course_discovery.apps.course_metadata.tests.factories import CourseRunFactory, OrganizationFactory
from course_discovery.apps.course_metadata.models import CourseOrganization
from course_discovery.apps.publisher.wrappers import CourseRunWrapper
class CourseRunWrapperTests(TestCase):
""" Tests for the publisher `BaseWrapper` model. """
def setUp(self):
super(CourseRunWrapperTests, self).setUp()
self.course_run = CourseRunFactory()
course = self.course_run.course
organization_1 = OrganizationFactory()
organization_2 = OrganizationFactory()
CourseOrganization.objects.create(
course=course,
organization=organization_1,
relation_type=CourseOrganization.OWNER
)
CourseOrganization.objects.create(
course=course,
organization=organization_2,
relation_type=CourseOrganization.OWNER
)
course.save()
self.wrapped_course_run = CourseRunWrapper(self.course_run)
def test_title(self):
""" Verify that the wrapper can override course_run title. """
self.assertEqual(self.wrapped_course_run.title, self.course_run.course.title)
def test_partner(self):
""" Verify that the wrapper can return partner values. """
partner = "/".join([org.key for org in self.course_run.course.organizations.all()])
self.assertEqual(self.wrapped_course_run.partner, partner)
def test_model_attr(self):
""" Verify that the wrapper passes through object values not defined on wrapper. """
self.assertEqual(self.wrapped_course_run.key, self.course_run.key)
def test_callable(self):
mock_callable = mock.Mock(return_value='callable_value')
mock_obj = mock.MagicMock(callable_attr=mock_callable)
wrapper = CourseRunWrapper(mock_obj)
self.assertEqual(wrapper.callable_attr(), 'callable_value')
...@@ -8,6 +8,7 @@ from course_discovery.apps.publisher import views ...@@ -8,6 +8,7 @@ from course_discovery.apps.publisher import views
urlpatterns = [ urlpatterns = [
url(r'^courses/new$', views.CreateCourseView.as_view(), name='publisher_courses_new'), url(r'^courses/new$', views.CreateCourseView.as_view(), name='publisher_courses_new'),
url(r'^courses/(?P<pk>\d+)/edit/$', views.UpdateCourseView.as_view(), name='publisher_courses_edit'), url(r'^courses/(?P<pk>\d+)/edit/$', views.UpdateCourseView.as_view(), name='publisher_courses_edit'),
url(r'^course_runs/$', views.CourseRunListView.as_view(), name='publisher_course_runs'),
url(r'^course_runs/new$', views.CreateCourseRunView.as_view(), name='publisher_course_runs_new'), url(r'^course_runs/new$', views.CreateCourseRunView.as_view(), name='publisher_course_runs_new'),
url(r'^course_runs/(?P<pk>\d+)/edit/$', views.UpdateCourseRunView.as_view(), name='publisher_course_runs_edit'), url(r'^course_runs/(?P<pk>\d+)/edit/$', views.UpdateCourseRunView.as_view(), name='publisher_course_runs_edit'),
url(r'^seats/new$', views.CreateSeatView.as_view(), name='publisher_seats_new'), url(r'^seats/new$', views.CreateSeatView.as_view(), name='publisher_seats_new'),
......
...@@ -3,15 +3,28 @@ Course publisher views. ...@@ -3,15 +3,28 @@ Course publisher views.
""" """
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.views.generic import edit from django.views.generic.edit import CreateView, UpdateView
from django.views.generic.list import ListView
from course_discovery.apps.publisher.forms import CourseForm, CourseRunForm, SeatForm from course_discovery.apps.publisher.forms import CourseForm, CourseRunForm, SeatForm
from course_discovery.apps.publisher.models import Course, CourseRun, Seat from course_discovery.apps.publisher.models import Course, CourseRun, Seat
from course_discovery.apps.publisher.wrappers import CourseRunWrapper
class CourseRunListView(ListView):
""" Create Course View."""
template_name = 'publisher/course_runs_list.html'
def get_queryset(self):
return [
CourseRunWrapper(course_run) for course_run in CourseRun.objects.select_related('course').all()
]
SEATS_HIDDEN_FIELDS = ['price', 'currency', 'upgrade_deadline', 'credit_provider', 'credit_hours'] SEATS_HIDDEN_FIELDS = ['price', 'currency', 'upgrade_deadline', 'credit_provider', 'credit_hours']
# pylint: disable=attribute-defined-outside-init # pylint: disable=attribute-defined-outside-init
class CreateCourseView(edit.CreateView): class CreateCourseView(CreateView):
""" Create Course View.""" """ Create Course View."""
model = Course model = Course
form_class = CourseForm form_class = CourseForm
...@@ -26,7 +39,7 @@ class CreateCourseView(edit.CreateView): ...@@ -26,7 +39,7 @@ class CreateCourseView(edit.CreateView):
return reverse(self.success_url, kwargs={'pk': self.object.id}) return reverse(self.success_url, kwargs={'pk': self.object.id})
class UpdateCourseView(edit.UpdateView): class UpdateCourseView(UpdateView):
""" Update Course View.""" """ Update Course View."""
model = Course model = Course
form_class = CourseForm form_class = CourseForm
...@@ -41,7 +54,7 @@ class UpdateCourseView(edit.UpdateView): ...@@ -41,7 +54,7 @@ class UpdateCourseView(edit.UpdateView):
return reverse(self.success_url, kwargs={'pk': self.object.id}) return reverse(self.success_url, kwargs={'pk': self.object.id})
class CreateCourseRunView(edit.CreateView): class CreateCourseRunView(CreateView):
""" Create Course Run View.""" """ Create Course Run View."""
model = CourseRun model = CourseRun
form_class = CourseRunForm form_class = CourseRunForm
...@@ -56,7 +69,7 @@ class CreateCourseRunView(edit.CreateView): ...@@ -56,7 +69,7 @@ class CreateCourseRunView(edit.CreateView):
return reverse(self.success_url, kwargs={'pk': self.object.id}) return reverse(self.success_url, kwargs={'pk': self.object.id})
class UpdateCourseRunView(edit.UpdateView): class UpdateCourseRunView(UpdateView):
""" Update Course Run View.""" """ Update Course Run View."""
model = CourseRun model = CourseRun
form_class = CourseRunForm form_class = CourseRunForm
...@@ -71,7 +84,7 @@ class UpdateCourseRunView(edit.UpdateView): ...@@ -71,7 +84,7 @@ class UpdateCourseRunView(edit.UpdateView):
return reverse(self.success_url, kwargs={'pk': self.object.id}) return reverse(self.success_url, kwargs={'pk': self.object.id})
class CreateSeatView(edit.CreateView): class CreateSeatView(CreateView):
""" Create Seat View.""" """ Create Seat View."""
model = Seat model = Seat
form_class = SeatForm form_class = SeatForm
...@@ -91,7 +104,7 @@ class CreateSeatView(edit.CreateView): ...@@ -91,7 +104,7 @@ class CreateSeatView(edit.CreateView):
return reverse(self.success_url, kwargs={'pk': self.object.id}) return reverse(self.success_url, kwargs={'pk': self.object.id})
class UpdateSeatView(edit.UpdateView): class UpdateSeatView(UpdateView):
""" Update Seat View.""" """ Update Seat View."""
model = Seat model = Seat
form_class = SeatForm form_class = SeatForm
......
"""Publisher Wrapper Classes"""
class BaseWrapper(object):
def __init__(self, wrapped_obj):
self.wrapped_obj = wrapped_obj
def __getattr__(self, attr):
orig_attr = self.wrapped_obj.__getattribute__(attr)
if callable(orig_attr):
def hooked(*args, **kwargs):
return orig_attr(*args, **kwargs)
return hooked
else:
return orig_attr
class CourseRunWrapper(BaseWrapper):
"""Decorator for the ``CourseRun`` model."""
@property
def title(self):
return self.wrapped_obj.course.title
@property
def partner(self):
return '/'.join([org.key for org in self.wrapped_obj.course.organizations.all()])
{% extends 'base.html' %}
{% load i18n %}
{% block title %}
{% trans "Course Run List" %}
{% endblock title %}
{% block content %}
<div class="layout-full layout">
<div class="card">
<h4 class="hd-4">{% trans "Course Run List" %}</h4>
<table class="table">
<tr>
<th>{% trans "Course Title" %}</th>
<th>{% trans "Partner" %}</th>
<th>{% trans "Target Content?" %}</th>
<th>{% trans "Priority" %}</th>
<th>{% trans "Last Updated" %}</th>
</tr>
{% for course_run in object_list %}
<tr>
<td>{{ course_run.title }}</td>
<td>{{ course_run.partner }}</td>
<td>{{ course_run.target_content }}</td>
<td>{{ course_run.priority }}</td>
<td>{{ course_run.modified }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
{% endblock %}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment