Commit 5fe79dbd by Dmitry Viskov

Allow multiple values for a single tag

parent 1912176c
"""
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)
# -*- 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'},
),
]
......@@ -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)
......@@ -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_values': 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 not found:
return Response("Invalid 'tag' parameter", status=400)
return Response()
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]
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
"""
......
......@@ -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})
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)
problem = modulestore().get_item(self.problem.location)
asides = problem.runtime.get_asides(problem)
tag_aside = None
for aside in asides:
if isinstance(aside, StructuredTagsAside):
tag_aside = aside
break
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])
......@@ -3,29 +3,36 @@
function StructuredTagsView(runtime, element) {
var $element = $(element);
var saveTagsInProgress = false;
$element.find('select').each(function() {
var loader = this;
var sts = $(this).attr('structured-tags-select-init');
$($element).find('.save_tags').click(function(e) {
var dataToPost = {};
if (!saveTagsInProgress) {
saveTagsInProgress = true;
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();
$element.find('select').each(function() {
dataToPost[$(this).attr('name')] = $(this).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', {
state: 'start',
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
});
state: 'end',
element: element
});
saveTagsInProgress = false;
});
}
});
......
<div class="xblock-render" class="studio-xblock-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>
% for v in tag['values']:
<%
selected = ''
if v == tag['current_value']:
selected = 'selected'
%>
<option value="${v}" ${selected}>${v}</option>
<div class="wrapper">
% for tag in tags:
<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 in tag['current_values']:
selected = 'selected'
%>
<option value="${v}" ${selected}>${v}</option>
% endfor
</select>
</div>
</div>
% 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>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment