Commit 18360657 by Dmitry Viskov

Dynamic values for the selectboxes with tags (tags are stored in the database tables)

parent 47e4804b
...@@ -903,6 +903,9 @@ INSTALLED_APPS = ( ...@@ -903,6 +903,9 @@ INSTALLED_APPS = (
# Management commands used for configuration automation # Management commands used for configuration automation
'edx_management_commands.management_commands', 'edx_management_commands.management_commands',
# Tagging
'cms.lib.xblock.tagging',
) )
......
# -*- coding: utf-8 -*-
"""
Structured Tagging based on XBlockAsides
"""
from .tagging import StructuredTagsAside
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
]
operations = [
migrations.CreateModel(
name='TagAvailableValues',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('value', models.CharField(max_length=255)),
],
options={
'ordering': ('id',),
},
),
migrations.CreateModel(
name='TagCategories',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=255, unique=True)),
('title', models.CharField(max_length=255)),
],
options={
'ordering': ('title',),
},
),
migrations.AddField(
model_name='tagavailablevalues',
name='category',
field=models.ForeignKey(to='tagging.TagCategories'),
),
]
"""
Django Model for tags
"""
from django.db import models
class TagCategories(models.Model):
"""
This model represents tag categories.
"""
name = models.CharField(max_length=255, unique=True)
title = models.CharField(max_length=255)
class Meta(object):
app_label = "tagging"
ordering = ('title',)
def __unicode__(self):
return "[TagCategories] {}: {}".format(self.name, self.title)
def get_values(self):
"""
Return the list of available values for the particular category
"""
return [t.value for t in TagAvailableValues.objects.filter(category=self)]
class TagAvailableValues(models.Model):
"""
This model represents available values for tags.
"""
category = models.ForeignKey(TagCategories, db_index=True)
value = models.CharField(max_length=255)
class Meta(object):
app_label = "tagging"
ordering = ('id',)
def __unicode__(self):
return "[TagAvailableValues] {}: {}".format(self.category, self.value)
# -*- coding: utf-8 -*-
""" """
Structured Tagging based on XBlockAsides Structured Tagging based on XBlockAsides
""" """
...@@ -7,64 +8,15 @@ from xblock.fragment import Fragment ...@@ -7,64 +8,15 @@ from xblock.fragment import Fragment
from xblock.fields import Scope, Dict from xblock.fields import Scope, Dict
from xmodule.x_module import STUDENT_VIEW from xmodule.x_module import STUDENT_VIEW
from xmodule.capa_module import CapaModule from xmodule.capa_module import CapaModule
from abc import ABCMeta, abstractproperty
from edxmako.shortcuts import render_to_string from edxmako.shortcuts import render_to_string
from django.conf import settings from django.conf import settings
from webob import Response from webob import Response
from collections import OrderedDict from .models import TagCategories
_ = lambda text: text _ = lambda text: text
class AbstractTag(object):
"""
Abstract class for tags
"""
__metaclass__ = ABCMeta
@abstractproperty
def key(self):
"""
Subclasses must implement key
"""
raise NotImplementedError('Subclasses must implement key')
@abstractproperty
def name(self):
"""
Subclasses must implement name
"""
raise NotImplementedError('Subclasses must implement name')
@abstractproperty
def allowed_values(self):
"""
Subclasses must implement allowed_values
"""
raise NotImplementedError('Subclasses must implement allowed_values')
class DifficultyTag(AbstractTag):
"""
Particular implementation tags for difficulty
"""
@property
def key(self):
""" Identifier for the difficulty selector """
return 'difficulty_tag'
@property
def name(self):
""" Label for the difficulty selector """
return _('Difficulty')
@property
def allowed_values(self):
""" Allowed values for the difficulty selector """
return OrderedDict([('easy', 'Easy'), ('medium', 'Medium'), ('hard', 'Hard')])
class StructuredTagsAside(XBlockAside): class StructuredTagsAside(XBlockAside):
""" """
Aside that allows tagging blocks Aside that allows tagging blocks
...@@ -72,7 +24,12 @@ class StructuredTagsAside(XBlockAside): ...@@ -72,7 +24,12 @@ class StructuredTagsAside(XBlockAside):
saved_tags = Dict(help=_("Dictionary with the available tags"), saved_tags = Dict(help=_("Dictionary with the available tags"),
scope=Scope.content, scope=Scope.content,
default={},) default={},)
available_tags = [DifficultyTag()]
def get_available_tags(self):
"""
Return available tags
"""
return TagCategories.objects.all()
def _get_studio_resource_url(self, relative_url): def _get_studio_resource_url(self, relative_url):
""" """
...@@ -88,14 +45,21 @@ class StructuredTagsAside(XBlockAside): ...@@ -88,14 +45,21 @@ class StructuredTagsAside(XBlockAside):
""" """
if isinstance(block, CapaModule): if isinstance(block, CapaModule):
tags = [] tags = []
for tag in self.available_tags: for tag in self.get_available_tags():
values = tag.get_values()
current_value = self.saved_tags.get(tag.name, None)
if current_value is not None and current_value not in values:
values.insert(0, current_value)
tags.append({ tags.append({
'key': tag.key, 'key': tag.name,
'title': tag.name, 'title': tag.title,
'values': tag.allowed_values, 'values': values,
'current_value': self.saved_tags.get(tag.key, None), 'current_value': current_value
}) })
fragment = Fragment(render_to_string('structured_tags_block.html', {'tags': tags})) fragment = Fragment(render_to_string('structured_tags_block.html', {'tags': tags,
'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')
return fragment return fragment
...@@ -113,14 +77,14 @@ class StructuredTagsAside(XBlockAside): ...@@ -113,14 +77,14 @@ class StructuredTagsAside(XBlockAside):
tag = request.params['tag'].split(':') tag = request.params['tag'].split(':')
for av_tag in self.available_tags: for av_tag in self.get_available_tags():
if av_tag.key == tag[0]: if av_tag.name == tag[0]:
if tag[1] in av_tag.allowed_values: if tag[1] == '':
self.saved_tags[tag[0]] = tag[1]
found = True
elif tag[1] == '':
self.saved_tags[tag[0]] = None self.saved_tags[tag[0]] = None
found = True found = True
elif tag[1] in av_tag.get_values():
self.saved_tags[tag[0]] = tag[1]
found = True
if not found: if not found:
return Response("Invalid 'tag' parameter", status=400) return Response("Invalid 'tag' parameter", status=400)
......
...@@ -7,7 +7,11 @@ from xmodule.modulestore.django import modulestore ...@@ -7,7 +7,11 @@ from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from xblock_config.models import StudioConfig from xblock_config.models import StudioConfig
from xblock.fields import ScopeIds
from xblock.runtime import DictKeyValueStore, KvsFieldData
from xblock.test.tools import TestRuntime
from cms.lib.xblock.tagging import StructuredTagsAside from cms.lib.xblock.tagging import StructuredTagsAside
from cms.lib.xblock.tagging.models import TagCategories, TagAvailableValues
from contentstore.views.preview import get_preview_fragment from contentstore.views.preview import get_preview_fragment
from contentstore.utils import reverse_usage_url from contentstore.utils import reverse_usage_url
from contentstore.tests.utils import AjaxEnabledTestClient from contentstore.tests.utils import AjaxEnabledTestClient
...@@ -30,8 +34,9 @@ class StructuredTagsAsideTestCase(ModuleStoreTestCase): ...@@ -30,8 +34,9 @@ class StructuredTagsAsideTestCase(ModuleStoreTestCase):
""" """
self.user_password = super(StructuredTagsAsideTestCase, self).setUp() self.user_password = super(StructuredTagsAsideTestCase, self).setUp()
self.aside_name = 'tagging_aside' self.aside_name = 'tagging_aside'
self.aside_tag = 'difficulty_tag' self.aside_tag_dif = 'difficulty'
self.aside_tag_value = 'hard' self.aside_tag_dif_value = 'Hard'
self.aside_tag_lo = 'learning_outcome'
course = CourseFactory.create(default_store=ModuleStoreEnum.Type.split) course = CourseFactory.create(default_store=ModuleStoreEnum.Type.split)
self.course = ItemFactory.create( self.course = ItemFactory.create(
...@@ -75,16 +80,47 @@ class StructuredTagsAsideTestCase(ModuleStoreTestCase): ...@@ -75,16 +80,47 @@ class StructuredTagsAsideTestCase(ModuleStoreTestCase):
user_id=self.user.id user_id=self.user.id
) )
_init_data = [
{
'name': 'difficulty',
'title': 'Difficulty',
'values': ['Easy', 'Medium', 'Hard'],
},
{
'name': 'learning_outcome',
'title': 'Learning outcome',
'values': ['Learned nothing', 'Learned a few things', 'Learned everything']
}
]
for tag in _init_data:
category = TagCategories.objects.create(name=tag['name'], title=tag['title'])
for val in tag['values']:
TagAvailableValues.objects.create(category=category, value=val)
config = StudioConfig.current() config = StudioConfig.current()
config.enabled = True config.enabled = True
config.save() config.save()
def tearDown(self):
TagAvailableValues.objects.all().delete()
TagCategories.objects.all().delete()
super(StructuredTagsAsideTestCase, self).tearDown()
def test_aside_contains_tags(self): def test_aside_contains_tags(self):
""" """
Checks that available_tags list is not empty Checks that available_tags list is not empty
""" """
self.assertGreater(len(StructuredTagsAside.available_tags), 0, sids = ScopeIds(user_id="bob",
"StructuredTagsAside should contains at least one available tag") block_type="bobs-type",
def_id="definition-id",
usage_id="usage-id")
key_store = DictKeyValueStore()
field_data = KvsFieldData(key_store)
runtime = TestRuntime(services={'field-data': field_data}) # pylint: disable=abstract-class-instantiated
xblock_aside = StructuredTagsAside(scope_ids=sids, runtime=runtime)
available_tags = xblock_aside.get_available_tags()
self.assertEquals(len(available_tags), 2, "StructuredTagsAside should contains two tag categories")
def test_preview_html(self): def test_preview_html(self):
""" """
...@@ -115,10 +151,26 @@ class StructuredTagsAsideTestCase(ModuleStoreTestCase): ...@@ -115,10 +151,26 @@ class StructuredTagsAsideTestCase(ModuleStoreTestCase):
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')
self.assertEquals(len(select_nodes), 1) 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)
option_values1 = [opt_elem.text for opt_elem in option_nodes1]
self.assertEquals(option_values1, ['Not selected', 'Easy', 'Medium', 'Hard'])
select_node2 = select_nodes[1]
self.assertEquals(select_node2.get('name'), self.aside_tag_lo)
option_nodes2 = select_node2.xpath('option')
self.assertEquals(len(option_nodes2), 4)
select_node = select_nodes[0] option_values2 = [opt_elem.text for opt_elem in option_nodes2 if opt_elem.text]
self.assertEquals(select_node.get('name'), self.aside_tag) self.assertEquals(option_values2, ['Not selected', '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[\"\']")
...@@ -146,11 +198,11 @@ class StructuredTagsAsideTestCase(ModuleStoreTestCase): ...@@ -146,11 +198,11 @@ class StructuredTagsAsideTestCase(ModuleStoreTestCase):
response = client.post(path=handler_url, data={'tag': 'undefined_tag:undefined'}) response = client.post(path=handler_url, data={'tag': 'undefined_tag:undefined'})
self.assertEqual(response.status_code, 400) self.assertEqual(response.status_code, 400)
val = '%s:undefined' % self.aside_tag val = '%s:undefined' % self.aside_tag_dif
response = client.post(path=handler_url, data={'tag': val}) response = client.post(path=handler_url, data={'tag': val})
self.assertEqual(response.status_code, 400) self.assertEqual(response.status_code, 400)
val = '%s:%s' % (self.aside_tag, self.aside_tag_value) val = '%s:%s' % (self.aside_tag_dif, self.aside_tag_dif_value)
response = client.post(path=handler_url, data={'tag': val}) response = client.post(path=handler_url, data={'tag': val})
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
...@@ -163,4 +215,4 @@ class StructuredTagsAsideTestCase(ModuleStoreTestCase): ...@@ -163,4 +215,4 @@ class StructuredTagsAsideTestCase(ModuleStoreTestCase):
break break
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], self.aside_tag_value) self.assertEqual(tag_aside.saved_tags[self.aside_tag_dif], self.aside_tag_dif_value)
<div class="xblock-render"> <div class="xblock-render" class="studio-xblock-wrapper">
% for tag in tags: % for tag in tags:
<label for="problem_tags_${tag['key']}">${tag['title']}</label>: <label for="tags_${tag['key']}_${block_location}">${tag['title']}</label>:
<select id="problem_tags_${tag['key']}" name="${tag['key']}"> <select id="tags_${tag['key']}_${block_location}" name="${tag['key']}">
<option value="" ${'' if tag['current_value'] else 'selected=""'}>Not selected</option> <option value="" ${'' if tag['current_value'] else 'selected=""'}>Not selected</option>
% for k,v in tag['values'].iteritems(): % for v in tag['values']:
<% <%
selected = '' selected = ''
if k == tag['current_value']: if v == tag['current_value']:
selected = 'selected' selected = 'selected'
%> %>
<option value="${k}" ${selected}>${v}</option> <option value="${v}" ${selected}>${v}</option>
% endfor % endfor
% endfor
</select> </select>
</div> % endfor
\ No newline at end of file </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