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
2e024585
Commit
2e024585
authored
Oct 18, 2016
by
Matthew Piatetsky
Committed by
GitHub
Oct 18, 2016
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #380 from edx/ECOM-5680
ECOM-5680 Sortable organizations in admin program form
parents
d0565ea7
31d69a20
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
100 additions
and
16 deletions
+100
-16
course_discovery/apps/course_metadata/admin.py
+2
-0
course_discovery/apps/course_metadata/forms.py
+3
-0
course_discovery/apps/course_metadata/tests/test_admin.py
+75
-1
course_discovery/static/js/sortable_select.js
+20
-15
No files found.
course_discovery/apps/course_metadata/admin.py
View file @
2e024585
...
...
@@ -111,6 +111,8 @@ class ProgramAdmin(admin.ModelAdmin):
try
:
# courses are ordered by django id, but form.cleaned_data is ordered correctly
obj
.
courses
=
form
.
cleaned_data
.
get
(
'courses'
)
obj
.
authoring_organizations
=
form
.
cleaned_data
.
get
(
'authoring_organizations'
)
obj
.
credit_backing_organizations
=
form
.
cleaned_data
.
get
(
'credit_backing_organizations'
)
obj
.
save
()
self
.
save_error
=
False
except
ProgramPublisherException
as
ex
:
...
...
course_discovery/apps/course_metadata/forms.py
View file @
2e024585
...
...
@@ -39,18 +39,21 @@ class ProgramAdminForm(HackDjangoAutocompleteMixin, forms.ModelForm):
url
=
'admin_metadata:course-autocomplete'
,
attrs
=
{
'data-minimum-input-length'
:
3
,
'class'
:
'sortable-select'
,
},
),
'authoring_organizations'
:
autocomplete
.
ModelSelect2Multiple
(
url
=
'admin_metadata:organisation-autocomplete'
,
attrs
=
{
'data-minimum-input-length'
:
3
,
'class'
:
'sortable-select'
,
}
),
'credit_backing_organizations'
:
autocomplete
.
ModelSelect2Multiple
(
url
=
'admin_metadata:organisation-autocomplete'
,
attrs
=
{
'data-minimum-input-length'
:
3
,
'class'
:
'sortable-select'
,
}
),
'video'
:
autocomplete
.
ModelSelect2
(
...
...
course_discovery/apps/course_metadata/tests/test_admin.py
View file @
2e024585
...
...
@@ -2,7 +2,9 @@ import itertools
import
ddt
from
django.core.urlresolvers
import
reverse
from
django.test
import
TestCase
from
django.test
import
TestCase
,
LiveServerTestCase
from
selenium
import
webdriver
from
selenium.webdriver.common.action_chains
import
ActionChains
from
course_discovery.apps.course_metadata.choices
import
ProgramStatus
from
course_discovery.apps.course_metadata.forms
import
ProgramAdminForm
...
...
@@ -179,3 +181,75 @@ class AdminTests(TestCase):
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
)
class
ProgramAdminFunctionalTests
(
LiveServerTestCase
):
""" Functional Tests for Admin page."""
def
setUp
(
self
):
super
(
ProgramAdminFunctionalTests
,
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
(
2
)
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
]
)
self
.
browser
=
webdriver
.
Firefox
()
# Get Page
domain
=
self
.
live_server_url
url
=
reverse
(
'admin:course_metadata_program_change'
,
args
=
(
self
.
program
.
id
,))
self
.
browser
.
get
(
domain
+
url
)
# Login
username
=
self
.
browser
.
find_element_by_id
(
'id_username'
)
password
=
self
.
browser
.
find_element_by_id
(
'id_password'
)
username
.
send_keys
(
self
.
user
.
username
)
password
.
send_keys
(
USER_PASSWORD
)
self
.
browser
.
find_element_by_css_selector
(
'input[type=submit]'
)
.
click
()
# This window size is close to the window size when running on travis
self
.
browser
.
set_window_size
(
548
,
768
)
def
tearDown
(
self
):
super
(
ProgramAdminFunctionalTests
,
self
)
.
tearDown
()
self
.
browser
.
quit
()
def
test_all_fields
(
self
):
# Make sure that all expected fields are present
classes
=
[
css_class
for
field
in
self
.
browser
.
find_elements_by_class_name
(
'form-row'
)
for
css_class
in
field
.
get_attribute
(
'class'
)
.
split
(
' '
)
if
css_class
.
startswith
(
'field-'
)
or
css_class
.
startswith
(
'dynamic-'
)]
expected_classes
=
[
'field-title'
,
'field-subtitle'
,
'field-status'
,
'field-type'
,
'field-partner'
,
'field-banner_image'
,
'field-banner_image_url'
,
'field-card_image_url'
,
'field-marketing_slug'
,
'field-overview'
,
'field-credit_redemption_overview'
,
'field-video'
,
'field-weeks_to_complete'
,
'field-min_hours_effort_per_week'
,
'field-max_hours_effort_per_week'
,
'field-courses'
,
'field-order_courses_by_start_date'
,
'field-custom_course_runs_display'
,
'field-excluded_course_runs'
,
'field-authoring_organizations'
,
'field-credit_backing_organizations'
,
'field-job_outlook_items'
,
'field-expected_learning_items'
,
'dynamic-Program_faq'
,
'dynamic-Program_individual_endorsements'
,
'dynamic-Program_corporate_endorsements'
]
self
.
assertEqual
(
classes
,
expected_classes
)
def
test_sortable_select_drag_and_drop
(
self
):
# Get order of select elements
hidden_options_text
=
[
el
.
text
for
el
in
self
.
browser
.
find_elements_by_css_selector
(
'.field-courses option'
)]
first_select_element
=
self
.
browser
.
find_element_by_css_selector
(
'.field-courses .select2-selection__choice'
)
# Drag and drop
first_select_element
.
click
()
ActionChains
(
self
.
browser
)
.
drag_and_drop_by_offset
(
first_select_element
,
500
,
0
)
.
perform
()
# Simulate expected drag and drop
hidden_options_text
=
[
hidden_options_text
[
1
],
hidden_options_text
[
0
]]
# Get actual results of drag and drop
new_hidden_options_text
=
[
el
.
text
for
el
in
self
.
browser
.
find_elements_by_css_selector
(
'.field-courses option'
)]
self
.
assertEqual
(
hidden_options_text
,
new_hidden_options_text
)
course_discovery/static/js/sortable_select.js
View file @
2e024585
function
updateSelect2Data
(
visibleCourseTitles
){
function
updateSelect2Data
(
el
){
var
i
,
j
,
visible
Course
TitlesLength
,
visibleTitlesLength
,
selectOptionsLength
,
visible
Course
Titles
=
[],
visibleTitles
=
[],
selectOptions
=
[],
items
=
[],
selectOptionsSelector
=
'.field-courses .select2-hidden-accessible'
;
selectOptionsElement
=
$
(
el
).
find
(
'.select2-hidden-accessible'
),
selectChoicesElement
=
$
(
el
).
find
(
'.select2-selection__choice'
),
selectOptionElement
=
$
(
selectOptionsElement
).
find
(
'option'
);
$
(
'.field-courses .select2-selection__choice'
)
.
each
(
function
(
index
,
value
){
selectChoicesElement
.
each
(
function
(
index
,
value
){
if
(
value
.
title
){
visible
Course
Titles
.
push
(
value
.
title
);
visibleTitles
.
push
(
value
.
title
);
}
});
$
(
'.field-courses .select2-hidden-accessible option'
)
.
each
(
function
(
index
,
value
){
selectOptionElement
.
each
(
function
(
index
,
value
){
selectOptions
.
push
({
id
:
value
.
value
,
text
:
value
.
text
});
});
// Update select2 options with new data
visible
CourseTitlesLength
=
visibleCours
eTitles
.
length
;
visible
TitlesLength
=
visibl
eTitles
.
length
;
selectOptionsLength
=
selectOptions
.
length
;
for
(
i
=
0
;
i
<
visible
Course
TitlesLength
;
i
++
)
{
for
(
i
=
0
;
i
<
visibleTitlesLength
;
i
++
)
{
for
(
j
=
0
;
j
<
selectOptionsLength
;
j
++
)
{
if
(
selectOptions
[
j
].
text
===
visible
Course
Titles
[
i
]){
if
(
selectOptions
[
j
].
text
===
visibleTitles
[
i
]){
items
.
push
(
'<option selected="selected" value="'
+
selectOptions
[
j
].
id
+
'">'
+
selectOptions
[
j
].
text
+
'</option>'
);
}
}
}
if
(
items
){
$
(
selectOptionsSelector
)
.
html
(
items
.
join
(
'
\
n'
));
selectOptionsElement
.
html
(
items
.
join
(
'
\
n'
));
}
}
$
(
window
).
load
(
function
(){
$
(
function
()
{
var
domSelector
=
'.field-courses .select2-selection--multiple'
;
$
(
'.field-courses ul.select2-selection__rendered'
).
sortable
({
containment
:
'parent'
,
update
:
updateSelect2Data
$
(
'.sortable-select'
).
parents
(
'.form-row'
).
each
(
function
(
index
,
el
){
$
(
el
).
find
(
'ul.select2-selection__rendered'
).
sortable
({
containment
:
'parent'
,
update
:
function
(){
updateSelect2Data
(
el
);}
})
})
})
});
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