Skip to content

Commit

Permalink
support model multi choice
Browse files Browse the repository at this point in the history
* Adds new serialize/deserialize/unlazify functions to handle complex types
  • Loading branch information
nitely committed May 24, 2018
1 parent 81e092b commit 642b2cb
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 24 deletions.
17 changes: 15 additions & 2 deletions djconfig/conf.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# -*- coding: utf-8 -*-

from __future__ import unicode_literals
import json

from django.apps import apps
from django import forms
from django.db import models
from django.conf import settings

__all__ = [
Expand Down Expand Up @@ -42,6 +44,16 @@ def _check_backend():
"is required but it was not found in "
"MIDDLEWARE_CLASSES nor in MIDDLEWARE")

def _deserialize(value, field):
assert isinstance(field, forms.Field)
if isinstance(field, forms.ModelMultipleChoiceField):
return json.loads(value, encoding='utf8')
return value

def _unlazify(value):
if isinstance(value, models.QuerySet):
return list(value)
return value

class Config(object):
"""
Expand Down Expand Up @@ -109,17 +121,18 @@ def _reload(self):
name: field.initial
for name, field in empty_form.fields.items()})
form = form_class(data={
name: data[name]
name: _deserialize(data[name], field)
for name, field in empty_form.fields.items()
if name in data and not isinstance(field, forms.FileField)})
form.is_valid()
cache.update({
name: value
name: _unlazify(value)
for name, value in form.cleaned_data.items()
if name in data})
# files are special because they don't have an initial value
# and the POSTED data must contain the file. So, we keep
# the stored path as is
# TODO: see if serialize/deserialize/unlazify can be used for this instead
cache.update({
name: data[name]
for name, field in empty_form.fields.items()
Expand Down
32 changes: 20 additions & 12 deletions djconfig/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ class ConfigForm(conf._ConfigFormBase):
name to avoid clashing with other registered forms,\
prefixing them with the app name is a good practice.
:param \*args: Positional parameters passed to parent class
:param \*\*kwargs: Keyword parameters passed to parent class
:param args: Positional parameters passed to parent class
:param kwargs: Keyword parameters passed to parent class
"""
def __init__(self, *args, **kwargs):
super(ConfigForm, self).__init__(*args, **kwargs)
Expand All @@ -45,19 +45,27 @@ def save(self):
'class_name': self.__class__.__name__
}

data = self.cleaned_data
data['_updated_at'] = timezone.now()
ConfigModel = apps.get_model('djconfig.Config')

for field_name, value in data.items():
value = utils.serialize(value)

for field_name, value in self.cleaned_data.items():
value = utils.serialize(
value=value,
field=self.fields.get(field_name, None))
# TODO: use update_or_create
count = ConfigModel.objects\
.filter(key=field_name)\
.update(value=value)

count = (ConfigModel.objects
.filter(key=field_name)
.update(value=value))
if not count:
ConfigModel.objects.create(key=field_name, value=value)
ConfigModel.objects.create(
key=field_name,
value=value)

count = (ConfigModel.objects
.filter(key='_updated_at')
.update(value=str(timezone.now())))
if not count:
ConfigModel.objects.create(
key='_updated_at',
value=str(timezone.now()))

conf.config._reload()
15 changes: 9 additions & 6 deletions djconfig/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

from __future__ import unicode_literals
from functools import wraps
import json

from django.db import models
from django import forms

from . import conf

Expand All @@ -19,7 +21,7 @@ def override_djconfig(**new_cache_values):
This is similar to :py:func:`django.test.override_settings`,\
use it in testing.
:param \*\*new_cache_values: Keyword arguments,\
:param new_cache_values: Keyword arguments,\
the key should match one in the config,\
a new one is created otherwise,\
the value is overridden within\
Expand Down Expand Up @@ -53,18 +55,19 @@ def func_wrapper(*args, **kw):

return decorator


def serialize(value):
# todo: add DateField
def serialize(value, field):
"""
Form values serialization
:param object value: A value to be serialized\
for saving it into the database and later\
loading it into the form as initial value
"""
# todo: add DateField

assert isinstance(field, forms.Field)
if isinstance(field, forms.ModelMultipleChoiceField):
return json.dumps([v.pk for v in value])
# todo: remove
if isinstance(value, models.Model):
return value.pk

return value
48 changes: 44 additions & 4 deletions tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class FooForm(ConfigForm):
url = forms.URLField(initial="foo.com/")
choices = forms.ChoiceField(initial=None, choices=[('1', 'label_a'), ('2', 'label_b')])
model_choices = forms.ModelChoiceField(initial=None, queryset=ChoiceModel.objects.all())
model_m_choices = forms.ModelMultipleChoiceField(initial=None, queryset=ChoiceModel.objects.all())
image = forms.ImageField(initial=None, required=False)
file = forms.FileField(initial=None, required=False)

Expand Down Expand Up @@ -102,6 +103,11 @@ def clean_model_choice(self):
return self.cleaned_data['model_choice'].pk


class ModelMultipleChoiceForm(ConfigForm):

model_choices = forms.ModelMultipleChoiceField(initial=None, queryset=ChoiceModel.objects.all())


class ImageForm(ConfigForm):

image = forms.ImageField(initial=None, required=False)
Expand Down Expand Up @@ -202,6 +208,24 @@ def test_config_form_model_choice_pk(self):
qs = ConfigModel.objects.get(key="model_choice")
self.assertEqual(qs.value, str(model_choice.pk))

def test_config_form_model_multi_choice(self):
"""
Saves ModelMultipleChoiceField
"""
djconfig.register(ModelMultipleChoiceForm)
model_choice_a = ChoiceModel.objects.create(name='foo')
model_choice_b = ChoiceModel.objects.create(name='bar')
form = ModelMultipleChoiceForm(data={
"model_choices": [
str(model_choice_a.pk),
str(model_choice_b.pk)]})
self.assertTrue(form.is_valid())
form.save()

qs = ConfigModel.objects.get(key="model_choices")
self.assertEqual(qs.value, '[1, 2]')
self.assertEqual(list(config.model_choices), [model_choice_a, model_choice_b])

def test_config_form_update(self):
"""
Should update the form with the supplied data when saving
Expand Down Expand Up @@ -344,7 +368,8 @@ def test_config_load(self):
djconfig.register(FooForm)
djconfig.reload_maybe()
keys = ['boolean', 'boolean_false', 'char', 'email', 'float_number',
'integer', 'url', 'choices', 'model_choices', 'image', 'file']
'integer', 'url', 'choices', 'model_choices', 'model_m_choices',
'image', 'file']
values = {k: getattr(config, k) for k in keys}
self.assertDictEqual(
values,
Expand All @@ -358,6 +383,7 @@ def test_config_load(self):
'url': "foo.com/",
'choices': None,
'model_choices': None,
'model_m_choices': None,
'image': None,
'file': None
}
Expand All @@ -368,6 +394,7 @@ def test_config_load_from_database(self):
Load configuration into the cache
"""
model_choice = ChoiceModel.objects.create(name='A')
model_choice_b = ChoiceModel.objects.create(name='B')
data = [
ConfigModel(key='boolean', value=False),
ConfigModel(key='boolean_false', value=True),
Expand All @@ -378,6 +405,9 @@ def test_config_load_from_database(self):
ConfigModel(key='url', value="foo2.com/"),
ConfigModel(key='choices', value='1'),
ConfigModel(key='model_choices', value=model_choice.pk),
ConfigModel(key='model_m_choices', value=utils.serialize(
ChoiceModel.objects.filter(pk=model_choice_b.pk),
forms.ModelMultipleChoiceField(None))),
ConfigModel(key='image', value='path/image.gif'),
ConfigModel(key='file', value='path/file.zip')
]
Expand All @@ -386,7 +416,8 @@ def test_config_load_from_database(self):
djconfig.register(FooForm)
djconfig.reload_maybe()
keys = ['boolean', 'boolean_false', 'char', 'email', 'float_number',
'integer', 'url', 'choices', 'model_choices', 'image', 'file']
'integer', 'url', 'choices', 'model_choices', 'model_m_choices',
'image', 'file']
values = {k: getattr(config, k) for k in keys}
self.assertDictEqual(
values,
Expand All @@ -400,6 +431,7 @@ def test_config_load_from_database(self):
'url': "http://foo2.com/",
'choices': '1',
'model_choices': model_choice,
'model_m_choices': [model_choice_b],
'image': 'path/image.gif',
'file': 'path/file.zip'
}
Expand Down Expand Up @@ -563,5 +595,13 @@ def test_serialize(self):
Serializes complex objects
"""
model_choice = ChoiceModel.objects.create(name='foo')
self.assertEqual(utils.serialize(model_choice), model_choice.pk)

self.assertEqual(
utils.serialize(
model_choice,
forms.ModelChoiceField(None)),
model_choice.pk)
self.assertEqual(
utils.serialize(
ChoiceModel.objects.all(),
forms.ModelMultipleChoiceField(None)),
str([model_choice.pk]))

0 comments on commit 642b2cb

Please sign in to comment.