Commit f2cfa6a4 by Bill DeRusha

Add migration, data migration, and admin for canonical_course_run

Add migration for canonical_course_run to course
Add data migration assigning a default canonical course_run to courses
Add type-ahead search for canonical course_run to course admin

ECOM-6226
parent 2e0757af
......@@ -4,7 +4,7 @@ from django.http import HttpResponseRedirect
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
from course_discovery.apps.course_metadata.forms import ProgramAdminForm
from course_discovery.apps.course_metadata.forms import ProgramAdminForm, CourseAdminForm
from course_discovery.apps.course_metadata.models import * # pylint: disable=wildcard-import
from course_discovery.apps.course_metadata.publishers import ProgramPublisherException
......@@ -42,6 +42,7 @@ class CorporateEndorsementsInline(admin.TabularInline):
@admin.register(Course)
class CourseAdmin(admin.ModelAdmin):
form = CourseAdminForm
list_display = ('uuid', 'key', 'title',)
list_filter = ('partner',)
ordering = ('key', 'title',)
......
......@@ -5,7 +5,7 @@ from django.forms.utils import ErrorList
from django.utils.translation import ugettext_lazy as _
from course_discovery.apps.course_metadata.choices import ProgramStatus
from course_discovery.apps.course_metadata.models import Program, CourseRun
from course_discovery.apps.course_metadata.models import Program, CourseRun, Course
def filter_choices_to_render_with_order_preserved(self, selected_choices):
......@@ -99,3 +99,17 @@ class CourseRunSelectionForm(forms.ModelForm):
self.fields['excluded_course_runs'].queryset = CourseRun.objects.filter(
course__id__in=query_set
)
class CourseAdminForm(forms.ModelForm):
class Meta:
model = Course
fields = '__all__'
widgets = {
'canonical_course_run': autocomplete.ModelSelect2(
url='admin_metadata:course-run-autocomplete',
attrs={
'data-minimum-input-length': 3,
}
),
}
from django.db.models import Q
from dal import autocomplete
from .models import Course, Organization, Video
from .models import Course, CourseRun, Organization, Video
class CourseAutocomplete(autocomplete.Select2QuerySetView):
......@@ -16,6 +16,18 @@ class CourseAutocomplete(autocomplete.Select2QuerySetView):
return []
class CourseRunAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
if self.request.user.is_authenticated() and self.request.user.is_staff:
qs = CourseRun.objects.all().select_related('course')
if self.q:
qs = qs.filter(Q(key__icontains=self.q) | Q(course__title__icontains=self.q))
return qs
return []
class OrganizationAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
if self.request.user.is_authenticated() and self.request.user.is_staff:
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('course_metadata', '0035_auto_20161103_2129'),
]
operations = [
migrations.AddField(
model_name='course',
name='canonical_course_run',
field=models.OneToOneField(null=True, default=None, blank=True, to='course_metadata.CourseRun', related_name='canonical_for_course'),
),
]
from django.db import migrations
from course_discovery.apps.course_metadata.choices import CourseRunStatus
def create_canonical(apps, schema_editor):
"""Create the canonical course run associations."""
Course = apps.get_model('course_metadata', 'Course')
courses = Course.objects.prefetch_related('course_runs').all()
for course in courses:
course_runs = course.course_runs.all().order_by('-start')
published_course_runs = course_runs.filter(status=CourseRunStatus.Published)
if published_course_runs:
# If there is a published course_run use the latest
canonical_course_run = published_course_runs[0]
else:
# otherwise just use the latest in general
canonical_course_run = course_runs.first()
course.canonical_course_run = canonical_course_run
course.save()
def delete_canonical(apps, schema_editor):
"""Delete the canonical course run associations."""
Course = apps.get_model('course_metadata', 'Course')
Course.objects.all().update(canonical_course_run=None)
class Migration(migrations.Migration):
dependencies = [
('course_metadata', '0036_course_canonical_course_run'),
]
operations = [
migrations.RunPython(create_canonical, reverse_code=delete_canonical),
]
......@@ -232,6 +232,9 @@ class Course(TimeStampedModel):
""" Course model. """
partner = models.ForeignKey(Partner)
uuid = models.UUIDField(default=uuid4, editable=False, verbose_name=_('UUID'))
canonical_course_run = models.OneToOneField(
'course_metadata.CourseRun', related_name='canonical_for_course', default=None, null=True, blank=True
)
key = models.CharField(max_length=255)
title = models.CharField(max_length=255, default=None, null=True, blank=True)
short_description = models.CharField(max_length=255, default=None, null=True, blank=True)
......
......@@ -17,6 +17,8 @@ class AutocompleteTests(TestCase):
self.user = UserFactory(is_staff=True)
self.client.login(username=self.user.username, password=USER_PASSWORD)
self.courses = factories.CourseFactory.create_batch(3, title='Some random course title')
for course in self.courses:
factories.CourseRunFactory(course=course)
self.organizations = factories.OrganizationFactory.create_batch(3)
@ddt.data('dum', 'ing')
......@@ -43,6 +45,34 @@ class AutocompleteTests(TestCase):
data = json.loads(response.content.decode('utf-8'))
self.assertEqual(data['results'], [])
@ddt.data('ing', 'dum')
def test_course_run_autocomplete(self, search_key):
""" Verify course run autocomplete returns the data. """
response = self.client.get(reverse('admin_metadata:course-run-autocomplete'))
data = json.loads(response.content.decode('utf-8'))
self.assertEqual(response.status_code, 200)
self.assertEqual(len(data['results']), 3)
# update the first course title
course = self.courses[0]
course.title = 'this is some thing new'
course.save()
course_run = self.courses[0].course_runs.first()
course_run.key = 'edx/dummy/testrun'
course_run.save()
response = self.client.get(
reverse('admin_metadata:course-run-autocomplete') + '?q={q}'.format(q=search_key)
)
data = json.loads(response.content.decode('utf-8'))
self.assertEqual(data['results'][0]['text'], str(course_run))
def test_course_run_autocomplete_un_authorize_user(self):
""" Verify course run autocomplete returns empty list for un-authorized users. """
self._make_user_non_staff()
response = self.client.get(reverse('admin_metadata:course-run-autocomplete'))
data = json.loads(response.content.decode('utf-8'))
self.assertEqual(data['results'], [])
@ddt.data('irc', 'ing')
def test_organization_autocomplete(self, search_key):
""" Verify Organization autocomplete returns the data. """
......@@ -77,7 +107,7 @@ class AutocompleteTests(TestCase):
response = self.client.get(reverse('admin_metadata:video-autocomplete'))
data = json.loads(response.content.decode('utf-8'))
self.assertEqual(response.status_code, 200)
self.assertEqual(len(data['results']), 3)
self.assertEqual(len(data['results']), 6)
self.courses[0].video.src = 'http://www.youtube.com/dummyurl'
self.courses[0].video.description = 'testing description'
......
......@@ -5,12 +5,13 @@ from django.conf.urls import url
from course_discovery.apps.course_metadata.views import CourseRunSelectionAdmin
from course_discovery.apps.course_metadata.lookups import (
CourseAutocomplete, OrganizationAutocomplete, VideoAutocomplete
CourseAutocomplete, CourseRunAutocomplete, OrganizationAutocomplete, VideoAutocomplete
)
urlpatterns = [
url(r'^update_course_runs/(?P<pk>\d+)/$', CourseRunSelectionAdmin.as_view(), name='update_course_runs',),
url(r'^course-autocomplete/$', CourseAutocomplete.as_view(), name='course-autocomplete',),
url(r'^course-run-autocomplete/$', CourseRunAutocomplete.as_view(), name='course-run-autocomplete',),
url(r'^organisation-autocomplete/$', OrganizationAutocomplete.as_view(), name='organisation-autocomplete',),
url(r'^video-autocomplete/$', VideoAutocomplete.as_view(), name='video-autocomplete',),
]
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