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):
)
full_description = models.TextField(default=None, null=True, blank=True, verbose_name=_('About this course'))
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(
LevelType, default=None, null=True, blank=True, related_name='publisher_courses', verbose_name=_('Course level')
......@@ -70,7 +70,7 @@ class CourseRun(TimeStampedModel, ChangedByMixin):
(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)
start = models.DateTimeField(null=True, blank=True)
......@@ -81,7 +81,7 @@ class CourseRun(TimeStampedModel, ChangedByMixin):
pacing_type = models.CharField(
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(
null=True, blank=True,
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
urlpatterns = [
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'^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/(?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'),
......
......@@ -3,15 +3,28 @@ Course publisher views.
"""
from django.core.urlresolvers import reverse
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.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']
# pylint: disable=attribute-defined-outside-init
class CreateCourseView(edit.CreateView):
class CreateCourseView(CreateView):
""" Create Course View."""
model = Course
form_class = CourseForm
......@@ -26,7 +39,7 @@ class CreateCourseView(edit.CreateView):
return reverse(self.success_url, kwargs={'pk': self.object.id})
class UpdateCourseView(edit.UpdateView):
class UpdateCourseView(UpdateView):
""" Update Course View."""
model = Course
form_class = CourseForm
......@@ -41,7 +54,7 @@ class UpdateCourseView(edit.UpdateView):
return reverse(self.success_url, kwargs={'pk': self.object.id})
class CreateCourseRunView(edit.CreateView):
class CreateCourseRunView(CreateView):
""" Create Course Run View."""
model = CourseRun
form_class = CourseRunForm
......@@ -56,7 +69,7 @@ class CreateCourseRunView(edit.CreateView):
return reverse(self.success_url, kwargs={'pk': self.object.id})
class UpdateCourseRunView(edit.UpdateView):
class UpdateCourseRunView(UpdateView):
""" Update Course Run View."""
model = CourseRun
form_class = CourseRunForm
......@@ -71,7 +84,7 @@ class UpdateCourseRunView(edit.UpdateView):
return reverse(self.success_url, kwargs={'pk': self.object.id})
class CreateSeatView(edit.CreateView):
class CreateSeatView(CreateView):
""" Create Seat View."""
model = Seat
form_class = SeatForm
......@@ -91,7 +104,7 @@ class CreateSeatView(edit.CreateView):
return reverse(self.success_url, kwargs={'pk': self.object.id})
class UpdateSeatView(edit.UpdateView):
class UpdateSeatView(UpdateView):
""" Update Seat View."""
model = Seat
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