Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
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
edx-platform
Commits
5fe79dbd
Commit
5fe79dbd
authored
Nov 03, 2016
by
Dmitry Viskov
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Allow multiple values for a single tag
parent
1912176c
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
155 additions
and
49 deletions
+155
-49
cms/lib/xblock/tagging/admin.py
+20
-0
cms/lib/xblock/tagging/migrations/0002_auto_20170116_1541.py
+22
-0
cms/lib/xblock/tagging/models.py
+3
-0
cms/lib/xblock/tagging/tagging.py
+37
-19
cms/lib/xblock/tagging/test.py
+35
-16
cms/static/js/xblock_asides/structured_tags.js
+17
-10
cms/templates/structured_tags_block.html
+21
-4
No files found.
cms/lib/xblock/tagging/admin.py
0 → 100644
View file @
5fe79dbd
"""
Admin registration for tags models
"""
from
django.contrib
import
admin
from
.models
import
TagCategories
,
TagAvailableValues
class
TagCategoriesAdmin
(
admin
.
ModelAdmin
):
"""Admin for TagCategories"""
search_fields
=
(
'name'
,
'title'
)
list_display
=
(
'id'
,
'name'
,
'title'
)
class
TagAvailableValuesAdmin
(
admin
.
ModelAdmin
):
"""Admin for TagAvailableValues"""
list_display
=
(
'id'
,
'category'
,
'value'
)
admin
.
site
.
register
(
TagCategories
,
TagCategoriesAdmin
)
admin
.
site
.
register
(
TagAvailableValues
,
TagAvailableValuesAdmin
)
cms/lib/xblock/tagging/migrations/0002_auto_20170116_1541.py
0 → 100644
View file @
5fe79dbd
# -*- coding: utf-8 -*-
from
__future__
import
unicode_literals
from
django.db
import
migrations
,
models
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'tagging'
,
'0001_initial'
),
]
operations
=
[
migrations
.
AlterModelOptions
(
name
=
'tagavailablevalues'
,
options
=
{
'ordering'
:
(
'id'
,),
'verbose_name'
:
'available tag value'
},
),
migrations
.
AlterModelOptions
(
name
=
'tagcategories'
,
options
=
{
'ordering'
:
(
'title'
,),
'verbose_name'
:
'tag category'
,
'verbose_name_plural'
:
'tag categories'
},
),
]
cms/lib/xblock/tagging/models.py
View file @
5fe79dbd
...
...
@@ -14,6 +14,8 @@ class TagCategories(models.Model):
class
Meta
(
object
):
app_label
=
"tagging"
ordering
=
(
'title'
,)
verbose_name
=
"tag category"
verbose_name_plural
=
"tag categories"
def
__unicode__
(
self
):
return
"[TagCategories] {}: {}"
.
format
(
self
.
name
,
self
.
title
)
...
...
@@ -35,6 +37,7 @@ class TagAvailableValues(models.Model):
class
Meta
(
object
):
app_label
=
"tagging"
ordering
=
(
'id'
,)
verbose_name
=
"available tag value"
def
__unicode__
(
self
):
return
"[TagAvailableValues] {}: {}"
.
format
(
self
.
category
,
self
.
value
)
cms/lib/xblock/tagging/tagging.py
View file @
5fe79dbd
...
...
@@ -46,19 +46,26 @@ class StructuredTagsAside(XBlockAside):
if
isinstance
(
block
,
CapaModule
):
tags
=
[]
for
tag
in
self
.
get_available_tags
():
values
=
tag
.
get_values
()
current_value
=
self
.
saved_tags
.
get
(
tag
.
name
,
None
)
tag_available_
values
=
tag
.
get_values
()
tag_current_values
=
self
.
saved_tags
.
get
(
tag
.
name
,
[]
)
if
current_value
is
not
None
and
current_value
not
in
values
:
values
.
insert
(
0
,
current_value
)
if
isinstance
(
tag_current_values
,
basestring
):
tag_current_values
=
[
tag_current_values
]
tag_values_not_exists
=
[
cur_val
for
cur_val
in
tag_current_values
if
cur_val
not
in
tag_available_values
]
tag_values_available_to_choose
=
tag_available_values
+
tag_values_not_exists
tag_values_available_to_choose
.
sort
()
tags
.
append
({
'key'
:
tag
.
name
,
'title'
:
tag
.
title
,
'values'
:
values
,
'current_value
'
:
current_value
'values'
:
tag_values_available_to_choose
,
'current_value
s'
:
tag_current_values
,
})
fragment
=
Fragment
(
render_to_string
(
'structured_tags_block.html'
,
{
'tags'
:
tags
,
'tags_count'
:
len
(
tags
),
'block_location'
:
block
.
location
}))
fragment
.
add_javascript_url
(
self
.
_get_studio_resource_url
(
'/js/xblock_asides/structured_tags.js'
))
fragment
.
initialize_js
(
'StructuredTagsInit'
)
...
...
@@ -71,25 +78,36 @@ class StructuredTagsAside(XBlockAside):
"""
Handler to save choosen tags with connected XBlock
"""
found
=
False
if
'tag'
not
in
request
.
params
:
return
Response
(
"The required parameter 'tag' is not passed"
,
status
=
400
)
try
:
posted_data
=
request
.
json
except
ValueError
:
return
Response
(
"Invalid request body"
,
status
=
400
)
tag
=
request
.
params
[
'tag'
]
.
split
(
':'
)
saved_tags
=
{}
need_update
=
False
for
av_tag
in
self
.
get_available_tags
():
if
av_tag
.
name
==
tag
[
0
]:
if
tag
[
1
]
==
''
:
self
.
saved_tags
[
tag
[
0
]]
=
None
found
=
True
elif
tag
[
1
]
in
av_tag
.
get_values
():
self
.
saved_tags
[
tag
[
0
]]
=
tag
[
1
]
found
=
True
if
av_tag
.
name
in
posted_data
and
posted_data
[
av_tag
.
name
]:
tag_available_values
=
av_tag
.
get_values
()
tag_current_values
=
self
.
saved_tags
.
get
(
av_tag
.
name
,
[])
if
isinstance
(
tag_current_values
,
basestring
):
tag_current_values
=
[
tag_current_values
]
if
not
found
:
return
Response
(
"Invalid 'tag' parameter"
,
status
=
400
)
for
posted_tag_value
in
posted_data
[
av_tag
.
name
]:
if
posted_tag_value
not
in
tag_available_values
and
posted_tag_value
not
in
tag_current_values
:
return
Response
(
"Invalid tag value was passed:
%
s"
%
posted_tag_value
,
status
=
400
)
saved_tags
[
av_tag
.
name
]
=
posted_data
[
av_tag
.
name
]
need_update
=
True
if
av_tag
.
name
in
posted_data
:
need_update
=
True
if
need_update
:
self
.
saved_tags
=
saved_tags
return
Response
()
else
:
return
Response
(
"Tags parameters were not passed"
,
status
=
400
)
def
get_event_context
(
self
,
event_type
,
event
):
# pylint: disable=unused-argument
"""
...
...
cms/lib/xblock/tagging/test.py
View file @
5fe79dbd
...
...
@@ -3,6 +3,7 @@ Tests for the Studio Tagging XBlockAside
"""
import
ddt
import
json
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
...
...
@@ -38,6 +39,7 @@ class StructuredTagsAsideTestCase(ModuleStoreTestCase):
self
.
aside_name
=
'tagging_aside'
self
.
aside_tag_dif
=
'difficulty'
self
.
aside_tag_dif_value
=
'Hard'
self
.
aside_tag_dif_value2
=
'Easy'
self
.
aside_tag_lo
=
'learning_outcome'
course
=
CourseFactory
.
create
(
default_store
=
ModuleStoreEnum
.
Type
.
split
)
...
...
@@ -152,27 +154,27 @@ class StructuredTagsAsideTestCase(ModuleStoreTestCase):
self
.
assertEquals
(
div_node
.
get
(
'data-runtime-version'
),
'1'
)
self
.
assertIn
(
'xblock_asides-v1'
,
div_node
.
get
(
'class'
))
select_nodes
=
div_node
.
xpath
(
'div/select'
)
select_nodes
=
div_node
.
xpath
(
"div//select[@multiple='multiple']"
)
self
.
assertEquals
(
len
(
select_nodes
),
2
)
select_node1
=
select_nodes
[
0
]
self
.
assertEquals
(
select_node1
.
get
(
'name'
),
self
.
aside_tag_dif
)
option_nodes1
=
select_node1
.
xpath
(
'option'
)
self
.
assertEquals
(
len
(
option_nodes1
),
4
)
self
.
assertEquals
(
len
(
option_nodes1
),
3
)
option_values1
=
[
opt_elem
.
text
for
opt_elem
in
option_nodes1
]
self
.
assertEquals
(
option_values1
,
[
'
Not selected'
,
'Easy'
,
'Medium'
,
'Hard
'
])
self
.
assertEquals
(
option_values1
,
[
'
Easy'
,
'Hard'
,
'Medium
'
])
select_node2
=
select_nodes
[
1
]
self
.
assertEquals
(
select_node2
.
get
(
'name'
),
self
.
aside_tag_lo
)
self
.
assertEquals
(
select_node2
.
get
(
'multiple'
),
'multiple'
)
option_nodes2
=
select_node2
.
xpath
(
'option'
)
self
.
assertEquals
(
len
(
option_nodes2
),
4
)
self
.
assertEquals
(
len
(
option_nodes2
),
3
)
option_values2
=
[
opt_elem
.
text
for
opt_elem
in
option_nodes2
if
opt_elem
.
text
]
self
.
assertEquals
(
option_values2
,
[
'Not selected'
,
'Learned nothing'
,
'Learned a few things'
,
'Learned everything'
])
self
.
assertEquals
(
option_values2
,
[
'Learned a few things'
,
'Learned everything'
,
'Learned nothing'
])
# Now ensure the acid_aside is not in the result
self
.
assertNotRegexpMatches
(
problem_html
,
r"data-block-type=[\"\']acid_aside[\"\']"
)
...
...
@@ -195,27 +197,44 @@ class StructuredTagsAsideTestCase(ModuleStoreTestCase):
client
=
AjaxEnabledTestClient
()
client
.
login
(
username
=
self
.
user
.
username
,
password
=
self
.
user_password
)
response
=
client
.
post
(
path
=
handler_url
,
data
=
{}
)
response
=
client
.
post
(
handler_url
,
json
.
dumps
({}),
content_type
=
"application/json"
)
self
.
assertEqual
(
response
.
status_code
,
400
)
response
=
client
.
post
(
path
=
handler_url
,
data
=
{
'tag'
:
'undefined_tag:undefined'
})
response
=
client
.
post
(
handler_url
,
json
.
dumps
({
'undefined_tag'
:
[
'undefined1'
,
'undefined2'
]}),
content_type
=
"application/json"
)
self
.
assertEqual
(
response
.
status_code
,
400
)
val
=
'
%
s:undefined'
%
self
.
aside_tag_dif
response
=
client
.
post
(
path
=
handler_url
,
data
=
{
'tag'
:
val
}
)
response
=
client
.
post
(
handler_url
,
json
.
dumps
({
self
.
aside_tag_dif
:
[
'undefined1'
,
'undefined2'
]}),
content_type
=
"application/json"
)
self
.
assertEqual
(
response
.
status_code
,
400
)
val
=
'
%
s:
%
s'
%
(
self
.
aside_tag_dif
,
self
.
aside_tag_dif_value
)
response
=
client
.
post
(
path
=
handler_url
,
data
=
{
'tag'
:
val
})
self
.
assertEqual
(
response
.
status_code
,
200
)
problem
=
modulestore
()
.
get_item
(
self
.
problem
.
location
)
def
_test_helper_func
(
problem_location
):
"""
Helper function
"""
problem
=
modulestore
()
.
get_item
(
problem_
location
)
asides
=
problem
.
runtime
.
get_asides
(
problem
)
tag_aside
=
None
for
aside
in
asides
:
if
isinstance
(
aside
,
StructuredTagsAside
):
tag_aside
=
aside
break
return
tag_aside
response
=
client
.
post
(
handler_url
,
json
.
dumps
({
self
.
aside_tag_dif
:
[
self
.
aside_tag_dif_value
]}),
content_type
=
"application/json"
)
self
.
assertEqual
(
response
.
status_code
,
200
)
tag_aside
=
_test_helper_func
(
self
.
problem
.
location
)
self
.
assertIsNotNone
(
tag_aside
,
"Necessary StructuredTagsAside object isn't found"
)
self
.
assertEqual
(
tag_aside
.
saved_tags
[
self
.
aside_tag_dif
],
[
self
.
aside_tag_dif_value
])
response
=
client
.
post
(
handler_url
,
json
.
dumps
({
self
.
aside_tag_dif
:
[
self
.
aside_tag_dif_value
,
self
.
aside_tag_dif_value2
]}),
content_type
=
"application/json"
)
self
.
assertEqual
(
response
.
status_code
,
200
)
tag_aside
=
_test_helper_func
(
self
.
problem
.
location
)
self
.
assertIsNotNone
(
tag_aside
,
"Necessary StructuredTagsAside object isn't found"
)
self
.
assertEqual
(
tag_aside
.
saved_tags
[
self
.
aside_tag_dif
],
self
.
aside_tag_dif_value
)
self
.
assertEqual
(
tag_aside
.
saved_tags
[
self
.
aside_tag_dif
],
[
self
.
aside_tag_dif_value
,
self
.
aside_tag_dif_value2
])
cms/static/js/xblock_asides/structured_tags.js
View file @
5fe79dbd
...
...
@@ -3,29 +3,36 @@
function
StructuredTagsView
(
runtime
,
element
)
{
var
$element
=
$
(
element
);
var
saveTagsInProgress
=
false
;
$
(
$element
).
find
(
'.save_tags'
).
click
(
function
(
e
)
{
var
dataToPost
=
{};
if
(
!
saveTagsInProgress
)
{
saveTagsInProgress
=
true
;
$element
.
find
(
'select'
).
each
(
function
()
{
var
loader
=
this
;
var
sts
=
$
(
this
).
attr
(
'structured-tags-select-init'
);
dataToPost
[
$
(
this
).
attr
(
'name'
)]
=
$
(
this
).
val
()
;
}
);
if
(
typeof
sts
===
typeof
undefined
||
sts
===
false
)
{
$
(
this
).
attr
(
'structured-tags-select-init'
,
1
);
$
(
this
).
change
(
function
(
e
)
{
e
.
preventDefault
();
var
selectedKey
=
$
(
loader
).
find
(
'option:selected'
).
val
();
runtime
.
notify
(
'save'
,
{
state
:
'start'
,
element
:
element
,
message
:
gettext
(
'Updating Tags'
)
});
$
.
post
(
runtime
.
handlerUrl
(
element
,
'save_tags'
),
{
'tag'
:
$
(
loader
).
attr
(
'name'
)
+
':'
+
selectedKey
}).
done
(
function
()
{
$
.
ajax
({
type
:
'POST'
,
url
:
runtime
.
handlerUrl
(
element
,
'save_tags'
),
data
:
JSON
.
stringify
(
dataToPost
),
dataType
:
'json'
,
contentType
:
'application/json; charset=utf-8'
}).
always
(
function
()
{
runtime
.
notify
(
'save'
,
{
state
:
'end'
,
element
:
element
});
})
;
saveTagsInProgress
=
false
;
});
}
});
...
...
cms/templates/structured_tags_block.html
View file @
5fe79dbd
<div
class=
"xblock-render"
class=
"studio-xblock-wrapper"
>
<div
class=
"wrapper"
>
% for tag in tags:
<label
for=
"tags_${tag['key']}_${block_location}"
>
${tag['title']}
</label>
:
<select
id=
"tags_${tag['key']}_${block_location}"
name=
"${tag['key']}"
>
<option
value=
""
${''
if
tag
['
current_value
']
else
'
selected=
""
'}
>
Not selected
</option>
<div
class=
"wrapper-content left"
>
<div><label
for=
"tags_${tag['key']}_${block_location}"
>
${tag['title']}
</label>
:
</div>
<div>
<select
id=
"tags_${tag['key']}_${block_location}"
name=
"${tag['key']}"
multiple=
"multiple"
>
% for v in tag['values']:
<
%
selected =
''
if
v =
=
tag
['
current_value
']
:
if
v
in
tag
['
current_values
']
:
selected =
'selected'
%
>
<option
value=
"${v}"
${
selected
}
>
${v}
</option>
% endfor
</select>
</div>
</div>
% endfor
% if tags_count > 0:
<div
class=
"wrapper-content left"
>
<div
class=
"outline-content"
>
<div
class=
"add-item"
>
<a
href=
"javascript: void(0);"
class=
"button button-new save_tags"
title=
"Save tags"
>
<span
class=
"action-button-text"
>
Save tags
</span>
</a>
</div>
</div>
</div>
% endif
</div>
</div>
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