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
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
180 additions
and
74 deletions
+180
-74
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
+40
-22
cms/lib/xblock/tagging/test.py
+39
-20
cms/static/js/xblock_asides/structured_tags.js
+26
-19
cms/templates/structured_tags_block.html
+30
-13
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):
...
@@ -14,6 +14,8 @@ class TagCategories(models.Model):
class
Meta
(
object
):
class
Meta
(
object
):
app_label
=
"tagging"
app_label
=
"tagging"
ordering
=
(
'title'
,)
ordering
=
(
'title'
,)
verbose_name
=
"tag category"
verbose_name_plural
=
"tag categories"
def
__unicode__
(
self
):
def
__unicode__
(
self
):
return
"[TagCategories] {}: {}"
.
format
(
self
.
name
,
self
.
title
)
return
"[TagCategories] {}: {}"
.
format
(
self
.
name
,
self
.
title
)
...
@@ -35,6 +37,7 @@ class TagAvailableValues(models.Model):
...
@@ -35,6 +37,7 @@ class TagAvailableValues(models.Model):
class
Meta
(
object
):
class
Meta
(
object
):
app_label
=
"tagging"
app_label
=
"tagging"
ordering
=
(
'id'
,)
ordering
=
(
'id'
,)
verbose_name
=
"available tag value"
def
__unicode__
(
self
):
def
__unicode__
(
self
):
return
"[TagAvailableValues] {}: {}"
.
format
(
self
.
category
,
self
.
value
)
return
"[TagAvailableValues] {}: {}"
.
format
(
self
.
category
,
self
.
value
)
cms/lib/xblock/tagging/tagging.py
View file @
5fe79dbd
...
@@ -46,19 +46,26 @@ class StructuredTagsAside(XBlockAside):
...
@@ -46,19 +46,26 @@ class StructuredTagsAside(XBlockAside):
if
isinstance
(
block
,
CapaModule
):
if
isinstance
(
block
,
CapaModule
):
tags
=
[]
tags
=
[]
for
tag
in
self
.
get_available_tags
():
for
tag
in
self
.
get_available_tags
():
values
=
tag
.
get_values
()
tag_available_
values
=
tag
.
get_values
()
current_value
=
self
.
saved_tags
.
get
(
tag
.
name
,
None
)
tag_current_values
=
self
.
saved_tags
.
get
(
tag
.
name
,
[]
)
if
current_value
is
not
None
and
current_value
not
in
values
:
if
isinstance
(
tag_current_values
,
basestring
):
values
.
insert
(
0
,
current_value
)
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
({
tags
.
append
({
'key'
:
tag
.
name
,
'key'
:
tag
.
name
,
'title'
:
tag
.
title
,
'title'
:
tag
.
title
,
'values'
:
values
,
'values'
:
tag_values_available_to_choose
,
'current_value
'
:
current_value
'current_value
s'
:
tag_current_values
,
})
})
fragment
=
Fragment
(
render_to_string
(
'structured_tags_block.html'
,
{
'tags'
:
tags
,
fragment
=
Fragment
(
render_to_string
(
'structured_tags_block.html'
,
{
'tags'
:
tags
,
'tags_count'
:
len
(
tags
),
'block_location'
:
block
.
location
}))
'block_location'
:
block
.
location
}))
fragment
.
add_javascript_url
(
self
.
_get_studio_resource_url
(
'/js/xblock_asides/structured_tags.js'
))
fragment
.
add_javascript_url
(
self
.
_get_studio_resource_url
(
'/js/xblock_asides/structured_tags.js'
))
fragment
.
initialize_js
(
'StructuredTagsInit'
)
fragment
.
initialize_js
(
'StructuredTagsInit'
)
...
@@ -71,25 +78,36 @@ class StructuredTagsAside(XBlockAside):
...
@@ -71,25 +78,36 @@ class StructuredTagsAside(XBlockAside):
"""
"""
Handler to save choosen tags with connected XBlock
Handler to save choosen tags with connected XBlock
"""
"""
found
=
False
try
:
if
'tag'
not
in
request
.
params
:
posted_data
=
request
.
json
return
Response
(
"The required parameter 'tag' is not passed"
,
status
=
400
)
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
():
for
av_tag
in
self
.
get_available_tags
():
if
av_tag
.
name
==
tag
[
0
]:
if
av_tag
.
name
in
posted_data
and
posted_data
[
av_tag
.
name
]:
if
tag
[
1
]
==
''
:
tag_available_values
=
av_tag
.
get_values
()
self
.
saved_tags
[
tag
[
0
]]
=
None
tag_current_values
=
self
.
saved_tags
.
get
(
av_tag
.
name
,
[])
found
=
True
elif
tag
[
1
]
in
av_tag
.
get_values
():
if
isinstance
(
tag_current_values
,
basestring
):
self
.
saved_tags
[
tag
[
0
]]
=
tag
[
1
]
tag_current_values
=
[
tag_current_values
]
found
=
True
for
posted_tag_value
in
posted_data
[
av_tag
.
name
]:
if
not
found
:
if
posted_tag_value
not
in
tag_available_values
and
posted_tag_value
not
in
tag_current_values
:
return
Response
(
"Invalid 'tag' parameter"
,
status
=
400
)
return
Response
(
"Invalid tag value was passed:
%
s"
%
posted_tag_value
,
status
=
400
)
return
Response
()
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
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
...
@@ -3,6 +3,7 @@ Tests for the Studio Tagging XBlockAside
"""
"""
import
ddt
import
ddt
import
json
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
...
@@ -38,6 +39,7 @@ class StructuredTagsAsideTestCase(ModuleStoreTestCase):
...
@@ -38,6 +39,7 @@ class StructuredTagsAsideTestCase(ModuleStoreTestCase):
self
.
aside_name
=
'tagging_aside'
self
.
aside_name
=
'tagging_aside'
self
.
aside_tag_dif
=
'difficulty'
self
.
aside_tag_dif
=
'difficulty'
self
.
aside_tag_dif_value
=
'Hard'
self
.
aside_tag_dif_value
=
'Hard'
self
.
aside_tag_dif_value2
=
'Easy'
self
.
aside_tag_lo
=
'learning_outcome'
self
.
aside_tag_lo
=
'learning_outcome'
course
=
CourseFactory
.
create
(
default_store
=
ModuleStoreEnum
.
Type
.
split
)
course
=
CourseFactory
.
create
(
default_store
=
ModuleStoreEnum
.
Type
.
split
)
...
@@ -152,27 +154,27 @@ class StructuredTagsAsideTestCase(ModuleStoreTestCase):
...
@@ -152,27 +154,27 @@ class StructuredTagsAsideTestCase(ModuleStoreTestCase):
self
.
assertEquals
(
div_node
.
get
(
'data-runtime-version'
),
'1'
)
self
.
assertEquals
(
div_node
.
get
(
'data-runtime-version'
),
'1'
)
self
.
assertIn
(
'xblock_asides-v1'
,
div_node
.
get
(
'class'
))
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
)
self
.
assertEquals
(
len
(
select_nodes
),
2
)
select_node1
=
select_nodes
[
0
]
select_node1
=
select_nodes
[
0
]
self
.
assertEquals
(
select_node1
.
get
(
'name'
),
self
.
aside_tag_dif
)
self
.
assertEquals
(
select_node1
.
get
(
'name'
),
self
.
aside_tag_dif
)
option_nodes1
=
select_node1
.
xpath
(
'option'
)
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
]
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
]
select_node2
=
select_nodes
[
1
]
self
.
assertEquals
(
select_node2
.
get
(
'name'
),
self
.
aside_tag_lo
)
self
.
assertEquals
(
select_node2
.
get
(
'name'
),
self
.
aside_tag_lo
)
self
.
assertEquals
(
select_node2
.
get
(
'multiple'
),
'multiple'
)
option_nodes2
=
select_node2
.
xpath
(
'option'
)
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
]
option_values2
=
[
opt_elem
.
text
for
opt_elem
in
option_nodes2
if
opt_elem
.
text
]
self
.
assertEquals
(
option_values2
,
[
'Not selected'
,
'Learned nothing'
,
self
.
assertEquals
(
option_values2
,
[
'Learned a few things'
,
'Learned everything'
,
'Learned nothing'
])
'Learned a few things'
,
'Learned everything'
])
# Now ensure the acid_aside is not in the result
# Now ensure the acid_aside is not in the result
self
.
assertNotRegexpMatches
(
problem_html
,
r"data-block-type=[\"\']acid_aside[\"\']"
)
self
.
assertNotRegexpMatches
(
problem_html
,
r"data-block-type=[\"\']acid_aside[\"\']"
)
...
@@ -195,27 +197,44 @@ class StructuredTagsAsideTestCase(ModuleStoreTestCase):
...
@@ -195,27 +197,44 @@ class StructuredTagsAsideTestCase(ModuleStoreTestCase):
client
=
AjaxEnabledTestClient
()
client
=
AjaxEnabledTestClient
()
client
.
login
(
username
=
self
.
user
.
username
,
password
=
self
.
user_password
)
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
)
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
)
self
.
assertEqual
(
response
.
status_code
,
400
)
val
=
'
%
s:undefined'
%
self
.
aside_tag_dif
response
=
client
.
post
(
handler_url
,
json
.
dumps
({
self
.
aside_tag_dif
:
[
'undefined1'
,
'undefined2'
]}),
response
=
client
.
post
(
path
=
handler_url
,
data
=
{
'tag'
:
val
}
)
content_type
=
"application/json"
)
self
.
assertEqual
(
response
.
status_code
,
400
)
self
.
assertEqual
(
response
.
status_code
,
400
)
val
=
'
%
s:
%
s'
%
(
self
.
aside_tag_dif
,
self
.
aside_tag_dif_value
)
def
_test_helper_func
(
problem_location
):
response
=
client
.
post
(
path
=
handler_url
,
data
=
{
'tag'
:
val
})
"""
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
)
self
.
assertEqual
(
response
.
status_code
,
200
)
problem
=
modulestore
()
.
get_item
(
self
.
problem
.
location
)
tag_aside
=
_test_helper_func
(
self
.
problem
.
location
)
asides
=
problem
.
runtime
.
get_asides
(
problem
)
self
.
assertIsNotNone
(
tag_aside
,
"Necessary StructuredTagsAside object isn't found"
)
tag_aside
=
None
self
.
assertEqual
(
tag_aside
.
saved_tags
[
self
.
aside_tag_dif
],
[
self
.
aside_tag_dif_value
])
for
aside
in
asides
:
if
isinstance
(
aside
,
StructuredTagsAside
):
response
=
client
.
post
(
handler_url
,
json
.
dumps
({
self
.
aside_tag_dif
:
[
self
.
aside_tag_dif_value
,
tag_aside
=
aside
self
.
aside_tag_dif_value2
]}),
break
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
.
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 @@
...
@@ -3,29 +3,36 @@
function
StructuredTagsView
(
runtime
,
element
)
{
function
StructuredTagsView
(
runtime
,
element
)
{
var
$element
=
$
(
element
);
var
$element
=
$
(
element
);
var
saveTagsInProgress
=
false
;
$element
.
find
(
'select'
).
each
(
function
()
{
$
(
$element
).
find
(
'.save_tags'
).
click
(
function
(
e
)
{
var
loader
=
this
;
var
dataToPost
=
{};
var
sts
=
$
(
this
).
attr
(
'structured-tags-select-init'
);
if
(
!
saveTagsInProgress
)
{
saveTagsInProgress
=
true
;
if
(
typeof
sts
===
typeof
undefined
||
sts
===
false
)
{
$element
.
find
(
'select'
).
each
(
function
()
{
$
(
this
).
attr
(
'structured-tags-select-init'
,
1
);
dataToPost
[
$
(
this
).
attr
(
'name'
)]
=
$
(
this
).
val
();
$
(
this
).
change
(
function
(
e
)
{
});
e
.
preventDefault
();
var
selectedKey
=
$
(
loader
).
find
(
'option:selected'
).
val
();
e
.
preventDefault
();
runtime
.
notify
(
'save'
,
{
state
:
'start'
,
element
:
element
,
message
:
gettext
(
'Updating Tags'
)
});
$
.
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'
,
{
runtime
.
notify
(
'save'
,
{
state
:
'start'
,
state
:
'end'
,
element
:
element
,
element
:
element
message
:
gettext
(
'Updating Tags'
)
});
$
.
post
(
runtime
.
handlerUrl
(
element
,
'save_tags'
),
{
'tag'
:
$
(
loader
).
attr
(
'name'
)
+
':'
+
selectedKey
}).
done
(
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=
"xblock-render"
class=
"studio-xblock-wrapper"
>
% for tag in tags:
<div
class=
"wrapper"
>
<label
for=
"tags_${tag['key']}_${block_location}"
>
${tag['title']}
</label>
:
% for tag in tags:
<select
id=
"tags_${tag['key']}_${block_location}"
name=
"${tag['key']}"
>
<div
class=
"wrapper-content left"
>
<option
value=
""
${''
if
tag
['
current_value
']
else
'
selected=
""
'}
>
Not selected
</option>
<div><label
for=
"tags_${tag['key']}_${block_location}"
>
${tag['title']}
</label>
:
</div>
% for v in tag['values']:
<div>
<
%
<select
id=
"tags_${tag['key']}_${block_location}"
name=
"${tag['key']}"
multiple=
"multiple"
>
selected =
''
% for v in tag['values']:
if
v =
=
tag
['
current_value
']
:
<
%
selected =
'selected'
selected =
''
%
>
if
v
in
tag
['
current_values
']
:
<option
value=
"${v}"
${
selected
}
>
${v}
</option>
selected =
'selected'
%
>
<option
value=
"${v}"
${
selected
}
>
${v}
</option>
% endfor
</select>
</div>
</div>
% endfor
% endfor
</select>
% 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>
</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