From 18f5438153ca2c2936226283a9bb5ab14e05fd97 Mon Sep 17 00:00:00 2001 From: Martin Heger Date: Wed, 7 Dec 2016 12:57:46 +0100 Subject: [PATCH 01/51] add options xml-export --- apps/options/renderers.py | 78 +++++++++++++++++++++ apps/options/serializers.py | 34 +++++++++ apps/options/templates/options/options.html | 8 +++ apps/options/urls.py | 1 + apps/options/views.py | 15 +++- 5 files changed, 134 insertions(+), 2 deletions(-) create mode 100644 apps/options/renderers.py diff --git a/apps/options/renderers.py b/apps/options/renderers.py new file mode 100644 index 000000000..37fddacef --- /dev/null +++ b/apps/options/renderers.py @@ -0,0 +1,78 @@ +from __future__ import unicode_literals + +from django.utils.xmlutils import SimplerXMLGenerator +from django.utils.six.moves import StringIO +from django.utils.encoding import smart_text +from rest_framework.renderers import BaseRenderer + + +class XMLRenderer(BaseRenderer): + """ + Renderer which serializes to XML. + """ + + media_type = 'application/xml' + format = 'xml' + + def render(self, data): + """ + Renders 'data' into serialized XML. + """ + if data is None: + return '' + + stream = StringIO() + + xml = SimplerXMLGenerator(stream, "utf-8") + xml.startDocument() + xml.startElement('OptionSets', {}) + + for optionset in data: + self._optionset(xml, optionset) + + xml.endElement('OptionSets') + xml.endDocument() + return stream.getvalue() + + def _option(self, xml, option): + xml.startElement('Option', {}) + self._text_element(xml, 'title', {}, option["title"]) + self._text_element(xml, 'order', {}, option["order"]) + self._text_element(xml, 'text_en', {}, option["text_en"]) + self._text_element(xml, 'text_de', {}, option["text_de"]) + self._text_element(xml, 'additional_input', {}, option["additional_input"]) + xml.endElement('Option') + + def _optionset(self, xml, optionset): + xml.startElement('OptionSet', {}) + self._text_element(xml, 'title', {}, optionset["title"]) + self._text_element(xml, 'order', {}, optionset["order"]) + + if 'options' in optionset and optionset['options']: + xml.startElement('Options', {}) + + for option in optionset['options']: + self._option(xml, option) + + xml.endElement('Options') + + if 'conditions' in optionset and optionset['conditions']: + xml.startElement('Conditions', {}) + + for conditions in optionset['conditions']: + self._conditions(xml, conditions) + + xml.endElement('Conditions') + + xml.endElement('OptionSet') + + def _conditions(self, xml, conditions): + xml.startElement('Condition', {}) + self._text_element(xml, 'title', {}, conditions["title"]) + xml.endElement('Condition') + + def _text_element(self, xml, tag, option, text): + xml.startElement(tag, option) + if text is not None: + xml.characters(smart_text(text)) + xml.endElement(tag) diff --git a/apps/options/serializers.py b/apps/options/serializers.py index 0047c7327..7d0fd8b60 100644 --- a/apps/options/serializers.py +++ b/apps/options/serializers.py @@ -62,3 +62,37 @@ class Meta: 'id', 'title' ) + +class ExportOptionSerializer(serializers.ModelSerializer): + + class Meta: + model = Option + fields = ( + 'title', + 'order', + 'text_en', + 'text_de', + 'additional_input' + ) + +class ExportConditionSerializer(serializers.ModelSerializer): + + class Meta: + model = Condition + fields = ( + 'title', + ) + +class ExportSerializer(serializers.ModelSerializer): + + options = ExportOptionSerializer(many=True) + conditions = ExportConditionSerializer(many=True) + + class Meta: + model = OptionSet + fields = ( + 'title', + 'order', + 'options', + 'conditions' + ) diff --git a/apps/options/templates/options/options.html b/apps/options/templates/options/options.html index bffb5c73a..c9ca24fc5 100644 --- a/apps/options/templates/options/options.html +++ b/apps/options/templates/options/options.html @@ -56,6 +56,14 @@

{% trans 'Export' %}

{% endfor %} + + {% endblock %} {% block page %} diff --git a/apps/options/urls.py b/apps/options/urls.py index 002af4149..06ff83b66 100644 --- a/apps/options/urls.py +++ b/apps/options/urls.py @@ -4,5 +4,6 @@ urlpatterns = [ url(r'^$', options, name='options'), + url(r'^export/xml/$', options_export_xml, name='options_export_xml'), url(r'^export/(?P[a-z]+)/$', options_export, name='options_export'), ] diff --git a/apps/options/views.py b/apps/options/views.py index 66d459442..f1728c781 100644 --- a/apps/options/views.py +++ b/apps/options/views.py @@ -1,19 +1,23 @@ from django.conf import settings -from django.contrib.admin.views.decorators import staff_member_required +from django.http import HttpResponse from django.shortcuts import render +from django.contrib.admin.views.decorators import staff_member_required from django.utils.translation import ugettext_lazy as _ -from rest_framework import viewsets +from rest_framework import viewsets, mixins, filters from rest_framework.permissions import DjangoModelPermissions, IsAuthenticated from rest_framework.decorators import list_route from rest_framework.response import Response +from apps.core.serializers import ChoicesSerializer from apps.core.utils import render_to_format +from apps.options.models import OptionSet from apps.conditions.models import Condition from .models import * from .serializers import * +from .renderers import * @staff_member_required @@ -30,6 +34,13 @@ def options_export(request, format): }) +@staff_member_required +def options_export_xml(request): + queryset = OptionSet.objects.all() + serializer = ExportSerializer(queryset, many=True) + return HttpResponse(XMLRenderer().render(serializer.data), content_type="application/xml") + + class OptionSetViewSet(viewsets.ModelViewSet): permission_classes = (DjangoModelPermissions, IsAuthenticated) From 537f924b3535f3b3ca811ff511ceaa3964737a46 Mon Sep 17 00:00:00 2001 From: Martin Heger Date: Wed, 7 Dec 2016 13:15:27 +0100 Subject: [PATCH 02/51] update renderers.py --- apps/options/renderers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/options/renderers.py b/apps/options/renderers.py index 37fddacef..974dd17bd 100644 --- a/apps/options/renderers.py +++ b/apps/options/renderers.py @@ -59,16 +59,16 @@ def _optionset(self, xml, optionset): if 'conditions' in optionset and optionset['conditions']: xml.startElement('Conditions', {}) - for conditions in optionset['conditions']: - self._conditions(xml, conditions) + for condition in optionset['conditions']: + self._condition(xml, condition) xml.endElement('Conditions') xml.endElement('OptionSet') - def _conditions(self, xml, conditions): + def _condition(self, xml, condition): xml.startElement('Condition', {}) - self._text_element(xml, 'title', {}, conditions["title"]) + self._text_element(xml, 'title', {}, condition["title"]) xml.endElement('Condition') def _text_element(self, xml, tag, option, text): From 00daed0506199f770db6053019720c14d7a77f4b Mon Sep 17 00:00:00 2001 From: Martin Heger Date: Wed, 7 Dec 2016 15:45:48 +0100 Subject: [PATCH 03/51] update serializers.py --- apps/options/serializers.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/options/serializers.py b/apps/options/serializers.py index 7d0fd8b60..32e49be13 100644 --- a/apps/options/serializers.py +++ b/apps/options/serializers.py @@ -63,6 +63,7 @@ class Meta: 'title' ) + class ExportOptionSerializer(serializers.ModelSerializer): class Meta: @@ -75,6 +76,7 @@ class Meta: 'additional_input' ) + class ExportConditionSerializer(serializers.ModelSerializer): class Meta: @@ -83,6 +85,7 @@ class Meta: 'title', ) + class ExportSerializer(serializers.ModelSerializer): options = ExportOptionSerializer(many=True) From e12737d02a12d19cbf48ae9d55f93f302d8781c2 Mon Sep 17 00:00:00 2001 From: Martin Heger Date: Thu, 8 Dec 2016 13:52:43 +0100 Subject: [PATCH 04/51] add conditions xml-export, update renderers --- apps/conditions/renderers.py | 46 +++++++++++++++++++ apps/conditions/serializers.py | 18 +++++++- .../templates/conditions/conditions.html | 8 ++++ apps/conditions/urls.py | 1 + apps/conditions/views.py | 14 +++++- apps/domain/renderers.py | 7 +-- apps/options/renderers.py | 7 +-- 7 files changed, 86 insertions(+), 15 deletions(-) create mode 100644 apps/conditions/renderers.py diff --git a/apps/conditions/renderers.py b/apps/conditions/renderers.py new file mode 100644 index 000000000..deb91e9fc --- /dev/null +++ b/apps/conditions/renderers.py @@ -0,0 +1,46 @@ +from __future__ import unicode_literals + +from django.utils.xmlutils import SimplerXMLGenerator +from django.utils.six.moves import StringIO +from django.utils.encoding import smart_text +from rest_framework.renderers import BaseRenderer + + +class XMLRenderer(BaseRenderer): + + media_type = 'application/xml' + format = 'xml' + + def render(self, data): + + if data is None: + return '' + + stream = StringIO() + + xml = SimplerXMLGenerator(stream, "utf-8") + xml.startDocument() + xml.startElement('Conditions', {}) + + for condition in data: + self._condition(xml, condition) + + xml.endElement('Conditions') + xml.endDocument() + return stream.getvalue() + + def _condition(self, xml, condition): + xml.startElement('Condition', {}) + self._text_element(xml, 'title', {}, condition["title"]) + self._text_element(xml, 'description', {}, condition["description"]) + self._text_element(xml, 'source', {}, condition["source"]) + self._text_element(xml, 'relation', {}, condition["relation"]) + self._text_element(xml, 'target_text', {}, condition["target_text"]) + self._text_element(xml, 'target_option', {}, condition["target_option"]) + xml.endElement('Condition') + + def _text_element(self, xml, tag, condition, text): + xml.startElement(tag, condition) + if text is not None: + xml.characters(smart_text(text)) + xml.endElement(tag) diff --git a/apps/conditions/serializers.py b/apps/conditions/serializers.py index 40277c43e..4d04dd26b 100644 --- a/apps/conditions/serializers.py +++ b/apps/conditions/serializers.py @@ -72,7 +72,7 @@ class Meta: class OptionSetSerializer(serializers.ModelSerializer): - options = OptionSetOptionSerializer(many=True) + conditions = OptionSetOptionSerializer(many=True) class Meta: model = OptionSet @@ -81,3 +81,19 @@ class Meta: 'order', 'options' ) + + +class ExportSerializer(serializers.ModelSerializer): + + target_option = serializers.CharField(source='target_option.title') + + class Meta: + model = Condition + fields = ( + 'title', + 'description', + 'source', + 'relation', + 'target_text', + 'target_option' + ) diff --git a/apps/conditions/templates/conditions/conditions.html b/apps/conditions/templates/conditions/conditions.html index 228d2a05f..a9ad8c490 100644 --- a/apps/conditions/templates/conditions/conditions.html +++ b/apps/conditions/templates/conditions/conditions.html @@ -51,6 +51,14 @@

{% trans 'Export' %}

{% endfor %} + + {% endblock %} {% block page %} diff --git a/apps/conditions/urls.py b/apps/conditions/urls.py index 7db8413d0..483dcaeb5 100644 --- a/apps/conditions/urls.py +++ b/apps/conditions/urls.py @@ -4,5 +4,6 @@ urlpatterns = [ url(r'^$', conditions, name='conditions'), + url(r'^export/xml/$', conditions_export_xml, name='conditions_export_xml'), url(r'^export/(?P[a-z]+)/$', conditions_export, name='conditions_export'), ] diff --git a/apps/conditions/views.py b/apps/conditions/views.py index a0437f15a..5efa2614e 100644 --- a/apps/conditions/views.py +++ b/apps/conditions/views.py @@ -1,9 +1,10 @@ from django.conf import settings -from django.contrib.admin.views.decorators import staff_member_required +from django.http import HttpResponse from django.shortcuts import render +from django.contrib.admin.views.decorators import staff_member_required from django.utils.translation import ugettext_lazy as _ -from rest_framework import viewsets, mixins +from rest_framework import viewsets, mixins, filters from rest_framework.permissions import DjangoModelPermissions, IsAuthenticated from rest_framework.decorators import list_route, detail_route from rest_framework.response import Response @@ -14,10 +15,12 @@ from apps.core.serializers import ChoicesSerializer from apps.domain.models import Attribute from apps.options.models import OptionSet +from apps.conditions.models import Condition from apps.projects.models import Snapshot from .models import * from .serializers import * +from .renderers import * @staff_member_required @@ -34,6 +37,13 @@ def conditions_export(request, format): }) +@staff_member_required +def conditions_export_xml(request): + queryset = Condition.objects.all() + serializer = ExportSerializer(queryset, many=True) + return HttpResponse(XMLRenderer().render(serializer.data), content_type="application/xml") + + class ConditionViewSet(viewsets.ModelViewSet): permission_classes = (DjangoModelPermissions, ) diff --git a/apps/domain/renderers.py b/apps/domain/renderers.py index 38cb6bac7..34f608e06 100644 --- a/apps/domain/renderers.py +++ b/apps/domain/renderers.py @@ -7,17 +7,12 @@ class XMLRenderer(BaseRenderer): - """ - Renderer which serializes to XML. - """ media_type = 'application/xml' format = 'xml' def render(self, data): - """ - Renders 'data' into serialized XML. - """ + if data is None: return '' diff --git a/apps/options/renderers.py b/apps/options/renderers.py index 974dd17bd..a1fcc4026 100644 --- a/apps/options/renderers.py +++ b/apps/options/renderers.py @@ -7,17 +7,12 @@ class XMLRenderer(BaseRenderer): - """ - Renderer which serializes to XML. - """ media_type = 'application/xml' format = 'xml' def render(self, data): - """ - Renders 'data' into serialized XML. - """ + if data is None: return '' From 887d16c1e6093906c0c263d5184f947be9885ad0 Mon Sep 17 00:00:00 2001 From: Martin Heger Date: Fri, 16 Dec 2016 16:07:16 +0100 Subject: [PATCH 05/51] add questions xml-export --- apps/questions/renderers.py | 106 ++++++++++++++++++ apps/questions/serializers.py | 99 ++++++++++++++++ .../templates/questions/catalogs_sidebar.html | 8 ++ apps/questions/urls.py | 3 +- apps/questions/views.py | 12 +- 5 files changed, 226 insertions(+), 2 deletions(-) create mode 100644 apps/questions/renderers.py diff --git a/apps/questions/renderers.py b/apps/questions/renderers.py new file mode 100644 index 000000000..5c65df04e --- /dev/null +++ b/apps/questions/renderers.py @@ -0,0 +1,106 @@ +from __future__ import unicode_literals + +from django.utils.xmlutils import SimplerXMLGenerator +from django.utils.six.moves import StringIO +from django.utils.encoding import smart_text +from rest_framework.renderers import BaseRenderer + + +class XMLRenderer(BaseRenderer): + + media_type = 'application/xml' + format = 'xml' + + def render(self, data): + + if data is None: + return '' + + stream = StringIO() + + xml = SimplerXMLGenerator(stream, "utf-8") + xml.startDocument() + xml.startElement('Catalogs', {}) + + for catalog in data: + self._catalog(xml, catalog) + + xml.endElement('Catalogs') + xml.endDocument() + return stream.getvalue() + + def _catalog(self, xml, catalog): + xml.startElement('Catalog', {}) + self._text_element(xml, 'title', {}, catalog["title"]) + self._text_element(xml, 'order', {}, catalog["order"]) + + if 'sections' in catalog and catalog['sections']: + xml.startElement('Sections', {}) + + for section in catalog['sections']: + self._section(xml, section) + + xml.endElement('Sections') + + xml.endElement('Catalog') + + def _section(self, xml, section): + xml.startElement('Section', {}) + self._text_element(xml, 'order', {}, section["order"]) + self._text_element(xml, 'title_en', {}, section["title_en"]) + self._text_element(xml, 'title_de', {}, section["title_de"]) + + if 'subsections' in section and section['subsections']: + xml.startElement('Subsections', {}) + + for subsection in section['subsections']: + self._subsection(xml, subsection) + + xml.endElement('Subsections') + + xml.endElement('Section') + + def _subsection(self, xml, subsection): + xml.startElement('Subsection', {}) + self._text_element(xml, 'order', {}, subsection["order"]) + self._text_element(xml, 'title_en', {}, subsection["title_en"]) + self._text_element(xml, 'title_de', {}, subsection["title_de"]) + + if 'entities' in subsection and subsection['entities']: + xml.startElement('QuestionEntities', {}) + + for questionentity in subsection['entities']: + if 'is_set' in questionentity and questionentity['is_set']: + self._questionset(xml, questionentity) + else: + self._question(xml, questionentity) + + xml.endElement('QuestionEntities') + + xml.endElement('Subsection') + + def _question(self, xml, question): + xml.startElement('Question', {}) + self._text_element(xml, 'text_en', {}, question["text_en"]) + self._text_element(xml, 'text_de', {}, question["text_de"]) + self._text_element(xml, 'attribute_entity', {}, question["attribute_entity"]['label']) + xml.endElement('Question') + + def _questionset(self, xml, questionset): + xml.startElement('QuestionSet', {}) + self._text_element(xml, 'order', {}, questionset["order"]) + self._text_element(xml, 'help_en', {}, questionset["help_en"]) + self._text_element(xml, 'help_de', {}, questionset["help_de"]) + xml.startElement('Questions', {}) + + for question in questionset['questions']: + self._question(xml, question) + + xml.endElement('Questions') + xml.endElement('QuestionSet') + + def _text_element(self, xml, tag, questionentity, text): + xml.startElement(tag, questionentity) + if text is not None: + xml.characters(smart_text(text)) + xml.endElement(tag) diff --git a/apps/questions/serializers.py b/apps/questions/serializers.py index 97337ceb5..c08d02307 100644 --- a/apps/questions/serializers.py +++ b/apps/questions/serializers.py @@ -269,3 +269,102 @@ def get_urls(self, obj): for format in settings.EXPORT_FORMATS: urls[format] = reverse('questions_catalog_export', args=[obj.pk, format]) return urls + + +class ExportAttributeEntitySerializer(serializers.ModelSerializer): + + class Meta: + model = AttributeEntity + fields = ( + 'id', + 'label' + ) + + +class ExportQuestionSerializer(serializers.ModelSerializer): + + attribute_entity = ExportAttributeEntitySerializer() + + class Meta: + model = Question + fields = ( + 'parent', + 'attribute_entity', + 'order', + 'help_en', + 'help_de', + 'text_en', + 'text_de', + 'widget_type' + ) + + +class ExportQuestionEntitySerializer(serializers.ModelSerializer): + + questions = ExportQuestionSerializer(many=True, read_only=True) + text_en = serializers.CharField(source='question.text_en') + text_de = serializers.CharField(source='question.text_de') + + attribute_entity = ExportAttributeEntitySerializer(read_only=True) + + class Meta: + model = QuestionEntity + fields = ( + 'text_en', + 'text_de', + 'is_set', + 'attribute_entity', + 'order', + 'help_en', + 'help_de', + 'questions' + ) + + +class ExportSubsectionSerializer(serializers.ModelSerializer): + + entities = serializers.SerializerMethodField() + + class Meta: + model = Subsection + fields = ( + 'order', + 'title_en', + 'title_de', + 'entities' + ) + + def get_entities(self, obj): + entities = QuestionEntity.objects.filter(subsection=obj, question__parent=None) + serializer = ExportQuestionEntitySerializer(instance=entities, many=True) + return serializer.data + + +class ExportSectionSerializer(serializers.ModelSerializer): + + subsections = ExportSubsectionSerializer(many=True) + + class Meta: + model = Catalog + fields = ( + 'order', + 'title', + 'title_en', + 'title_de', + 'subsections' + ) + + +class ExportSerializer(serializers.ModelSerializer): + + sections = ExportSectionSerializer(many=True) + + class Meta: + model = Catalog + fields = ( + 'order', + 'title', + 'title_en', + 'title_de', + 'sections' + ) diff --git a/apps/questions/templates/questions/catalogs_sidebar.html b/apps/questions/templates/questions/catalogs_sidebar.html index 2daa11969..dcf02ecf1 100644 --- a/apps/questions/templates/questions/catalogs_sidebar.html +++ b/apps/questions/templates/questions/catalogs_sidebar.html @@ -71,3 +71,11 @@

{% trans 'Export' %}

{% endfor %} + + diff --git a/apps/questions/urls.py b/apps/questions/urls.py index 827fe3587..ec2dc385a 100644 --- a/apps/questions/urls.py +++ b/apps/questions/urls.py @@ -3,7 +3,8 @@ from .views import * urlpatterns = [ + url(r'^catalogs/', catalogs, name='catalogs'), + url(r'^export/xml/$', questions_catalog_export_xml, name='questions_catalog_export_xml'), url(r'^catalogs/(?P[0-9]+)/export/(?P[a-z]+)/$', catalog_export, name='questions_catalog_export'), - url(r'^catalogs/', catalogs, name='catalogs'), ] diff --git a/apps/questions/views.py b/apps/questions/views.py index 8753e5156..4ce102a79 100644 --- a/apps/questions/views.py +++ b/apps/questions/views.py @@ -1,8 +1,10 @@ from django.conf import settings +from django.http import HttpResponse +from django.shortcuts import render from django.contrib.admin.views.decorators import staff_member_required from django.shortcuts import render, get_object_or_404 -from rest_framework import viewsets, mixins +from rest_framework import viewsets, mixins, filters from rest_framework.permissions import DjangoModelPermissions, IsAuthenticated from rest_framework.decorators import list_route, detail_route from rest_framework.response import Response @@ -12,6 +14,7 @@ from .models import * from .serializers import * +from .renderers import * @staff_member_required @@ -30,6 +33,13 @@ def catalog_export(request, catalog_id, format): }) +@staff_member_required +def questions_catalog_export_xml(request): + queryset = Catalog.objects.all() + serializer = ExportSerializer(queryset, many=True) + return HttpResponse(XMLRenderer().render(serializer.data), content_type="application/xml") + + class CatalogViewSet(viewsets.ModelViewSet): permission_classes = (DjangoModelPermissions, ) From 4edefb680ab781dd6378e362853e393a49d86bd1 Mon Sep 17 00:00:00 2001 From: Jochen Klar Date: Wed, 25 Jan 2017 14:11:53 +0100 Subject: [PATCH 06/51] refactor options app --- apps/core/management/commands/import.py | 16 +++ apps/core/utils.py | 25 ++++ apps/options/admin.py | 15 ++- apps/options/migrations/0005_refactoring.py | 29 +++++ apps/options/migrations/0006_refactoring.py | 95 ++++++++++++++ apps/options/models.py | 118 ++++++++++++++---- apps/options/renderers.py | 72 +++++------ apps/options/serializers.py | 24 ++-- apps/options/templates/options/options.html | 4 +- .../options/options_modal_form_options.html | 23 +++- .../options_modal_form_optionsets.html | 22 +++- apps/options/urls.py | 2 +- apps/options/utils.py | 43 +++++++ apps/options/views.py | 19 +-- fixtures/options.json | 59 +-------- 15 files changed, 425 insertions(+), 141 deletions(-) create mode 100644 apps/core/management/commands/import.py create mode 100644 apps/options/migrations/0005_refactoring.py create mode 100644 apps/options/migrations/0006_refactoring.py create mode 100644 apps/options/utils.py diff --git a/apps/core/management/commands/import.py b/apps/core/management/commands/import.py new file mode 100644 index 000000000..7784d573e --- /dev/null +++ b/apps/core/management/commands/import.py @@ -0,0 +1,16 @@ +from django.core.management.base import BaseCommand + +from apps.core.utils import import_xml + + +class Command(BaseCommand): + + def add_arguments(self, parser): + parser.add_argument('xmlfile', action='store', default=False, help='RDMO XML export file') + + def handle(self, *args, **options): + print options['xmlfile'] + + with open(options['xmlfile']) as f: + xml_string = f.read() + import_xml(xml_string) diff --git a/apps/core/utils.py b/apps/core/utils.py index 372bc816d..692b4fcab 100644 --- a/apps/core/utils.py +++ b/apps/core/utils.py @@ -2,6 +2,7 @@ import csv from tempfile import mkstemp +from lxml import objectify import pypandoc from django.conf import settings @@ -10,6 +11,11 @@ from django.utils.six.moves.urllib.parse import urlparse from django.utils.translation import ugettext_lazy as _ +#from apps.conditions.utils import import_xml as import_conditions +from apps.options.utils import import_xml as import_options +#from apps.domain.utils import import_xml as import_domain +#from apps.questions.utils import import_xml as import_questions + def get_script_alias(request): return request.path[:-len(request.path_info)] @@ -101,3 +107,22 @@ def render_to_csv(request, title, rows): writer.writerow(tuple(row)) return response + + +def import_xml(xml_string): + xml_root = objectify.fromstring(xml_string) + + if xml_root.tag == 'conditions': + import_conditions(xml_root) + + elif xml_root.tag == 'options': + import_options(xml_root) + + elif xml_root.tag == 'domain': + import_domain(xml_root) + + elif xml_root.tag == 'catalogs': + import_questions(xml_root) + + else: + raise Exception('This is not a proper RDMO XML Export.') diff --git a/apps/options/admin.py b/apps/options/admin.py index 9bd105ebc..5fb7f7227 100644 --- a/apps/options/admin.py +++ b/apps/options/admin.py @@ -1,6 +1,15 @@ from django.contrib import admin -from .models import * +from .models import OptionSet, Option -admin.site.register(OptionSet) -admin.site.register(Option) + +class OptionSetAdmin(admin.ModelAdmin): + readonly_fields = ('uri', ) + + +class OptionAdmin(admin.ModelAdmin): + readonly_fields = ('uri', ) + + +admin.site.register(OptionSet, OptionSetAdmin) +admin.site.register(Option, OptionAdmin) diff --git a/apps/options/migrations/0005_refactoring.py b/apps/options/migrations/0005_refactoring.py new file mode 100644 index 000000000..a488aea7f --- /dev/null +++ b/apps/options/migrations/0005_refactoring.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2017-01-25 11:06 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('options', '0004_conditions'), + ] + + operations = [ + migrations.AlterModelOptions( + name='optionset', + options={'ordering': ('key',), 'verbose_name': 'OptionSet', 'verbose_name_plural': 'OptionSets'}, + ), + migrations.RenameField( + model_name='option', + old_name='title', + new_name='key', + ), + migrations.RenameField( + model_name='optionset', + old_name='title', + new_name='key', + ), + ] diff --git a/apps/options/migrations/0006_refactoring.py b/apps/options/migrations/0006_refactoring.py new file mode 100644 index 000000000..e326d5aac --- /dev/null +++ b/apps/options/migrations/0006_refactoring.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2017-01-25 11:34 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('options', '0005_refactoring'), + ] + + operations = [ + migrations.AlterModelOptions( + name='optionset', + options={'ordering': ('uri',), 'verbose_name': 'OptionSet', 'verbose_name_plural': 'OptionSets'}, + ), + migrations.AddField( + model_name='option', + name='comment', + field=models.TextField(blank=True, help_text='Additional information about this option.', null=True, verbose_name='Comment'), + ), + migrations.AddField( + model_name='option', + name='uri', + field=models.URLField(blank=True, help_text='The Uniform Resource Identifier of this option (auto-generated).', max_length=640, null=True, verbose_name='URI'), + ), + migrations.AddField( + model_name='option', + name='uri_prefix', + field=models.URLField(blank=True, help_text='The prefix for the URI of this option.', max_length=256, null=True, verbose_name='URI Prefix'), + ), + migrations.AddField( + model_name='optionset', + name='comment', + field=models.TextField(blank=True, help_text='Additional information about this option set.', null=True, verbose_name='Comment'), + ), + migrations.AddField( + model_name='optionset', + name='uri', + field=models.URLField(blank=True, help_text='The Uniform Resource Identifier of this option set (auto-generated).', max_length=640, null=True, verbose_name='URI'), + ), + migrations.AddField( + model_name='optionset', + name='uri_prefix', + field=models.URLField(blank=True, help_text='The prefix for the URI of this option set.', max_length=256, null=True, verbose_name='URI Prefix'), + ), + migrations.AlterField( + model_name='option', + name='additional_input', + field=models.BooleanField(default=False, help_text='Designates whether an additional input is possible for this option.', verbose_name='Additional input'), + ), + migrations.AlterField( + model_name='option', + name='key', + field=models.SlugField(blank=True, help_text='The internal identifier of this option. The URI will be generated from this key.', max_length=128, null=True, verbose_name='Key'), + ), + migrations.AlterField( + model_name='option', + name='optionset', + field=models.ForeignKey(blank=True, help_text='The option set this option belongs to.', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='options', to='options.OptionSet', verbose_name='Option set'), + ), + migrations.AlterField( + model_name='option', + name='order', + field=models.IntegerField(default=0, help_text='The position of this option in lists.', verbose_name='Order'), + ), + migrations.AlterField( + model_name='option', + name='text_de', + field=models.CharField(help_text='The German text displayed for this option.', max_length=256, verbose_name='Text (de)'), + ), + migrations.AlterField( + model_name='option', + name='text_en', + field=models.CharField(help_text='The English text displayed for this option.', max_length=256, verbose_name='Text (en)'), + ), + migrations.AlterField( + model_name='optionset', + name='conditions', + field=models.ManyToManyField(blank=True, help_text='The list of conditions evaluated for this option set.', to='conditions.Condition', verbose_name='Conditions'), + ), + migrations.AlterField( + model_name='optionset', + name='key', + field=models.SlugField(blank=True, help_text='The internal identifier of this option set. The URI will be generated from this key.', max_length=128, null=True, verbose_name='Key'), + ), + migrations.AlterField( + model_name='optionset', + name='order', + field=models.IntegerField(default=0, help_text='The position of this option set in lists.', verbose_name='Order'), + ), + ] diff --git a/apps/options/models.py b/apps/options/models.py index dcfa10491..95be823ac 100644 --- a/apps/options/models.py +++ b/apps/options/models.py @@ -2,6 +2,8 @@ from django.core.validators import RegexValidator from django.db import models +from django.db.models.signals import pre_save +from django.dispatch import receiver from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ @@ -12,47 +14,121 @@ @python_2_unicode_compatible class OptionSet(models.Model): - title = models.CharField(max_length=256, validators=[ - RegexValidator('^[a-zA-z0-9_]*$', _('Only letters, numbers, or underscores are allowed.')) - ]) - - order = models.IntegerField(default=0) - - conditions = models.ManyToManyField(Condition, blank=True) + uri_prefix = models.URLField( + max_length=256, blank=True, null=True, + verbose_name=_('URI Prefix'), + help_text=_('The prefix for the URI of this option set.') + ) + key = models.SlugField( + max_length=128, blank=True, null=True, + verbose_name=_('Key'), + help_text=_('The internal identifier of this option set. The URI will be generated from this key.') + ) + comment = models.TextField( + blank=True, null=True, + verbose_name=_('Comment'), + help_text=_('Additional information about this option set.') + ) + order = models.IntegerField( + default=0, + verbose_name=_('Order'), + help_text=_('The position of this option set in lists.') + ) + conditions = models.ManyToManyField( + Condition, blank=True, + verbose_name=_('Conditions'), + help_text=_('The list of conditions evaluated for this option set.') + ) + uri = models.URLField( + max_length=640, blank=True, null=True, + verbose_name=_('URI'), + help_text=_('The Uniform Resource Identifier of this option set (auto-generated).') + ) class Meta: - ordering = ('title', ) + ordering = ('uri', ) verbose_name = _('OptionSet') verbose_name_plural = _('OptionSets') def __str__(self): - return self.title - + return self.uri -@python_2_unicode_compatible -class Option(models.Model, TranslationMixin): + def save(self, *args, **kwargs): + self.uri = self.build_uri() + super(OptionSet, self).save(*args, **kwargs) - optionset = models.ForeignKey('OptionSet', null=True, blank=True, related_name='options') + for option in self.options.all(): + option.save() - title = models.CharField(max_length=256, validators=[ - RegexValidator('^[a-zA-z0-9_]*$', _('Only letters, numbers, or underscores are allowed.')) - ]) + def build_uri(self): + return self.uri_prefix.rstrip('/') + '/options/' + self.key - order = models.IntegerField(default=0) - text_en = models.CharField(max_length=256) - text_de = models.CharField(max_length=256) +@python_2_unicode_compatible +class Option(models.Model, TranslationMixin): - additional_input = models.BooleanField(default=False) + optionset = models.ForeignKey( + 'OptionSet', null=True, blank=True, related_name='options', + verbose_name=_('Option set'), + help_text=_('The option set this option belongs to.') + ) + uri_prefix = models.URLField( + max_length=256, blank=True, null=True, + verbose_name=_('URI Prefix'), + help_text=_('The prefix for the URI of this option.') + ) + key = models.SlugField( + max_length=128, blank=True, null=True, + verbose_name=_('Key'), + help_text=_('The internal identifier of this option. The URI will be generated from this key.') + ) + comment = models.TextField( + blank=True, null=True, + verbose_name=_('Comment'), + help_text=_('Additional information about this option.') + ) + order = models.IntegerField( + default=0, + verbose_name=_('Order'), + help_text=_('The position of this option in lists.') + ) + text_en = models.CharField( + max_length=256, + verbose_name=_('Text (en)'), + help_text=_('The English text displayed for this option.') + ) + text_de = models.CharField( + max_length=256, + verbose_name=_('Text (de)'), + help_text=_('The German text displayed for this option.') + ) + additional_input = models.BooleanField( + default=False, + verbose_name=_('Additional input'), + help_text=_('Designates whether an additional input is possible for this option.') + ) + uri = models.URLField( + max_length=640, blank=True, null=True, + verbose_name=_('URI'), + help_text=_('The Uniform Resource Identifier of this option (auto-generated).') + ) class Meta: + ordering = ('uri', ) ordering = ('optionset', 'order', ) verbose_name = _('Option') verbose_name_plural = _('Options') def __str__(self): - return self.title + return self.key + + def save(self, *args, **kwargs): + self.uri = self.build_uri() + super(Option, self).save(*args, **kwargs) @property def text(self): return self.trans('text') + + def build_uri(self): + return self.uri_prefix.rstrip('/') + '/options/' + self.optionset.key + '/' + self.key diff --git a/apps/options/renderers.py b/apps/options/renderers.py index a1fcc4026..395c6a544 100644 --- a/apps/options/renderers.py +++ b/apps/options/renderers.py @@ -20,54 +20,56 @@ def render(self, data): xml = SimplerXMLGenerator(stream, "utf-8") xml.startDocument() - xml.startElement('OptionSets', {}) - - for optionset in data: - self._optionset(xml, optionset) - - xml.endElement('OptionSets') + self.render_document(xml, data) xml.endDocument() return stream.getvalue() - def _option(self, xml, option): - xml.startElement('Option', {}) - self._text_element(xml, 'title', {}, option["title"]) - self._text_element(xml, 'order', {}, option["order"]) - self._text_element(xml, 'text_en', {}, option["text_en"]) - self._text_element(xml, 'text_de', {}, option["text_de"]) - self._text_element(xml, 'additional_input', {}, option["additional_input"]) - xml.endElement('Option') + def render_text_element(self, xml, tag, attrs, text): + xml.startElement(tag, attrs) + if text is not None: + xml.characters(smart_text(text)) + xml.endElement(tag) + + def render_document(self, xml, optionsets): + xml.startElement('options', { + 'xmlns:dc': "http://purl.org/dc/elements/1.1/" + }) - def _optionset(self, xml, optionset): - xml.startElement('OptionSet', {}) - self._text_element(xml, 'title', {}, optionset["title"]) - self._text_element(xml, 'order', {}, optionset["order"]) + for optionset in optionsets: + self.render_optionset(xml, optionset) + + xml.endElement('options') + + def render_optionset(self, xml, optionset): + xml.startElement('optionset', {}) + self.render_text_element(xml, 'dc:uri', {}, optionset["uri"]) + self.render_text_element(xml, 'dc:comment', {}, optionset["comment"]) + self.render_text_element(xml, 'order', {}, optionset["order"]) if 'options' in optionset and optionset['options']: - xml.startElement('Options', {}) + xml.startElement('options', {}) for option in optionset['options']: - self._option(xml, option) + self.render_option(xml, option) - xml.endElement('Options') + xml.endElement('options') if 'conditions' in optionset and optionset['conditions']: - xml.startElement('Conditions', {}) + xml.startElement('conditions', {}) for condition in optionset['conditions']: - self._condition(xml, condition) - - xml.endElement('Conditions') + self.render_text_element(xml, 'condition', condition, None) - xml.endElement('OptionSet') + xml.endElement('conditions') - def _condition(self, xml, condition): - xml.startElement('Condition', {}) - self._text_element(xml, 'title', {}, condition["title"]) - xml.endElement('Condition') + xml.endElement('optionset') - def _text_element(self, xml, tag, option, text): - xml.startElement(tag, option) - if text is not None: - xml.characters(smart_text(text)) - xml.endElement(tag) + def render_option(self, xml, option): + xml.startElement('option', {}) + self.render_text_element(xml, 'dc:uri', {}, option["uri"]) + self.render_text_element(xml, 'dc:comment', {}, option["comment"]) + self.render_text_element(xml, 'order', {}, option["order"]) + self.render_text_element(xml, 'text', {'lang': 'en'}, option["text_en"]) + self.render_text_element(xml, 'text', {'lang': 'de'}, option["text_de"]) + self.render_text_element(xml, 'additional_input', {}, option["additional_input"]) + xml.endElement('option') diff --git a/apps/options/serializers.py b/apps/options/serializers.py index 32e49be13..151092500 100644 --- a/apps/options/serializers.py +++ b/apps/options/serializers.py @@ -1,6 +1,8 @@ from rest_framework import serializers -from .models import * +from apps.conditions.models import Condition + +from .models import OptionSet, Option class OptionSetIndexOptionsSerializer(serializers.ModelSerializer): @@ -9,7 +11,7 @@ class Meta: model = Option fields = ( 'id', - 'title', + 'uri', 'text' ) @@ -22,7 +24,7 @@ class Meta: model = OptionSet fields = ( 'id', - 'title', + 'uri', 'options' ) @@ -33,7 +35,9 @@ class Meta: model = OptionSet fields = ( 'id', - 'title', + 'uri_prefix', + 'key', + 'comment', 'order', 'conditions' ) @@ -46,7 +50,9 @@ class Meta: fields = ( 'id', 'optionset', - 'title', + 'uri_prefix', + 'key', + 'comment', 'order', 'text_en', 'text_de', @@ -69,7 +75,8 @@ class ExportOptionSerializer(serializers.ModelSerializer): class Meta: model = Option fields = ( - 'title', + 'uri', + 'comment', 'order', 'text_en', 'text_de', @@ -82,7 +89,7 @@ class ExportConditionSerializer(serializers.ModelSerializer): class Meta: model = Condition fields = ( - 'title', + 'uri', ) @@ -94,7 +101,8 @@ class ExportSerializer(serializers.ModelSerializer): class Meta: model = OptionSet fields = ( - 'title', + 'uri', + 'comment', 'order', 'options', 'conditions' diff --git a/apps/options/templates/options/options.html b/apps/options/templates/options/options.html index c9ca24fc5..e6eb27226 100644 --- a/apps/options/templates/options/options.html +++ b/apps/options/templates/options/options.html @@ -103,7 +103,7 @@

{% trans 'Options' %}

{% trans 'Option set' %} - {$ optionset.title $} + {$ optionset.uri $}
    @@ -119,8 +119,8 @@

    {% trans 'Options' %}

    {% trans 'Option' %} - {$ option.title $} {$ option.text $} + {$ option.uri $}
diff --git a/apps/options/templates/options/options_modal_form_options.html b/apps/options/templates/options/options_modal_form_options.html index f86d7d656..d139f98c9 100644 --- a/apps/options/templates/options/options_modal_form_options.html +++ b/apps/options/templates/options/options_modal_form_options.html @@ -13,6 +13,27 @@