Skip to content

Commit

Permalink
virtual fieldset support
Browse files Browse the repository at this point in the history
  • Loading branch information
sheppard committed Jan 28, 2021
1 parent af1b7fa commit 1598f59
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 10 deletions.
62 changes: 57 additions & 5 deletions rest/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,21 +164,37 @@ def get_boolean_choices(self, field):
return [(True, 'Yes'), (False, 'No')]

def get_wq_config(self):
serializer_fields = self.get_fields_for_config()
fields = []
nested_fields = []
has_geo_fields = False

overrides = getattr(self.Meta, 'wq_field_config', {})
overrides = getattr(self.Meta, 'wq_field_config', None) or {}
fieldsets = getattr(self.Meta, 'wq_fieldsets', None) or {}

for name, field in self.get_fields_for_config().items():
def get_info(name, field):
info = self.get_wq_field_info(name, field)
if info['name'] in overrides:
info.update(overrides[info['name']])
return info

for name, conf in fieldsets.items():
conf = conf.copy()
conf.setdefault('name', name)
conf.setdefault('type', 'group')
conf.setdefault('label', name)
conf['children'] = [
get_info(name, serializer_fields.pop(name))
for name in conf.pop('fields')
]
nested_fields.append(conf)

for name, field in serializer_fields.items():
info = get_info(name, field)

if info['type'].startswith('geo') or info['name'] == 'latitude':
has_geo_fields = True

if info['name'] in overrides:
info.update(overrides[info['name']])

if info['type'] == 'repeat':
nested_fields.append(info)
else:
Expand Down Expand Up @@ -313,9 +329,45 @@ def get_wq_foreignkey_info(self, model):
model_conf = serializer().get_wq_config()
return model_conf['name']

def to_representation(self, obj):
data = super().to_representation(obj)
if getattr(self.Meta, 'wq_fieldsets', None):
return self.to_virtual_fieldsets(data)
else:
return data

def to_virtual_fieldsets(self, data):
for name, conf in self.Meta.wq_fieldsets.items():
for field in conf['fields']:
if field not in data:
continue
data.setdefault(name, {})
data[name][field] = data.pop(field)
return data

def to_internal_value(self, data):
if not getattr(self.Meta, 'wq_fieldsets', None):
return super().to_internal_value(data)

for name, conf in self.Meta.wq_fieldsets.items():
fs_data = data.pop(name, None)
if isinstance(fs_data, dict):
data.update(fs_data)

try:
return super().to_internal_value(data)
except serializers.ValidationError as e:
if isinstance(e.detail, dict):
raise serializers.ValidationError(
self.to_virtual_fieldsets(e.detail)
)
else:
raise

class Meta:
wq_config = {}
wq_field_config = {}
wq_fieldsets = None


class ModelSerializer(BaseModelSerializer):
Expand Down
15 changes: 14 additions & 1 deletion tests/rest_app/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 3.1.1 on 2020-12-16 04:49
# Generated by Django 3.1.1 on 2021-01-28 07:10

from django.conf import settings
from django.db import migrations, models
Expand Down Expand Up @@ -57,6 +57,19 @@ class Migration(migrations.Migration):
'abstract': False,
},
),
migrations.CreateModel(
name='FieldsetModel',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50)),
('title', models.CharField(max_length=20)),
('address', models.CharField(max_length=255)),
('city', models.CharField(max_length=255)),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='FileModel',
fields=[
Expand Down
10 changes: 10 additions & 0 deletions tests/rest_app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,13 @@ class ExpensiveModel(LabelModel):
null=True,
blank=True
)


class FieldsetModel(LabelModel):
# General
name = models.CharField(max_length=50)
title = models.CharField(max_length=20)

# Address
address = models.CharField(max_length=255)
city = models.CharField(max_length=255)
8 changes: 6 additions & 2 deletions tests/rest_app/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
FileModel, ImageModel,
SlugModel, SlugRefParent, SlugRefChild,
DateModel, ChoiceModel, TranslatedModel,
CharFieldModel, ExpensiveModel
CharFieldModel, ExpensiveModel, FieldsetModel
)
from .serializers import (
RootModelSerializer, ParentSerializer, ItemSerializer,
SlugRefChildSerializer, ExpensiveSerializer,
SlugRefChildSerializer, ExpensiveSerializer, FieldsetSerializer,
)


