Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
C
course-discovery
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
course-discovery
Commits
cced9af5
Commit
cced9af5
authored
Aug 17, 2016
by
Awais
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
ECOM-5248
Adding inline fields.
parent
301fa881
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
213 additions
and
11 deletions
+213
-11
course_discovery/apps/course_metadata/admin.py
+4
-6
course_discovery/apps/course_metadata/forms.py
+28
-1
course_discovery/apps/course_metadata/lookups.py
+41
-0
course_discovery/apps/course_metadata/models.py
+3
-0
course_discovery/apps/course_metadata/templates/admin/course_metadata/change_form.html
+9
-0
course_discovery/apps/course_metadata/tests/test_admin.py
+13
-3
course_discovery/apps/course_metadata/tests/test_lookups.py
+106
-0
course_discovery/apps/course_metadata/urls.py
+6
-1
course_discovery/settings/base.py
+2
-0
requirements/base.txt
+1
-0
No files found.
course_discovery/apps/course_metadata/admin.py
View file @
cced9af5
...
@@ -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
...
...
course_discovery/apps/course_metadata/forms.py
View file @
cced9af5
...
@@ -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'
)
...
...
course_discovery/apps/course_metadata/lookups.py
0 → 100644
View file @
cced9af5
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
[]
course_discovery/apps/course_metadata/models.py
View file @
cced9af5
...
@@ -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. """
...
...
course_discovery/apps/course_metadata/templates/admin/course_metadata/change_form.html
0 → 100644
View file @
cced9af5
{% extends "admin/change_form.html" %}
{% block extrahead %}
{{ block.super }}
<style
type=
"text/css"
>
.select2-container
{
width
:
auto
!important
;
}
</style>
{% endblock %}
course_discovery/apps/course_metadata/tests/test_admin.py
View file @
cced9af5
...
@@ -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
)
course_discovery/apps/course_metadata/tests/test_lookups.py
0 → 100644
View file @
cced9af5
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
)
course_discovery/apps/course_metadata/urls.py
View file @
cced9af5
...
@@ -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'
,),
]
]
course_discovery/settings/base.py
View file @
cced9af5
...
@@ -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'
,
...
...
requirements/base.txt
View file @
cced9af5
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
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment