Commit cced9af5 by Awais

ECOM-5248

Adding inline fields.
parent 301fa881
...@@ -71,18 +71,16 @@ class ProgramAdmin(admin.ModelAdmin): ...@@ -71,18 +71,16 @@ class ProgramAdmin(admin.ModelAdmin):
search_fields = ('uuid', 'title', 'marketing_slug') search_fields = ('uuid', 'title', 'marketing_slug')
filter_horizontal = ( filter_horizontal = ('job_outlook_items', 'expected_learning_items',)
'job_outlook_items', 'expected_learning_items',
'credit_backing_organizations', 'authoring_organizations',
)
# ordering the field display on admin page. # ordering the field display on admin page.
fields = ( fields = (
'title', 'status', 'type', 'banner_image', 'title', 'status', 'type', 'partner', 'banner_image',
'banner_image_url', 'card_image_url', 'overview', 'video', 'banner_image_url', 'card_image_url', 'overview', 'video',
) )
fields += ( fields += (
'courses', 'course_runs', 'excluded_course_runs' 'courses', 'course_runs', 'excluded_course_runs', 'authoring_organizations',
'credit_backing_organizations'
) )
fields += filter_horizontal fields += filter_horizontal
......
...@@ -3,6 +3,7 @@ from django.core.exceptions import ValidationError ...@@ -3,6 +3,7 @@ from django.core.exceptions import ValidationError
from django.forms.util import ErrorList from django.forms.util import ErrorList
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from dal import autocomplete
from course_discovery.apps.course_metadata.models import Program, CourseRun from course_discovery.apps.course_metadata.models import Program, CourseRun
...@@ -13,12 +14,38 @@ class ProgramAdminForm(forms.ModelForm): ...@@ -13,12 +14,38 @@ class ProgramAdminForm(forms.ModelForm):
exclude = ( exclude = (
'subtitle', 'category', 'marketing_slug', 'weeks_to_complete', 'subtitle', 'category', 'marketing_slug', 'weeks_to_complete',
'min_hours_effort_per_week', 'max_hours_effort_per_week', 'min_hours_effort_per_week', 'max_hours_effort_per_week',
'partner',
) )
widgets = {
'courses': autocomplete.ModelSelect2Multiple(
url='admin_metadata:course-autocomplete',
attrs={
'data-minimum-input-length': 3,
},
),
'authoring_organizations': autocomplete.ModelSelect2Multiple(
url='admin_metadata:organisation-autocomplete',
attrs={
'data-minimum-input-length': 3,
}
),
'credit_backing_organizations': autocomplete.ModelSelect2Multiple(
url='admin_metadata:organisation-autocomplete',
attrs={
'data-minimum-input-length': 3,
}
),
'video': autocomplete.ModelSelect2(
url='admin_metadata:video-autocomplete',
attrs={
'data-minimum-input-length': 3,
}
),
}
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(ProgramAdminForm, self).__init__(*args, **kwargs) super(ProgramAdminForm, self).__init__(*args, **kwargs)
self.fields['type'].required = True self.fields['type'].required = True
self.fields['courses'].required = False
def clean(self): def clean(self):
status = self.cleaned_data.get('status') status = self.cleaned_data.get('status')
......
from django.db.models import Q
from dal import autocomplete
from .models import Course, Organization, Video
class CourseAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
if self.request.user.is_authenticated() and self.request.user.is_staff:
qs = Course.objects.all()
if self.q:
qs = qs.filter(Q(key__icontains=self.q) | Q(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:
qs = Organization.objects.all()
if self.q:
qs = qs.filter(Q(key__icontains=self.q) | Q(name__icontains=self.q))
return qs
return []
class VideoAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
if self.request.user.is_authenticated() and self.request.user.is_staff:
qs = Video.objects.all()
if self.q:
qs = qs.filter(Q(description__icontains=self.q) | Q(src__icontains=self.q))
return qs
return []
...@@ -94,6 +94,9 @@ class Video(AbstractMediaModel): ...@@ -94,6 +94,9 @@ class Video(AbstractMediaModel):
""" Video model. """ """ Video model. """
image = models.ForeignKey(Image, null=True, blank=True) image = models.ForeignKey(Image, null=True, blank=True)
def __str__(self):
return '{src}: {description}'.format(src=self.src, description=self.description)
class LevelType(AbstractNamedModel): class LevelType(AbstractNamedModel):
""" LevelType model. """ """ LevelType model. """
......
{% extends "admin/change_form.html" %}
{% block extrahead %}
{{ block.super }}
<style type="text/css">
.select2-container{
width: auto !important;
}
</style>
{% endblock %}
...@@ -124,7 +124,6 @@ class AdminTests(TestCase): ...@@ -124,7 +124,6 @@ class AdminTests(TestCase):
status using admin form. status using admin form.
""" """
data = self._post_data(status) data = self._post_data(status)
data['status'] = status
self.valid_post_form(data, {'banner_image': ''}) self.valid_post_form(data, {'banner_image': ''})
@ddt.data( @ddt.data(
...@@ -136,7 +135,6 @@ class AdminTests(TestCase): ...@@ -136,7 +135,6 @@ class AdminTests(TestCase):
def test_program_with_image(self, status): def test_program_with_image(self, status):
""" Verify that new program can be added with `image` and any status.""" """ Verify that new program can be added with `image` and any status."""
data = self._post_data(status) data = self._post_data(status)
data['status'] = status
self.valid_post_form(data, {'banner_image': make_image_file('test_banner.jpg')}) self.valid_post_form(data, {'banner_image': make_image_file('test_banner.jpg')})
def _post_data(self, status): def _post_data(self, status):
...@@ -144,7 +142,8 @@ class AdminTests(TestCase): ...@@ -144,7 +142,8 @@ class AdminTests(TestCase):
'title': 'some test title', 'title': 'some test title',
'courses': [self.courses[0].id], 'courses': [self.courses[0].id],
'type': self.program.type.id, 'type': self.program.type.id,
'status': status 'status': status,
'partner': self.program.partner.id
} }
def valid_post_form(self, data, file_data): def valid_post_form(self, data, file_data):
...@@ -153,3 +152,14 @@ class AdminTests(TestCase): ...@@ -153,3 +152,14 @@ class AdminTests(TestCase):
program = form.save() program = form.save()
response = self.client.get(reverse('admin:course_metadata_program_change', args=(program.id,))) response = self.client.get(reverse('admin:course_metadata_program_change', args=(program.id,)))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_new_program_without_courses(self):
""" Verify that new program can be added without `courses`."""
data = self._post_data(Program.ProgramStatus.Unpublished)
data['courses'] = []
form = ProgramAdminForm(data)
self.assertTrue(form.is_valid())
program = form.save()
self.assertEqual(0, program.courses.all().count())
response = self.client.get(reverse('admin:course_metadata_program_change', args=(program.id,)))
self.assertEqual(response.status_code, 200)
import json
import ddt
from django.core.urlresolvers import reverse
from django.test import TestCase
from course_discovery.apps.course_metadata.tests import factories
from course_discovery.apps.core.tests.factories import UserFactory, USER_PASSWORD
# pylint: disable=no-member
@ddt.ddt
class AutocompleteTests(TestCase):
""" Tests for autocomplete lookups."""
def setUp(self):
super(AutocompleteTests, self).setUp()
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')
self.organizations = factories.OrganizationFactory.create_batch(3)
@ddt.data('dum', 'ing')
def test_course_autocomplete(self, search_key):
""" Verify course autocomplete returns the data. """
response = self.client.get(reverse('admin_metadata:course-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
self.courses[0].key = 'edx/dummy/key'
self.courses[0].title = 'this is some thing new'
self.courses[0].save()
response = self.client.get(
reverse('admin_metadata:course-autocomplete') + '?q={title}'.format(title=search_key)
)
data = json.loads(response.content.decode('utf-8'))
self.assertEqual(data['results'][0]['text'], str(self.courses[0]))
def test_course_autocomplete_un_authorize_user(self):
""" Verify course autocomplete returns empty list for un-authorized users. """
self._make_user_non_staff()
response = self.client.get(reverse('admin_metadata:course-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. """
response = self.client.get(reverse('admin_metadata:organisation-autocomplete'))
data = json.loads(response.content.decode('utf-8'))
self.assertEqual(response.status_code, 200)
self.assertEqual(len(data['results']), 3)
self.organizations[0].key = 'Mirco'
self.organizations[0].name = 'testing name'
self.organizations[0].save()
response = self.client.get(
reverse('admin_metadata:organisation-autocomplete') + '?q={key}'.format(
key=search_key
)
)
data = json.loads(response.content.decode('utf-8'))
self.assertEqual(data['results'][0]['text'], str(self.organizations[0]))
self.assertEqual(len(data['results']), 1)
def test_organization_autocomplete_un_authorize_user(self):
""" Verify Organization autocomplete returns empty list for un-authorized users. """
self._make_user_non_staff()
response = self.client.get(reverse('admin_metadata:organisation-autocomplete'))
data = json.loads(response.content.decode('utf-8'))
self.assertEqual(data['results'], [])
@ddt.data('you', 'des')
def test_video_autocomplete(self, search_key):
""" Verify video autocomplete returns the data. """
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.courses[0].video.src = 'http://www.youtube.com/dummyurl'
self.courses[0].video.description = 'testing description'
self.courses[0].video.save()
response = self.client.get(
reverse('admin_metadata:video-autocomplete') + '?q={key}'.format(
key=search_key
)
)
data = json.loads(response.content.decode('utf-8'))
self.assertEqual(data['results'][0]['text'], str(self.courses[0].video))
self.assertEqual(len(data['results']), 1)
def test_video_autocomplete_un_authorize_user(self):
""" Verify video autocomplete returns empty list for un-authorized users. """
self._make_user_non_staff()
response = self.client.get(reverse('admin_metadata:video-autocomplete'))
data = json.loads(response.content.decode('utf-8'))
self.assertEqual(data['results'], [])
def _make_user_non_staff(self):
self.client.logout()
self.user = UserFactory(is_staff=False)
self.user.save()
self.client.login(username=self.user.username, password=USER_PASSWORD)
...@@ -4,8 +4,13 @@ URLs for the admin autocomplete lookups. ...@@ -4,8 +4,13 @@ URLs for the admin autocomplete lookups.
from django.conf.urls import url from django.conf.urls import url
from course_discovery.apps.course_metadata.views import CourseRunSelectionAdmin from course_discovery.apps.course_metadata.views import CourseRunSelectionAdmin
from course_discovery.apps.course_metadata.lookups import (
CourseAutocomplete, OrganizationAutocomplete, VideoAutocomplete
)
urlpatterns = [ urlpatterns = [
url(r'^update_course_runs/(?P<pk>\d+)/$', CourseRunSelectionAdmin.as_view(), name='update_course_runs',), 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'^organisation-autocomplete/$', OrganizationAutocomplete.as_view(), name='organisation-autocomplete',),
url(r'^video-autocomplete/$', VideoAutocomplete.as_view(), name='video-autocomplete',),
] ]
...@@ -22,6 +22,8 @@ ALLOWED_HOSTS = [] ...@@ -22,6 +22,8 @@ ALLOWED_HOSTS = []
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
'dal',
'dal_select2',
'django.contrib.admin', 'django.contrib.admin',
'django.contrib.auth', 'django.contrib.auth',
'django.contrib.contenttypes', 'django.contrib.contenttypes',
......
boto==2.42.0 boto==2.42.0
cryptography==1.4 cryptography==1.4
django==1.8.14 django==1.8.14
django-autocomplete-light==3.1.8
django-choices==1.4.3 django-choices==1.4.3
django-compressor==2.0 django-compressor==2.0
django-contrib-comments==1.7.2 django-contrib-comments==1.7.2
......
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