Expand Down Expand Up @@ -97,6 +97,10 @@ def cache_users_own_data(qs, req):
serializer=ExpensiveSerializer,
queryset=ExpensiveModel.objects.defer('expensive', 'more_expensive'),
)
rest.router.register_model(
FieldsetModel,
serializer=FieldsetSerializer,
)

rest.router.add_page("rest_context", {})
rest.router.add_page("auth_context", {})
Expand Down
19 changes: 18 additions & 1 deletion tests/rest_app/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from wq.db.rest.serializers import ModelSerializer
from .models import (
OneToOneModel, ExtraModel, Child, ChoiceModel, DateModel, Item,
ExpensiveModel,
ExpensiveModel, FieldsetModel
)


Expand Down Expand Up @@ -99,3 +99,20 @@ class Meta:
list_exclude = ('expensive', 'more_expensive')
config_exclude = ('more_expensive',)
model = ExpensiveModel


class FieldsetSerializer(ModelSerializer):
class Meta:
model = FieldsetModel
fields = "__all__"
wq_fieldsets = {
'general': {
'label': 'General',
'fields': ['name', 'title']
},
'contact': {
'label': 'Contact Information',
'control': {'appearance': 'contact-fieldset'},
'fields': ['address', 'city']
}
}
48 changes: 48 additions & 0 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,51 @@ def test_rest_list_exclude_config(self):
self.assertIsNone(
self.get_field(conf, 'more_expensive', allow_none=True)
)

def test_rest_virtual_fieldset(self):
conf = self.get_config('fieldsetmodel')
self.assertEqual([
{
'name': 'general',
'type': 'group',
'label': 'General',
'children': [
{
'name': 'name',
'label': 'Name',
'type': 'string',
'wq:length': 50,
'bind': {'required': True},
},
{
'name': 'title',
'label': 'Title',
'type': 'string',
'wq:length': 20,
'bind': {'required': True},
},
]
},
{
'name': 'contact',
'type': 'group',
'label': 'Contact Information',
'control': {'appearance': 'contact-fieldset'},
'children': [
{
'name': 'address',
'label': 'Address',
'type': 'string',
'wq:length': 255,
'bind': {'required': True},
},
{
'name': 'city',
'label': 'City',
'type': 'string',
'wq:length': 255,
'bind': {'required': True},
},
]
},
], conf['form'])
40 changes: 39 additions & 1 deletion tests/test_post.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from .base import APITestCase
from rest_framework import status
import json
from tests.rest_app.models import SlugModel
from tests.rest_app.models import SlugModel, FieldsetModel
from tests.gis_app.models import GeometryModel
from django.contrib.auth.models import User
from django.conf import settings
Expand Down Expand Up @@ -204,3 +204,41 @@ def test_rest_list_exclude_post(self):
item = response.data
self.assertEqual(item['name'], "Test 2")
self.assertEqual(item['expensive'], "SOME_OTHER_DATA")

def test_rest_virtual_fieldset(self):
response = self.client.post('/fieldsetmodels.json', {
"general": {
"name": "Test",
"title": "Dr.",
},
"contact": {
"address": "123 Main St",
"city": "Minneapolis",
}
}, format='json')
self.assertTrue(
status.is_success(response.status_code), response.data
)
obj = FieldsetModel.objects.get(pk=response.data['id'])
self.assertEqual(obj.name, "Test")
self.assertEqual(obj.title, "Dr.")
self.assertEqual(obj.address, "123 Main St")
self.assertEqual(obj.city, "Minneapolis")

response = self.client.post('/fieldsetmodels.json', {
"general": {
"name": "Test",
},
"contact": {
"address": "123 Main St",
"city": "Minneapolis",
}
}, format='json')
self.assertTrue(
status.is_client_error(response.status_code), response.data
)
self.assertEqual(response.data, {
'general': {
'title': ['This field is required.']
}
})

0 comments on commit 1598f59

Please sign in to comment.