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
d2c31cf8
Commit
d2c31cf8
authored
Aug 17, 2016
by
Awais
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
ECOM-5248
Adding inline fields.
parent
edf1589e
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
314 additions
and
8 deletions
+314
-8
course_discovery/apps/course_metadata/admin.py
+51
-4
course_discovery/apps/course_metadata/forms.py
+37
-0
course_discovery/apps/course_metadata/models.py
+9
-0
course_discovery/apps/course_metadata/tests/test_admin.py
+103
-0
course_discovery/apps/course_metadata/tests/test_models.py
+42
-3
course_discovery/apps/course_metadata/urls.py
+11
-0
course_discovery/apps/course_metadata/views.py
+29
-1
course_discovery/templates/metadata/admin/course_run.html
+31
-0
course_discovery/urls.py
+1
-0
No files found.
course_discovery/apps/course_metadata/admin.py
View file @
d2c31cf8
from
django.contrib
import
admin
from
simple_history.admin
import
SimpleHistoryAdmin
from
django.core.urlresolvers
import
reverse
from
django.http
import
HttpResponseRedirect
from
simple_history.admin
import
SimpleHistoryAdmin
from
course_discovery.apps.course_metadata.forms
import
ProgramAdminForm
from
course_discovery.apps.course_metadata.models
import
*
# pylint: disable=wildcard-import
...
...
@@ -14,6 +17,27 @@ class PositionInline(admin.TabularInline):
extra
=
0
class
FaqsInline
(
admin
.
TabularInline
):
model
=
Program
.
faq
.
through
exclude
=
(
'sort_value'
,)
extra
=
1
verbose_name_plural
=
'Faqs'
class
IndividualEndorsementInline
(
admin
.
TabularInline
):
model
=
Program
.
individual_endorsements
.
through
exclude
=
(
'sort_value'
,)
extra
=
1
verbose_name_plural
=
'Individual Endorsement'
class
CorporateEndorsementsInline
(
admin
.
TabularInline
):
model
=
Program
.
corporate_endorsements
.
through
exclude
=
(
'sort_value'
,)
extra
=
1
verbose_name_plural
=
'Corporate Endorsement'
@admin.register
(
Course
)
class
CourseAdmin
(
admin
.
ModelAdmin
):
list_display
=
(
'uuid'
,
'key'
,
'title'
,)
...
...
@@ -38,12 +62,35 @@ class CourseRunAdmin(admin.ModelAdmin):
@admin.register
(
Program
)
class
ProgramAdmin
(
admin
.
ModelAdmin
):
list_display
=
(
'uuid'
,
'title'
,
'marketing_slug'
,
'type'
,)
form
=
ProgramAdminForm
inlines
=
[
FaqsInline
,
IndividualEndorsementInline
,
CorporateEndorsementsInline
]
list_display
=
(
'id'
,
'uuid'
,
'title'
,
'category'
,
'type'
,
'partner'
,
'status'
,)
list_filter
=
(
'partner'
,
'type'
,)
ordering
=
(
'uuid'
,
'title'
,)
readonly_fields
=
(
'uuid'
,)
ordering
=
(
'uuid'
,
'title'
,
'status'
)
readonly_fields
=
(
'uuid'
,
'course_runs'
,
'excluded_course_runs'
,)
search_fields
=
(
'uuid'
,
'title'
,
'marketing_slug'
)
filter_horizontal
=
(
'job_outlook_items'
,
'expected_learning_items'
,
'credit_backing_organizations'
,)
# ordering the field display on admin page.
fields
=
(
'title'
,
'status'
,
'banner_image_url'
,
'card_image_url'
,
'overview'
,
'video'
,
)
fields
+=
(
'courses'
,
'course_runs'
,
'excluded_course_runs'
)
fields
+=
filter_horizontal
def
response_add
(
self
,
request
,
obj
,
post_url_continue
=
None
):
return
HttpResponseRedirect
(
reverse
(
'admin_metadata:update_course_runs'
,
kwargs
=
{
'pk'
:
obj
.
pk
}))
def
response_change
(
self
,
request
,
obj
):
if
'_continue'
in
request
.
POST
or
'_save'
in
request
.
POST
:
return
HttpResponseRedirect
(
reverse
(
'admin_metadata:update_course_runs'
,
kwargs
=
{
'pk'
:
obj
.
pk
}))
else
:
return
HttpResponseRedirect
(
reverse
(
'admin:course_metadata_program_add'
))
@admin.register
(
ProgramType
)
class
ProgramTypeAdmin
(
admin
.
ModelAdmin
):
...
...
course_discovery/apps/course_metadata/forms.py
0 → 100644
View file @
d2c31cf8
from
django
import
forms
from
django.forms.util
import
ErrorList
from
course_discovery.apps.course_metadata.models
import
Program
,
CourseRun
class
ProgramAdminForm
(
forms
.
ModelForm
):
class
Meta
:
model
=
Program
exclude
=
(
'subtitle'
,
'category'
,
'type'
,
'marketing_slug'
,
'weeks_to_complete'
,
'min_hours_effort_per_week'
,
'max_hours_effort_per_week'
,
'authoring_organizations'
,
)
class
CourseRunSelectionForm
(
forms
.
ModelForm
):
class
Meta
:
model
=
Program
fields
=
(
'excluded_course_runs'
,)
def
__init__
(
self
,
data
=
None
,
files
=
None
,
auto_id
=
'id_
%
s'
,
prefix
=
None
,
initial
=
None
,
error_class
=
ErrorList
,
label_suffix
=
':'
,
empty_permitted
=
False
,
instance
=
None
):
super
(
CourseRunSelectionForm
,
self
)
.
__init__
(
data
,
files
,
auto_id
,
prefix
,
initial
,
error_class
,
label_suffix
,
empty_permitted
,
instance
)
query_set
=
[
course
.
pk
for
course
in
instance
.
courses
.
all
()]
self
.
fields
[
"excluded_course_runs"
]
.
widget
=
forms
.
widgets
.
CheckboxSelectMultiple
()
self
.
fields
[
"excluded_course_runs"
]
.
help_text
=
""
self
.
fields
[
'excluded_course_runs'
]
.
queryset
=
CourseRun
.
objects
.
filter
(
course__id__in
=
query_set
)
course_discovery/apps/course_metadata/models.py
View file @
d2c31cf8
...
...
@@ -512,6 +512,9 @@ class Endorsement(TimeStampedModel):
endorser
=
models
.
ForeignKey
(
Person
,
blank
=
False
,
null
=
False
)
quote
=
models
.
TextField
(
blank
=
False
,
null
=
False
)
def
__str__
(
self
):
return
self
.
endorser
.
full_name
class
CorporateEndorsement
(
TimeStampedModel
):
corporation_name
=
models
.
CharField
(
max_length
=
128
,
blank
=
False
,
null
=
False
)
...
...
@@ -519,6 +522,9 @@ class CorporateEndorsement(TimeStampedModel):
image
=
models
.
ForeignKey
(
Image
,
blank
=
True
,
null
=
True
)
individual_endorsements
=
SortedManyToManyField
(
Endorsement
)
def
__str__
(
self
):
return
self
.
corporation_name
class
FAQ
(
TimeStampedModel
):
question
=
models
.
TextField
(
blank
=
False
,
null
=
False
)
...
...
@@ -528,6 +534,9 @@ class FAQ(TimeStampedModel):
verbose_name
=
_
(
'FAQ'
)
verbose_name_plural
=
_
(
'FAQs'
)
def
__str__
(
self
):
return
self
.
question
class
ProgramType
(
TimeStampedModel
):
name
=
models
.
CharField
(
max_length
=
32
,
unique
=
True
,
null
=
False
,
blank
=
False
)
...
...
course_discovery/apps/course_metadata/tests/test_admin.py
0 → 100644
View file @
d2c31cf8
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
AdminTests
(
TestCase
):
""" Tests Admin page."""
def
setUp
(
self
):
super
(
AdminTests
,
self
)
.
setUp
()
self
.
user
=
UserFactory
(
is_staff
=
True
,
is_superuser
=
True
)
self
.
client
.
login
(
username
=
self
.
user
.
username
,
password
=
USER_PASSWORD
)
self
.
course_runs
=
factories
.
CourseRunFactory
.
create_batch
(
3
)
self
.
courses
=
[
course_run
.
course
for
course_run
in
self
.
course_runs
]
self
.
excluded_course_run
=
factories
.
CourseRunFactory
(
course
=
self
.
courses
[
0
])
self
.
program
=
factories
.
ProgramFactory
(
courses
=
self
.
courses
,
excluded_course_runs
=
[
self
.
excluded_course_run
]
)
def
test_program_detail_form
(
self
):
""" Verify in admin panel program detail form load successfully. """
response
=
self
.
client
.
get
(
reverse
(
'admin:course_metadata_program_change'
,
args
=
(
self
.
program
.
id
,)))
self
.
assertEqual
(
response
.
status_code
,
200
)
def
test_custom_course_selection_page
(
self
):
""" Verify that course selection page loads successfully. """
response
=
self
.
client
.
get
(
reverse
(
'admin_metadata:update_course_runs'
,
args
=
(
self
.
program
.
id
,)))
self
.
assertEqual
(
response
.
status_code
,
200
)
def
test_custom_course_selection_page_with_invalid_id
(
self
):
""" Verify that course selection page will return 404 for invalid program id. """
response
=
self
.
client
.
get
(
reverse
(
'admin_metadata:update_course_runs'
,
args
=
(
10
,)))
self
.
assertEqual
(
response
.
status_code
,
404
)
def
test_custom_course_selection_page_with_non_staff
(
self
):
""" Verify that course selection page will return 404 for non authorized user. """
self
.
client
.
logout
()
self
.
user
.
is_superuser
=
False
self
.
user
.
is_staff
=
False
self
.
user
.
save
()
self
.
client
.
login
(
username
=
self
.
user
.
username
,
password
=
USER_PASSWORD
)
response
=
self
.
client
.
get
(
reverse
(
'admin_metadata:update_course_runs'
,
args
=
(
self
.
program
.
id
,)))
self
.
assertEqual
(
response
.
status_code
,
404
)
def
test_page_loads_only_course_related_runs
(
self
):
""" Verify that course selection page loads only all course runs. Also marked checkboxes with
excluded courses runs only.
"""
# add some new courses and course runs
factories
.
CourseRunFactory
.
create_batch
(
2
)
response
=
self
.
client
.
get
(
reverse
(
'admin_metadata:update_course_runs'
,
args
=
(
self
.
program
.
id
,)))
html
=
'<input checked="checked" id="id_excluded_course_runs_0" '
html
+=
'name="excluded_course_runs" type="checkbox" value="{id}" />'
.
format
(
id
=
self
.
excluded_course_run
.
id
)
self
.
assertContains
(
response
,
html
)
for
run
in
self
.
course_runs
:
self
.
assertContains
(
response
,
run
.
key
)
def
test_page_with_post_new_course_run
(
self
):
""" Verify that course selection page with posting the data. """
self
.
assertEqual
(
1
,
self
.
program
.
excluded_course_runs
.
all
()
.
count
())
self
.
assertEqual
(
3
,
len
(
self
.
program
.
course_runs
.
all
()))
params
=
{
'excluded_course_runs'
:
[
self
.
excluded_course_run
.
id
,
self
.
course_runs
[
0
]
.
id
],
}
post_url
=
reverse
(
'admin_metadata:update_course_runs'
,
args
=
(
self
.
program
.
id
,))
response
=
self
.
client
.
post
(
post_url
,
params
)
self
.
assertRedirects
(
response
,
expected_url
=
reverse
(
'admin:course_metadata_program_change'
,
args
=
(
self
.
program
.
id
,)),
status_code
=
302
,
target_status_code
=
200
)
self
.
assertEqual
(
2
,
self
.
program
.
excluded_course_runs
.
all
()
.
count
())
self
.
assertEqual
(
2
,
len
(
self
.
program
.
course_runs
.
all
()))
def
test_page_with_post_without_course_run
(
self
):
""" Verify that course selection page without posting any selected excluded check run. """
self
.
assertEqual
(
1
,
self
.
program
.
excluded_course_runs
.
all
()
.
count
())
params
=
{
'excluded_course_runs'
:
[],
}
post_url
=
reverse
(
'admin_metadata:update_course_runs'
,
args
=
(
self
.
program
.
id
,))
response
=
self
.
client
.
post
(
post_url
,
params
)
self
.
assertRedirects
(
response
,
expected_url
=
reverse
(
'admin:course_metadata_program_change'
,
args
=
(
self
.
program
.
id
,)),
status_code
=
302
,
target_status_code
=
200
)
self
.
assertEqual
(
0
,
self
.
program
.
excluded_course_runs
.
all
()
.
count
())
self
.
assertEqual
(
4
,
len
(
self
.
program
.
course_runs
.
all
()))
response
=
self
.
client
.
get
(
reverse
(
'admin_metadata:update_course_runs'
,
args
=
(
self
.
program
.
id
,)))
self
.
assertNotContains
(
response
,
'<input checked="checked")'
)
course_discovery/apps/course_metadata/tests/test_models.py
View file @
d2c31cf8
...
...
@@ -11,13 +11,15 @@ from django.test import TestCase
from
freezegun
import
freeze_time
from
course_discovery.apps.core.models
import
Currency
from
course_discovery.apps.core.tests.helpers
import
make_image_file
from
course_discovery.apps.core.utils
import
SearchQuerySetWrapper
from
course_discovery.apps.course_metadata.models
import
(
AbstractNamedModel
,
AbstractMediaModel
,
AbstractValueModel
,
Course
,
CourseRun
,
SeatType
,
AbstractMediaModel
,
AbstractNamedModel
,
AbstractValueModel
,
CorporateEndorsement
,
Course
,
CourseRun
,
Endorsement
,
FAQ
,
SeatType
)
from
course_discovery.apps.course_metadata.tests
import
factories
from
course_discovery.apps.course_metadata.tests.factories
import
CourseRunFactory
from
course_discovery.apps.core.tests.helpers
import
make_image_file
from
course_discovery.apps.course_metadata.tests.factories
import
CourseRunFactory
,
ImageFactory
from
course_discovery.apps.ietf_language_tags.models
import
LanguageTag
...
...
@@ -428,3 +430,40 @@ class ProgramTypeTests(TestCase):
def
test_str
(
self
):
program_type
=
factories
.
ProgramTypeFactory
()
self
.
assertEqual
(
str
(
program_type
),
program_type
.
name
)
class
EndorsementTests
(
TestCase
):
""" Tests of the Endorsement model. """
def
setUp
(
self
):
super
(
EndorsementTests
,
self
)
.
setUp
()
self
.
person
=
factories
.
PersonFactory
()
self
.
endorsement
=
Endorsement
.
objects
.
create
(
endorser
=
self
.
person
,
quote
=
'test quote'
)
def
test_str
(
self
):
self
.
assertEqual
(
str
(
self
.
endorsement
),
self
.
person
.
full_name
)
class
CorporateEndorsementTests
(
TestCase
):
""" Tests of the CorporateEndorsement model. """
def
setUp
(
self
):
super
(
CorporateEndorsementTests
,
self
)
.
setUp
()
self
.
corporation_name
=
'test org'
self
.
individual_endorsements
=
CorporateEndorsement
.
objects
.
create
(
corporation_name
=
self
.
corporation_name
,
statement
=
'test statement'
,
image
=
ImageFactory
()
)
def
test_str
(
self
):
self
.
assertEqual
(
str
(
self
.
individual_endorsements
),
self
.
corporation_name
)
class
FAQTests
(
TestCase
):
""" Tests of the FAQ model. """
def
test_str
(
self
):
question
=
'test question'
faq
=
FAQ
.
objects
.
create
(
question
=
question
,
answer
=
'test'
)
self
.
assertEqual
(
str
(
faq
),
question
)
course_discovery/apps/course_metadata/urls.py
0 → 100644
View file @
d2c31cf8
"""
URLs for the admin autocomplete lookups.
"""
from
django.conf.urls
import
url
from
course_discovery.apps.course_metadata.views
import
CourseRunSelectionAdmin
urlpatterns
=
[
url
(
r'^update_course_runs/(?P<pk>\d+)/$'
,
CourseRunSelectionAdmin
.
as_view
(),
name
=
'update_course_runs'
,),
]
course_discovery/apps/course_metadata/views.py
View file @
d2c31cf8
from
django.views.generic
import
TemplateView
from
django.contrib
import
messages
from
django.core.urlresolvers
import
reverse
from
django.http
import
HttpResponseRedirect
,
Http404
from
django.views.generic
import
TemplateView
,
UpdateView
from
course_discovery.apps.course_metadata.forms
import
CourseRunSelectionForm
from
course_discovery.apps.course_metadata.models
import
Program
class
QueryPreviewView
(
TemplateView
):
...
...
@@ -7,3 +13,25 @@ class QueryPreviewView(TemplateView):
class
SearchDemoView
(
TemplateView
):
template_name
=
'demo/search.html'
# pylint: disable=attribute-defined-outside-init
class
CourseRunSelectionAdmin
(
UpdateView
):
""" Create Course View."""
model
=
Program
template_name
=
'metadata/admin/course_run.html'
form_class
=
CourseRunSelectionForm
def
get_context_data
(
self
,
**
kwargs
):
if
self
.
request
.
user
.
is_authenticated
()
and
self
.
request
.
user
.
is_staff
:
return
super
(
CourseRunSelectionAdmin
,
self
)
.
get_context_data
(
**
kwargs
)
raise
Http404
def
form_valid
(
self
,
form
):
self
.
object
=
form
.
save
()
message
=
'The program course runs was changed successfully.'
messages
.
add_message
(
self
.
request
,
messages
.
SUCCESS
,
message
)
return
HttpResponseRedirect
(
self
.
get_success_url
())
def
get_success_url
(
self
):
return
reverse
(
'admin:course_metadata_program_change'
,
args
=
(
self
.
object
.
id
,))
course_discovery/templates/metadata/admin/course_run.html
0 → 100644
View file @
d2c31cf8
{% extends 'admin/base_site.html' %}
{% load i18n %}
{% block title %}
{% trans "CourseRun Selection Form" %}
{% endblock title %}
{% block content %}
<form
class=
"form"
method=
"post"
action=
""
>
{% csrf_token %}
<fieldset
class=
"module aligned "
>
{% for field in form %}
<div
class=
"form-row field-overview"
>
<label
for=
"{{ field.name }}"
>
{{ field.label }}
</label>
<div
class=
"form-row"
>
{{ field }}
</div>
</div>
{% endfor %}
</fieldset>
{% block submit_buttons_bottom %}
<div
class=
"submit-row"
>
<a
href=
"{% url 'admin:course_metadata_program_changelist' %}"
>
<input
type=
"button"
name=
"Cancel"
value=
"{% trans 'Cancel' %}"
>
</a>
<input
type=
"submit"
value=
"{% trans 'Save Course Run' %}"
class=
"default"
name=
"_save"
/>
</div>
{% endblock %}
</form>
{% endblock %}
course_discovery/urls.py
View file @
d2c31cf8
...
...
@@ -27,6 +27,7 @@ from course_discovery.apps.course_metadata.views import QueryPreviewView
admin
.
autodiscover
()
urlpatterns
=
auth_urlpatterns
+
[
url
(
r'^admin/course_metadata/'
,
include
(
'course_discovery.apps.course_metadata.urls'
,
namespace
=
'admin_metadata'
)),
url
(
r'^admin/'
,
include
(
admin
.
site
.
urls
)),
url
(
r'^api/'
,
include
(
'course_discovery.apps.api.urls'
,
namespace
=
'api'
)),
# Use the same auth views for all logins, including those originating from the browseable API.
...
...
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