Skip to content
Snippets Groups Projects
Commit 18360657 authored by Dmitry Viskov's avatar Dmitry Viskov
Browse files

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

parent 47e4804b
No related merge requests found
......@@ -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>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment