Skip to content

Commit

Permalink
Refactor ThroughSerializer again (revert parts of ce9003f) and add el…
Browse files Browse the repository at this point in the history
…ements

field to Page and QuestionSet components
  • Loading branch information
jochenklar committed Mar 26, 2023
1 parent a1761d9 commit 69332e0
Show file tree
Hide file tree
Showing 40 changed files with 415 additions and 369 deletions.
59 changes: 19 additions & 40 deletions rdmo/core/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,22 +58,6 @@ def __init__(self, *args, **kwargs):
allow_blank=model_field.blank)


class ThroughModelListField(serializers.ListField):

def to_internal_value(self, data):
target_field_name = self.child.Meta.fields[0]
return super().to_internal_value([
{
target_field_name: value
} for value in data
])

def to_representation(self, data):
target_field_name = self.child.Meta.fields[0]
items = sorted(data.all(), key=lambda e: e.order)
return [getattr(item, target_field_name).id for item in items]


class ThroughModelSerializerMixin(object):

def create(self, validated_data):
Expand All @@ -92,21 +76,21 @@ def get_through_fields(self, validated_data):
model_info = model_meta.get_field_info(self.Meta.model)

through_fields = {}
for field_name, field in self.get_fields().items():
if isinstance(field, ThroughModelListField):
through_model = model_info.reverse_relations[field.source].related_model

target_field_name = field.child.Meta.fields[0]
for fn, f in model_meta.get_field_info(through_model).forward_relations.items():
if fn != target_field_name:
source_field_name = fn

through_fields[field.source] = {
'source_field_name': source_field_name,
'target_field_name': target_field_name,
'through_model': through_model,
'validated_data': validated_data.pop(field.source, None)
}
for field_name in self.Meta.through_fields:
field = self.get_fields().get(field_name)

through_model = model_info.reverse_relations[field.source].related_model

target_field_name = field.child.Meta.fields[0]
for fn, f in model_meta.get_field_info(through_model).forward_relations.items():
if fn != target_field_name:
source_field_name = fn
through_fields[field.source] = {
'source_field_name': source_field_name,
'target_field_name': target_field_name,
'through_model': through_model,
'validated_data': validated_data.pop(field.source, None)
}

return through_fields

Expand All @@ -115,23 +99,22 @@ def set_through_fields(self, instance, through_fields):
if field_config['validated_data'] is not None:
items = list(getattr(instance, field_name).all())

for order, data in enumerate(field_config['validated_data']):
for data in field_config['validated_data']:
try:
# look for the item in items
item = next(filter(lambda item: getattr(item, field_config['target_field_name']) ==
data.get(field_config['target_field_name']), items))
# update order if the item if it changed
if item.order != order:
item.order = order
if item.order != data.get('order'):
item.order = data.get('order')
item.save()

# remove the item from the items list so that it won't get removed
items.remove(item)
except StopIteration:
# create a new item
new_data = dict({
field_config['source_field_name']: instance,
'order': order
field_config['source_field_name']: instance
}, **data)
new_item = field_config['through_model'](**new_data)
new_item.save()
Expand All @@ -157,10 +140,6 @@ def get_warning(self, obj):
return any([get_language_warning(obj, field_name) for field_name in self.Meta.warning_fields])


class ElementSerializer(serializers.ModelSerializer):
pass


class SiteSerializer(serializers.ModelSerializer):

class Meta:
Expand Down
11 changes: 11 additions & 0 deletions rdmo/core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,17 @@ def get_model_field_meta(model):
if hasattr(field, 'help_text'):
meta[field.name]['help_text'] = field.help_text

if model.__name__ == 'Page':
meta['elements'] = {
'verbose_name': _('Elements'),
'help_text': _('The questions and question sets for this page.')
}
elif model.__name__ == 'QuestionSet':
meta['elements'] = {
'verbose_name': _('Elements'),
'help_text': _('The questions and question sets for this question set.')
}

return meta


Expand Down
20 changes: 11 additions & 9 deletions rdmo/domain/tests/test_viewset_attribute.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,6 @@ def test_list(db, client, username, password):
assert response.status_code == status_map['list'][username], response.json()


@pytest.mark.parametrize('username,password', users)
def test_nested(db, client, username, password):
client.login(username=username, password=password)

url = reverse(urlnames['nested'])
response = client.get(url)
assert response.status_code == status_map['list'][username], response.json()


@pytest.mark.parametrize('username,password', users)
def test_export(db, client, username, password):
client.login(username=username, password=password)
Expand All @@ -85,6 +76,17 @@ def test_detail(db, client, username, password):
assert response.status_code == status_map['detail'][username], response.json()


@pytest.mark.parametrize('username,password', users)
def test_nested(db, client, username, password):
client.login(username=username, password=password)
instances = Attribute.objects.order_by('-level')

for instance in instances:
url = reverse(urlnames['nested'], args=[instance.pk])
response = client.get(url)
assert response.status_code == status_map['detail'][username], response.json()


@pytest.mark.parametrize('username,password', users)
def test_create(db, client, username, password):
client.login(username=username, password=password)
Expand Down
4 changes: 2 additions & 2 deletions rdmo/management/assets/js/actions/elementActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,6 @@ export function deleteElementError(element, error) {

// update elements

export function updateElement(element, field, value) {
return {type: 'elements/updateElement', element, field, value}
export function updateElement(element, values) {
return {type: 'elements/updateElement', element, values}
}
2 changes: 1 addition & 1 deletion rdmo/management/assets/js/components/edit/EditAttribute.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const EditAttribute = ({ config, attribute, elements, elementActions }) => {

const { attributes, conditions, pages, questionsets, questions, tasks } = elements

const updateAttribute = (key, value) => elementActions.updateElement(attribute, key, value)
const updateAttribute = (key, value) => elementActions.updateElement(attribute, {[key]: value})
const storeAttribute = () => elementActions.storeElement('attributes', attribute)
const deleteAttribute = () => elementActions.deleteElement('attributes', attribute)

Expand Down
4 changes: 2 additions & 2 deletions rdmo/management/assets/js/components/edit/EditCatalog.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const EditCatalog = ({ config, catalog, elements, elementActions }) => {

const { sites, groups, sections } = elements

const updateCatalog = (key, value) => elementActions.updateElement(catalog, key, value)
const updateCatalog = (key, value) => elementActions.updateElement(catalog, {[key]: value})
const storeCatalog = () => elementActions.storeElement('catalogs', catalog)
const deleteCatalog = () => elementActions.deleteElement('catalogs', catalog)

Expand All @@ -35,7 +35,7 @@ const EditCatalog = ({ config, catalog, elements, elementActions }) => {
<BackButton />
{
catalog.id ? <SaveButton onClick={storeCatalog} />
: <CreateButton onClick={storeCatalog} />
: <CreateButton onClick={storeCatalog} />
}
</div>
{
Expand Down
3 changes: 1 addition & 2 deletions rdmo/management/assets/js/components/edit/EditCondition.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ const EditCondition = ({ config, condition, elements, elementActions }) => {

const { relations, attributes, optionsets, options, pages, questionsets, questions, tasks } = elements


const updateCondition = (key, value) => elementActions.updateElement(condition, key, value)
const updateCondition = (key, value) => elementActions.updateElement(condition, {[key]: value})
const storeCondition = () => elementActions.storeElement('conditions', condition)
const deleteCondition = () => elementActions.deleteElement('conditions', condition)

Expand Down
2 changes: 1 addition & 1 deletion rdmo/management/assets/js/components/edit/EditOption.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const EditOption = ({ config, option, elements, elementActions }) => {

const optionConditions = conditions.filter(e => option.conditions.includes(e.id))

const updateOption = (key, value) => elementActions.updateElement(option, key, value)
const updateOption = (key, value) => elementActions.updateElement(option, {[key]: value})
const storeOption = () => elementActions.storeElement('options', option)
const deleteOption = () => elementActions.deleteElement('options', option)

Expand Down
2 changes: 1 addition & 1 deletion rdmo/management/assets/js/components/edit/EditOptionSet.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const EditOptionSet = ({ config, optionset, elements, elementActions }) => {

const optionsetQuestions = questions.filter(e => optionset.questions.includes(e.id))

const updateOptionSet = (key, value) => elementActions.updateElement(optionset, key, value)
const updateOptionSet = (key, value) => elementActions.updateElement(optionset, {[key]: value})
const storeOptionSet = () => elementActions.storeElement('optionsets', optionset)
const deleteOptionSet = () => elementActions.deleteElement('optionsets', optionset)

Expand Down
34 changes: 24 additions & 10 deletions rdmo/management/assets/js/components/edit/EditPage.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React, { Component, useState } from 'react'
import PropTypes from 'prop-types'
import { Tabs, Tab } from 'react-bootstrap';
import isUndefined from 'lodash/isUndefined'
import orderBy from 'lodash/orderBy'

import Checkbox from '../forms/Checkbox'
import MultiSelect from '../forms/MultiSelect'
Expand All @@ -21,9 +23,25 @@ const EditPage = ({ config, page, elements, elementActions }) => {

const { attributes, conditions, sections, questionsets, questions } = elements

const pageSections = sections.filter(e => page.sections.includes(e.id))

const updatePage = (key, value) => elementActions.updateElement(page, key, value)
const elementValues = orderBy(page.questions.concat(page.questionsets), ['order', 'uri'])
const elementOptions = elements.questions.map(question => ({
value: 'question-' + question.id,
label: interpolate(gettext('Question: %s'), [question.uri])
})).concat(elements.questionsets.map(questionset => ({
value: 'questionset-' + questionset.id,
label: interpolate(gettext('Question set: %s'), [questionset.uri])
})))

const updatePage = (key, value) => {
if (key == 'elements') {
elementActions.updateElement(page, {
questions: value.filter(e => !isUndefined(e.question)),
questionsets: value.filter(e => !isUndefined(e.questionset))
})
} else {
elementActions.updateElement(page, { [key]: value })
}
}
const storePage = () => elementActions.storeElement('pages', page)
const deletePage = () => elementActions.deleteElement('pages', page)

Expand Down Expand Up @@ -80,13 +98,9 @@ const EditPage = ({ config, page, elements, elementActions }) => {
options={attributes} onChange={updatePage} />
</div>
<div className="col-sm-12">
<OrderedMultiSelect config={config} element={page} field="questionsets"
options={questionsets} verboseName="questionset"
onChange={updatePage} />
</div>
<div className="col-sm-12">
<OrderedMultiSelect config={config} element={page} field="questions"
options={questions} verboseName="question"
<OrderedMultiSelect config={config} element={page} field="elements"
values={elementValues} options={elementOptions}
verboseName={gettext('element')}
onChange={updatePage} />
</div>
<div className="col-sm-12">
Expand Down
2 changes: 1 addition & 1 deletion rdmo/management/assets/js/components/edit/EditQuestion.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const EditQuestion = ({ config, question, elements, elementActions}) => {

const { attributes, optionsets, options, conditions, pages, questionsets, widgetTypes, valueTypes } = elements

const updateQuestion = (key, value) => elementActions.updateElement(question, key, value)
const updateQuestion = (key, value) => elementActions.updateElement(question, {[key]: value})
const storeQuestion = () => elementActions.storeElement('questions', question)
const deleteQuestion = () => elementActions.deleteElement('questions', question)

Expand Down
32 changes: 24 additions & 8 deletions rdmo/management/assets/js/components/edit/EditQuestionSet.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React, { Component, useState } from 'react'
import PropTypes from 'prop-types'
import { Tabs, Tab } from 'react-bootstrap';
import isUndefined from 'lodash/isUndefined'
import orderBy from 'lodash/orderBy'

import Checkbox from '../forms/Checkbox'
import OrderedMultiSelect from '../forms/OrderedMultiSelect'
Expand All @@ -20,7 +22,25 @@ const EditQuestionSet = ({ config, questionset, elements, elementActions }) => {

const { attributes, conditions, pages, questionsets, questions } = elements

const updateQuestionSet = (key, value) => elementActions.updateElement(questionset, key, value)
const elementValues = orderBy(questionset.questions.concat(questionset.questionsets), ['order', 'uri'])
const elementOptions = elements.questions.map(question => ({
value: 'question-' + question.id,
label: interpolate(gettext('Question: %s'), [question.uri])
})).concat(elements.questionsets.map(questionset => ({
value: 'questionset-' + questionset.id,
label: interpolate(gettext('Question set: %s'), [questionset.uri])
})))

const updateQuestionSet = (key, value) => {
if (key == 'elements') {
elementActions.updateElement(questionset, {
questions: value.filter(e => !isUndefined(e.question)),
questionsets: value.filter(e => !isUndefined(e.questionset))
})
} else {
elementActions.updateElement(questionset, { [key]: value })
}
}
const storeQuestionSet = () => elementActions.storeElement('questionsets', questionset)
const deleteQuestionSet = () => elementActions.deleteElement('questionsets', questionset)

Expand Down Expand Up @@ -77,13 +97,9 @@ const EditQuestionSet = ({ config, questionset, elements, elementActions }) => {
options={attributes} onChange={updateQuestionSet} />
</div>
<div className="col-sm-12">
<OrderedMultiSelect config={config} element={questionset} field="questionsets"
options={questionsets} verboseName="questionset"
onChange={updateQuestionSet} />
</div>
<div className="col-sm-12">
<OrderedMultiSelect config={config} element={questionset} field="questions"
options={questions} verboseName="question"
<OrderedMultiSelect config={config} element={questionset} field="elements"
values={elementValues} options={elementOptions}
verboseName={gettext('element')}
onChange={updateQuestionSet} />
</div>
<div className="col-sm-12">
Expand Down
2 changes: 1 addition & 1 deletion rdmo/management/assets/js/components/edit/EditSection.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const EditSection = ({ config, section, elements, elementActions}) => {
const { pages, catalogs } = elements
const sectionCatalogs = catalogs.filter(e => section.catalogs.includes(e.id))

const updateSection = (key, value) => elementActions.updateElement(section, key, value)
const updateSection = (key, value) => elementActions.updateElement(section, {[key]: value})
const storeSection = () => elementActions.storeElement('sections', section)
const deleteSection = () => elementActions.deleteElement('sections', section)

Expand Down
2 changes: 1 addition & 1 deletion rdmo/management/assets/js/components/edit/EditTask.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const EditTask = ({ config, task, elements, elementActions}) => {

const { attributes, catalogs, sites, groups } = elements

const updateTask = (key, value) => elementActions.updateElement(task, key, value)
const updateTask = (key, value) => elementActions.updateElement(task, {[key]: value})
const storeTask = () => elementActions.storeElement('tasks', task)
const deleteTask = () => elementActions.deleteElement('tasks', task)

Expand Down
2 changes: 1 addition & 1 deletion rdmo/management/assets/js/components/edit/EditView.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const EditView = ({ config, view, elements, elementActions }) => {

const { catalogs, sites, groups } = elements

const updateView = (key, value) => elementActions.updateElement(view, key, value)
const updateView = (key, value) => elementActions.updateElement(view, {[key]: value})
const storeView = () => elementActions.storeElement('views', view)
const deleteView = () => elementActions.deleteElement('views', view)

Expand Down

0 comments on commit 69332e0

Please sign in to comment.