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 = (
# Management commands used for configuration automation
'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
"""
......@@ -7,64 +8,15 @@ from xblock.fragment import Fragment
from xblock.fields import Scope, Dict
from xmodule.x_module import STUDENT_VIEW
from xmodule.capa_module import CapaModule
from abc import ABCMeta, abstractproperty
from edxmako.shortcuts import render_to_string
from django.conf import settings
from webob import Response
from collections import OrderedDict
from .models import TagCategories
_ = 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):
"""
Aside that allows tagging blocks
......@@ -72,7 +24,12 @@ class StructuredTagsAside(XBlockAside):
saved_tags = Dict(help=_("Dictionary with the available tags"),
scope=Scope.content,
default={},)
available_tags = [DifficultyTag()]
def get_available_tags(self):
"""
Return available tags
"""
return TagCategories.objects.all()
def _get_studio_resource_url(self, relative_url):
"""
......@@ -88,14 +45,21 @@ class StructuredTagsAside(XBlockAside):
"""
if isinstance(block, CapaModule):
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({
'key': tag.key,
'title': tag.name,
'values': tag.allowed_values,
'current_value': self.saved_tags.get(tag.key, None),
'key': tag.name,
'title': tag.title,
'values': values,
'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.initialize_js('StructuredTagsInit')
return fragment
......@@ -113,14 +77,14 @@ class StructuredTagsAside(XBlockAside):
tag = request.params['tag'].split(':')
for av_tag in self.available_tags:
if av_tag.key == tag[0]:
if tag[1] in av_tag.allowed_values:
self.saved_tags[tag[0]] = tag[1]
found = True
elif tag[1] == '':
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)
......
......@@ -7,7 +7,11 @@ from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
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.models import TagCategories, TagAvailableValues
from contentstore.views.preview import get_preview_fragment
from contentstore.utils import reverse_usage_url
from contentstore.tests.utils import AjaxEnabledTestClient
......@@ -30,8 +34,9 @@ class StructuredTagsAsideTestCase(ModuleStoreTestCase):
"""
self.user_password = super(StructuredTagsAsideTestCase, self).setUp()
self.aside_name = 'tagging_aside'
self.aside_tag = 'difficulty_tag'
self.aside_tag_value = 'hard'
self.aside_tag_dif = 'difficulty'
self.aside_tag_dif_value = 'Hard'
self.aside_tag_lo = 'learning_outcome'
course = CourseFactory.create(default_store=ModuleStoreEnum.Type.split)
self.course = ItemFactory.create(
......@@ -75,16 +80,47 @@ class StructuredTagsAsideTestCase(ModuleStoreTestCase):
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.enabled = True
config.save()
def tearDown(self):
TagAvailableValues.objects.all().delete()
TagCategories.objects.all().delete()
super(StructuredTagsAsideTestCase, self).tearDown()
def test_aside_contains_tags(self):
"""
Checks that available_tags list is not empty
"""
self.assertGreater(len(StructuredTagsAside.available_tags), 0,
"StructuredTagsAside should contains at least one available tag")
sids = ScopeIds(user_id="bob",
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):
"""
......@@ -115,10 +151,26 @@ class StructuredTagsAsideTestCase(ModuleStoreTestCase):
self.assertIn('xblock_asides-v1', div_node.get('class'))
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]
self.assertEquals(select_node.get('name'), self.aside_tag)
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'])
# Now ensure the acid_aside is not in the result
self.assertNotRegexpMatches(problem_html, r"data-block-type=[\"\']acid_aside[\"\']")
......@@ -146,11 +198,11 @@ class StructuredTagsAsideTestCase(ModuleStoreTestCase):
response = client.post(path=handler_url, data={'tag': 'undefined_tag:undefined'})
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})
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})
self.assertEqual(response.status_code, 200)
......@@ -163,4 +215,4 @@ class StructuredTagsAsideTestCase(ModuleStoreTestCase):
break
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:
<label for="problem_tags_${tag['key']}">${tag['title']}</label>:
<select id="problem_tags_${tag['key']}" name="${tag['key']}">
<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 k,v in tag['values'].iteritems():
% for v in tag['values']:
<%
selected = ''
if k == tag['current_value']:
if v == tag['current_value']:
selected = 'selected'
%>
<option value="${k}" ${selected}>${v}</option>
<option value="${v}" ${selected}>${v}</option>
% endfor
% endfor
</select>
</div>
\ No newline at end of file
% endfor
</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