Fixes on serializers DRF3 compat

This commit is contained in:
Marc Aymerich 2015-05-18 15:21:42 +00:00
parent 3963f6ce86
commit 907250d2e7
19 changed files with 241 additions and 131 deletions

20
TODO.md
View File

@ -369,3 +369,23 @@ pip3 install https://github.com/fantix/gevent/archive/master.zip
# user order_id as bill line id
# BUG Delete related services also deletes account!
# auto apend trailing slash
# get_related service__rates__isnull=TRue is that correct?
# uwsgi hot reload? http://uwsgi-docs.readthedocs.org/en/latest/articles/TheArtOfGracefulReloading.html
# change mailer.message.priority by, queue/sent inmediatelly or rename critical to noq
# method(
arg, arg, arg)
# Finish Nested *resource* serializers, like websites.domains: make fields readonly: read_only_fields = ('name',)
# websites.directives full validation like directive formset: move formset validation out and call it with compat-data from both places
# apply normlocation function on unique_location validation

View File

@ -1,5 +1,6 @@
import copy
from django.db import models
from django.forms import widgets
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
@ -19,6 +20,8 @@ class HyperlinkedModelSerializer(serializers.HyperlinkedModelSerializer):
def validate(self, attrs):
""" calls model.clean() """
attrs = super(HyperlinkedModelSerializer, self).validate(attrs)
if isinstance(attrs, models.Model):
return attrs
validated_data = dict(attrs)
ModelClass = self.Meta.model
# Remove many-to-many relationships from validated_data.
@ -39,9 +42,10 @@ class HyperlinkedModelSerializer(serializers.HyperlinkedModelSerializer):
def post_only_cleanning(self, instance, validated_data):
""" removes postonly_fields from attrs """
model_attrs = dict(**validated_data)
if instance is not None:
post_only_fields = getattr(self, 'post_only_fields', None)
if instance is not None and post_only_fields:
for attr, value in validated_data.items():
if attr in self.Meta.postonly_fields:
if attr in post_only_fields:
model_attrs.pop(attr)
return model_attrs
@ -56,6 +60,21 @@ class HyperlinkedModelSerializer(serializers.HyperlinkedModelSerializer):
return super(HyperlinkedModelSerializer, self).partial_update(instance, model_attrs)
class RelatedHyperlinkedModelSerializer(HyperlinkedModelSerializer):
""" returns object on to_internal_value based on URL """
def to_internal_value(self, data):
url = data.get('url')
if not url:
raise ValidationError({
'url': "URL is required."
})
account = self.get_account()
queryset = self.Meta.model.objects.filter(account=self.get_account())
self.fields['url'].queryset = queryset
obj = self.fields['url'].to_internal_value(url)
return obj
class SetPasswordHyperlinkedSerializer(HyperlinkedModelSerializer):
password = serializers.CharField(max_length=128, label=_('Password'),
validators=[validate_password], write_only=True, required=False,

View File

@ -16,10 +16,12 @@ class AccountSerializerMixin(object):
def __init__(self, *args, **kwargs):
super(AccountSerializerMixin, self).__init__(*args, **kwargs)
self.account = None
def get_account(self):
request = self.context.get('request')
if request:
self.account = request.user
return request.user
def create(self, validated_data):
validated_data['account'] = self.account
validated_data['account'] = self.get_account()
return super(AccountSerializerMixin, self).create(validated_data)

View File

@ -170,12 +170,12 @@ a:hover {
}
#lines .column-id {
width: 5%;
width: 8%;
text-align: right;
}
#lines .column-description {
width: 45%;
width: 42%;
text-align: left;
}

View File

@ -77,17 +77,25 @@
<span class="title column-subtotal">{% trans "subtotal" %}</span>
<br>
{% for line in lines %}
{% with sublines=line.sublines.all %}
<span class="{% if not sublines %}last {% endif %}column-id">{{ line.id }}</span>
<span class="{% if not sublines %}last {% endif %}column-description">{{ line.description }}</span>
<span class="{% if not sublines %}last {% endif %}column-period">{{ line.get_verbose_period }}</span>
<span class="{% if not sublines %}last {% endif %}column-quantity">{{ line.get_verbose_quantity|default:"&nbsp;"|safe }}</span>
<span class="{% if not sublines %}last {% endif %}column-rate">{% if line.rate %}{{ line.rate }} &{{ currency.lower }};{% else %}&nbsp;{% endif %}</span>
<span class="{% if not sublines %}last {% endif %}column-subtotal">{{ line.subtotal }} &{{ currency.lower }};</span>
{% with sublines=line.sublines.all description=line.description|slice:"40:" %}
<span class="{% if not sublines and not description %}last {% endif %}column-id">{% if not line.order_id %}L{% endif %}{{ line.order_id }}</span>
<span class="{% if not sublines and not description %}last {% endif %}column-description">{{ line.description|slice:":40" }}</span>
<span class="{% if not sublines and not description %}last {% endif %}column-period">{{ line.get_verbose_period }}</span>
<span class="{% if not sublines and not description %}last {% endif %}column-quantity">{{ line.get_verbose_quantity|default:"&nbsp;"|safe }}</span>
<span class="{% if not sublines and not description %}last {% endif %}column-rate">{% if line.rate %}{{ line.rate }} &{{ currency.lower }};{% else %}&nbsp;{% endif %}</span>
<span class="{% if not sublines and not description %}last {% endif %}column-subtotal">{{ line.subtotal }} &{{ currency.lower }};</span>
<br>
{% if description %}
<span class="{% if not sublines %}last {% endif %}subline column-id">&nbsp;</span>
<span class="{% if not sublines %}last {% endif %}subline column-description">{{ description|truncatechars:41 }}</span>
<span class="{% if not sublines %}last {% endif %}subline column-period">&nbsp;</span>
<span class="{% if not sublines %}last {% endif %}subline column-quantity">&nbsp;</span>
<span class="{% if not sublines %}last {% endif %}subline column-rate">&nbsp;</span>
<span class="{% if not sublines %}last {% endif %}subline column-subtotal">&nbsp;</span>
{% endif %}
{% for subline in sublines %}
<span class="{% if forloop.last %}last {% endif %}subline column-id">&nbsp;</span>
<span class="{% if forloop.last %}last {% endif %}subline column-description">{{ subline.description }}</span>
<span class="{% if forloop.last %}last {% endif %}subline column-description">{{ subline.description|truncatechars:41 }}</span>
<span class="{% if forloop.last %}last {% endif %}subline column-period">&nbsp;</span>
<span class="{% if forloop.last %}last {% endif %}subline column-quantity">&nbsp;</span>
<span class="{% if forloop.last %}last {% endif %}subline column-rate">&nbsp;</span>

View File

@ -3,21 +3,18 @@ from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from orchestra.api.serializers import HyperlinkedModelSerializer, SetPasswordHyperlinkedSerializer
from orchestra.api.serializers import (HyperlinkedModelSerializer,
SetPasswordHyperlinkedSerializer, RelatedHyperlinkedModelSerializer)
from orchestra.contrib.accounts.serializers import AccountSerializerMixin
from .models import Database, DatabaseUser
class RelatedDatabaseUserSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
class RelatedDatabaseUserSerializer(AccountSerializerMixin, RelatedHyperlinkedModelSerializer):
class Meta:
model = DatabaseUser
fields = ('url', 'id', 'username')
def from_native(self, data, files=None):
queryset = self.opts.model.objects.filter(account=self.account)
return get_object_or_404(queryset, username=data['username'])
class DatabaseSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
users = RelatedDatabaseUserSerializer(many=True) #allow_add_remove=True
@ -35,15 +32,11 @@ class DatabaseSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
return attrs
class RelatedDatabaseSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
class RelatedDatabaseSerializer(AccountSerializerMixin, RelatedHyperlinkedModelSerializer):
class Meta:
model = Database
fields = ('url', 'id', 'name',)
def from_native(self, data, files=None):
queryset = self.opts.model.objects.filter(account=self.account)
return get_object_or_404(queryset, name=data['name'])
class DatabaseUserSerializer(AccountSerializerMixin, SetPasswordHyperlinkedSerializer):
databases = RelatedDatabaseSerializer(many=True, required=False) # allow_add_remove=True

View File

@ -2,7 +2,7 @@ import re
from django import forms
from django.contrib import admin
from django.db.models.functions import Concat
from django.db.models.functions import Concat, Coalesce
from django.utils.translation import ugettext_lazy as _
from orchestra.admin import ExtendedModelAdmin
@ -100,7 +100,10 @@ class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin):
qs = super(DomainAdmin, self).get_queryset(request)
qs = qs.select_related('top', 'account')
if request.method == 'GET':
qs = qs.annotate(structured_name=Concat('top__name', 'name')).order_by('structured_name')
qs = qs.annotate(
structured_id=Coalesce('top__id', 'id'),
structured_name=Concat('top__name', 'name')
).order_by('-structured_id', 'structured_name')
if apps.isinstalled('orchestra.contrib.websites'):
qs = qs.prefetch_related('websites')
return qs

View File

@ -9,7 +9,7 @@ from .models import Domain
class BatchDomainCreationAdminForm(forms.ModelForm):
name = forms.CharField(label=_("Names"), widget=forms.Textarea(attrs={'rows': 5, 'cols': 50}),
help_text=_("Domain per line. All domains will share the same attributes."))
help_text=_("Domain per line. All domains will have the provided account and records."))
def clean_name(self):
self.extra_names = []

View File

@ -162,7 +162,9 @@ class Domain(models.Model):
type=Record.SOA,
value=' '.join(soa)
))
is_host = self.is_top or not types or Record.A in types or Record.AAAA in types
has_a = Record.A in types
has_aaaa = Record.AAAA in types
is_host = self.is_top or not types or has_a or has_aaaa
if is_host:
if Record.MX not in types:
for mx in settings.DOMAINS_DEFAULT_MX:
@ -170,14 +172,15 @@ class Domain(models.Model):
type=Record.MX,
value=mx
))
if not has_a and not has_aaaa:
default_a = settings.DOMAINS_DEFAULT_A
if default_a and Record.A not in types:
if default_a:
records.append(AttrDict(
type=Record.A,
value=default_a
))
default_aaaa = settings.DOMAINS_DEFAULT_AAAA
if default_aaaa and Record.AAAA not in types:
if default_aaaa:
records.append(AttrDict(
type=Record.AAAA,
value=default_aaaa

View File

@ -36,11 +36,11 @@ class DomainSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
raise ValidationError(_("Can not create subdomains of other users domains"))
return attrs
def full_clean(self, instance):
def validate(self, data):
""" Checks if everything is consistent """
instance = super(DomainSerializer, self).full_clean(instance)
if instance and instance.name:
records = self.init_data.get('records', [])
domain = domain_for_validation(instance, records)
data = super(DomainSerializer, self).validate(data)
if self.instance and data.get('name'):
records = data['records']
domain = domain_for_validation(self.instance, records)
validators.validate_zone(domain.render_zone())
return instance
return data

View File

@ -4,22 +4,18 @@ from django.utils.translation import ugettext_lazy as _
from django.shortcuts import get_object_or_404
from rest_framework import serializers
from orchestra.api.serializers import SetPasswordHyperlinkedSerializer
from orchestra.api.serializers import SetPasswordHyperlinkedSerializer, RelatedHyperlinkedModelSerializer
from orchestra.contrib.accounts.serializers import AccountSerializerMixin
from orchestra.core.validators import validate_password
from .models import List
class RelatedDomainSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
class RelatedDomainSerializer(AccountSerializerMixin, RelatedHyperlinkedModelSerializer):
class Meta:
model = List.address_domain.field.rel.to
fields = ('url', 'id', 'name')
def from_native(self, data, files=None):
queryset = self.opts.model.objects.filter(account=self.account)
return get_object_or_404(queryset, name=data['name'])
class ListSerializer(AccountSerializerMixin, SetPasswordHyperlinkedSerializer):
password = serializers.CharField(max_length=128, label=_('Password'),

View File

@ -3,21 +3,17 @@ from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from orchestra.api.serializers import SetPasswordHyperlinkedSerializer
from orchestra.api.serializers import SetPasswordHyperlinkedSerializer, RelatedHyperlinkedModelSerializer
from orchestra.contrib.accounts.serializers import AccountSerializerMixin
from .models import Mailbox, Address
class RelatedDomainSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
class RelatedDomainSerializer(AccountSerializerMixin, RelatedHyperlinkedModelSerializer):
class Meta:
model = Address.domain.field.rel.to
fields = ('url', 'id', 'name')
def from_native(self, data, files=None):
queryset = self.opts.model.objects.filter(account=self.account)
return get_object_or_404(queryset, name=data['name'])
class RelatedAddressSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
domain = RelatedDomainSerializer()
@ -42,15 +38,11 @@ class MailboxSerializer(AccountSerializerMixin, SetPasswordHyperlinkedSerializer
postonly_fields = ('name', 'password')
class RelatedMailboxSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
class RelatedMailboxSerializer(AccountSerializerMixin, RelatedHyperlinkedModelSerializer):
class Meta:
model = Mailbox
fields = ('url', 'id', 'name')
def from_native(self, data, files=None):
queryset = self.opts.model.objects.filter(account=self.account)
return get_object_or_404(queryset, name=data['name'])
class AddressSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
domain = RelatedDomainSerializer()

View File

@ -15,8 +15,8 @@ class ResourceSerializer(serializers.ModelSerializer):
fields = ('name', 'used', 'allocated', 'unit')
read_only_fields = ('used',)
def from_native(self, raw_data, files=None):
data = super(ResourceSerializer, self).from_native(raw_data, files=files)
def to_internal_value(self, raw_data):
data = super(ResourceSerializer, self).to_internal_value(raw_data)
if not data.resource_id:
data.resource = Resource.objects.get(name=raw_data['name'])
return data

View File

@ -3,25 +3,21 @@ from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from orchestra.api.serializers import SetPasswordHyperlinkedSerializer
from orchestra.api.serializers import SetPasswordHyperlinkedSerializer, RelatedHyperlinkedModelSerializer
from orchestra.contrib.accounts.serializers import AccountSerializerMixin
from .models import SystemUser
from .validators import validate_home
class GroupSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
class RelatedGroupSerializer(AccountSerializerMixin, RelatedHyperlinkedModelSerializer):
class Meta:
model = SystemUser
fields = ('url', 'id', 'username',)
def from_native(self, data, files=None):
queryset = self.opts.model.objects.filter(account=self.account)
return get_object_or_404(queryset, username=data['username'])
class SystemUserSerializer(AccountSerializerMixin, SetPasswordHyperlinkedSerializer):
groups = GroupSerializer(many=True, required=False)
groups = RelatedGroupSerializer(many=True, required=False)
class Meta:
model = SystemUser
@ -36,7 +32,7 @@ class SystemUserSerializer(AccountSerializerMixin, SetPasswordHyperlinkedSeriali
username=attrs.get('username') or self.instance.username,
shell=attrs.get('shell') or self.instance.shell,
)
validate_home(user, attrs, self.account)
validate_home(user, attrs, self.get_account())
return attrs
def validate_groups(self, attrs, source):

View File

@ -112,5 +112,13 @@ class WebsiteAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
formfield.queryset = formfield.queryset.exclude(qset)
return formfield
def _create_formsets(self, request, obj, change):
""" bind contents formset to directive formset for unique location cross-validation """
formsets, inline_instances = super(WebsiteAdmin, self)._create_formsets(request, obj, change)
if request.method == 'POST':
contents, directives = formsets
directives.content_formset = contents
return formsets, inline_instances
admin.site.register(Website, WebsiteAdmin)

View File

@ -1,4 +1,5 @@
import re
from collections import defaultdict
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
@ -19,6 +20,7 @@ class SiteDirective(Plugin):
help_text = ""
unique_name = False
unique_value = False
unique_location = False
@classmethod
@cached
@ -50,6 +52,37 @@ class SiteDirective(Plugin):
for group, options in options.items():
yield (group, [(op.name, op.verbose_name) for op in options])
def validate_uniqueness(self, directive, values, locations):
""" Validates uniqueness location, name and value """
errors = defaultdict(list)
# location uniqueness
location = None
if self.unique_location:
location = directive['value'].split()[0]
if location is not None and location in locations:
errors['value'].append(ValidationError(
"Location '%s' already in use by other content/directive." % location
))
else:
locations.add(location)
# name uniqueness
if self.unique_name and self.name in values:
errors[None].append(ValidationError(
_("Only one %s can be defined.") % self.get_verbose_name()
))
# value uniqueness
value = directive.get('value', None)
if value is not None:
if self.unique_value and value in values.get(self.name, []):
errors['value'].append(ValidationError(
_("This value is already used by other %s.") % force_text(self.get_verbose_name())
))
values[self.name].append(value)
if errors:
raise ValidationError(errors)
def validate(self, website):
if self.regex and not re.match(self.regex, website.value):
raise ValidationError({
@ -68,6 +101,7 @@ class Redirect(SiteDirective):
regex = r'^[^ ]+\s[^ ]+$'
group = SiteDirective.HTTPD
unique_value = True
unique_location = True
class Proxy(SiteDirective):
@ -77,6 +111,7 @@ class Proxy(SiteDirective):
regex = r'^[^ ]+\shttp[^ ]+(timeout=[0-9]{1,3}|retry=[0-9]|\s)*$'
group = SiteDirective.HTTPD
unique_value = True
unique_location = True
class ErrorDocument(SiteDirective):
@ -125,6 +160,7 @@ class SecRuleRemove(SiteDirective):
help_text = _("Space separated ModSecurity rule IDs.")
regex = r'^[0-9\s]+$'
group = SiteDirective.SEC
unique_location = True
class SecEngine(SiteDirective):
@ -143,6 +179,7 @@ class WordPressSaaS(SiteDirective):
group = SiteDirective.SAAS
regex = r'^/[^ ]*$'
unique_value = True
unique_location = True
class DokuWikiSaaS(SiteDirective):
@ -152,6 +189,7 @@ class DokuWikiSaaS(SiteDirective):
group = SiteDirective.SAAS
regex = r'^/[^ ]*$'
unique_value = True
unique_location = True
class DrupalSaaS(SiteDirective):
@ -161,3 +199,4 @@ class DrupalSaaS(SiteDirective):
group = SiteDirective.SAAS
regex = r'^/[^ ]*$'
unique_value = True
unique_location = True

View File

@ -1,8 +1,11 @@
from collections import defaultdict
from django import forms
from django.core.exceptions import ValidationError
from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _
from .directives import SiteDirective
from .validators import validate_domain_protocol
@ -24,24 +27,22 @@ class WebsiteAdminForm(forms.ModelForm):
class WebsiteDirectiveInlineFormSet(forms.models.BaseInlineFormSet):
""" Validate uniqueness """
def clean(self):
values = {}
# directives formset cross-validation with contents for unique locations
locations = set()
for form in self.content_formset.forms:
location = form.cleaned_data.get('path')
if location is not None:
locations.add(location)
directives = []
values = defaultdict(list)
for form in self.forms:
name = form.cleaned_data.get('name', None)
if name is not None:
directive = form.instance.directive_class
if directive.unique_name and name in values:
form.add_error(None, ValidationError(
_("Only one %s can be defined.") % directive.get_verbose_name()
))
value = form.cleaned_data.get('value', None)
if value is not None:
if directive.unique_value and value in values.get(name, []):
form.add_error('value', ValidationError(
_("This value is already used by other %s.") % force_text(directive.get_verbose_name())
))
website = form.instance
directive = form.cleaned_data
if directive.get('name') is not None:
try:
values[name].append(value)
except KeyError:
values[name] = [value]
website.directive_instance.validate_uniqueness(directive, values, locations)
except ValidationError as err:
for k,v in err.error_dict.items():
form.add_error(k, v)

View File

@ -2,34 +2,28 @@ from django.core.exceptions import ValidationError
from django.shortcuts import get_object_or_404
from rest_framework import serializers
from orchestra.api.serializers import HyperlinkedModelSerializer
from orchestra.api.serializers import HyperlinkedModelSerializer, RelatedHyperlinkedModelSerializer
from orchestra.contrib.accounts.serializers import AccountSerializerMixin
from .directives import SiteDirective
from .models import Website, Content, WebsiteDirective
from .validators import validate_domain_protocol
class RelatedDomainSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
class RelatedDomainSerializer(AccountSerializerMixin, RelatedHyperlinkedModelSerializer):
class Meta:
model = Website.domains.field.rel.to
fields = ('url', 'id', 'name')
def from_native(self, data, files=None):
queryset = self.opts.model.objects.filter(account=self.account)
return get_object_or_404(queryset, name=data['name'])
class RelatedWebAppSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
class RelatedWebAppSerializer(AccountSerializerMixin, RelatedHyperlinkedModelSerializer):
class Meta:
model = Content.webapp.field.rel.to
fields = ('url', 'id', 'name', 'type')
def from_native(self, data, files=None):
queryset = self.opts.model.objects.filter(account=self.account)
return get_object_or_404(queryset, name=data['name'])
class ContentSerializer(serializers.HyperlinkedModelSerializer):
class ContentSerializer(serializers.ModelSerializer):
webapp = RelatedWebAppSerializer()
class Meta:
@ -53,9 +47,8 @@ class DirectiveSerializer(serializers.ModelSerializer):
class WebsiteSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
domains = RelatedDomainSerializer(many=True, required=False) #allow_add_remove=True
contents = ContentSerializer(required=False, many=True, #allow_add_remove=True,
source='content_set')
domains = RelatedDomainSerializer(many=True, required=False)
contents = ContentSerializer(required=False, many=True, source='content_set')
directives = DirectiveSerializer(required=False)
class Meta:
@ -63,15 +56,37 @@ class WebsiteSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
fields = ('url', 'id', 'name', 'protocol', 'domains', 'is_active', 'contents', 'directives')
postonly_fileds = ('name',)
def full_clean(self, instance):
def validate(self, data):
""" Prevent multiples domains on the same protocol """
for domain in instance._m2m_data['domains']:
# Validate location and directive uniqueness
errors = []
directives = data.get('directives', [])
if directives:
locations = set()
for content in data.get('content_set', []):
location = content.get('path')
if location is not None:
locations.add(location)
values = defaultdict(list)
for name, value in directives.items():
directive = {
'name': name,
'value': value,
}
try:
validate_domain_protocol(instance, domain, instance.protocol)
except ValidationError as e:
# TODO not sure about this one
self.add_error(None, e)
return instance
SiteDirective.get(name).validate_uniqueness(directive, values, locations)
except ValidationError as err:
errors.append(err)
# Validate domain protocol uniqueness
instance = self.instance
for domain in data['domains']:
try:
validate_domain_protocol(instance, domain, data['protocol'])
except ValidationError as err:
errors.append(err)
if errors:
raise ValidationError(errors)
return data
def create(self, validated_data):
directives_data = validated_data.pop('directives')
@ -80,9 +95,7 @@ class WebsiteSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
WebsiteDirective.objects.create(webapp=webapp, name=key, value=value)
return webap
def update(self, instance, validated_data):
directives_data = validated_data.pop('directives')
instance = super(WebsiteSerializer, self).update(instance, validated_data)
def update_directives(self, instance, directives_data):
existing = {}
for obj in instance.directives.all():
existing[obj.name] = obj
@ -99,4 +112,19 @@ class WebsiteSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
directive.save(update_fields=('value',))
for to_delete in set(existing.keys())-posted:
existing[to_delete].delete()
def update_contents(self, instance, contents_data):
raise NotImplementedError
def update_domains(self, instance, domains_data):
raise NotImplementedError
def update(self, instance, validated_data):
directives_data = validated_data.pop('directives')
domains_data = validated_data.pop('domains')
contents_data = validated_data.pop('content_set')
instance = super(WebsiteSerializer, self).update(instance, validated_data)
self.update_directives(instance, directives_data)
self.update_contents(instance, contents_data)
self.update_domains(instance, domains_data)
return instance

View File

@ -21,16 +21,18 @@ class Register(object):
kwargs['verbose_name'] = model._meta.verbose_name
if 'verbose_name_plural' not in kwargs:
kwargs['verbose_name_plural'] = model._meta.verbose_name_plural
self._registry[model] = AttrDict(**kwargs)
defaults = {
'menu': True,
}
defaults.update(kwargs)
self._registry[model] = AttrDict(**defaults)
def register_view(self, view_name, **kwargs):
if view_name in self._registry:
raise KeyError("%s already registered" % view_name)
if 'verbose_name' not in kwargs:
raise KeyError("%s verbose_name is required for views" % view_name)
if 'verbose_name_plural' not in kwargs:
kwargs['verbose_name_plural'] = string_concat(kwargs['verbose_name'], 's')
self._registry[view_name] = AttrDict(**kwargs)
self.register(view_name, **kwargs)
def get(self, *args):
if args: