Added postonly serializer fields

This commit is contained in:
Marc 2014-10-15 21:18:50 +00:00
parent b93ba235b0
commit d817fe7198
11 changed files with 83 additions and 22 deletions

12
TODO.md
View file

@ -135,19 +135,15 @@ Remember that, as always with QuerySets, any subsequent chained methods which im
* multiple domains creation; line separated domains * multiple domains creation; line separated domains
* Move MU webapps to SaaS? * Move MU webapps to SaaS?
* DN: Transaction atomicity and backend failure
* SaaS Icons
* offer to create mailbox on account creation * offer to create mailbox on account creation
* init.d celery scripts * init.d celery scripts
-# Required-Start: $network $local_fs $remote_fs postgresql celeryd -# Required-Start: $network $local_fs $remote_fs postgresql celeryd
-# Required-Stop: $network $local_fs $remote_fs postgresql celeryd -# Required-Stop: $network $local_fs $remote_fs postgresql celeryd
* POST only fields (account, username, name) etc * POST only fields (account, username, name) etc http://inka-labs.com/blog/2013/04/18/post-only-fields-django-rest-framework/
* for list virtual_domains cleaning up we need to know the old domain name when a list changes its address domain, but this is not possible with the current design. * for list virtual_domains cleaning up we need to know the old domain name when a list changes its address domain, but this is not possible with the current design.
* regenerate virtual_domains every time (configure a separate file for orchestra on postfix)
* update_fields=[] doesn't trigger post save! * update_fields=[] doesn't trigger post save!
* lists -> SaaS ?

View file

@ -235,7 +235,7 @@ class ChangePasswordAdminMixin(object):
def change_password(self, request, id, form_url=''): def change_password(self, request, id, form_url=''):
if not self.has_change_permission(request): if not self.has_change_permission(request):
raise PermissionDenied raise PermissionDenied
# TODO use this insetad of self.get_object() # TODO use this insetad of self.get_object(), in other places
user = get_object_or_404(self.get_queryset(request), pk=id) user = get_object_or_404(self.get_queryset(request), pk=id)
related = [] related = []

View file

@ -10,6 +10,55 @@ class SetPasswordSerializer(serializers.Serializer):
widget=widgets.PasswordInput, validators=[validate_password]) widget=widgets.PasswordInput, validators=[validate_password])
from rest_framework.serializers import (HyperlinkedModelSerializerOptions,
HyperlinkedModelSerializer)
class tHyperlinkedModelSerializerOptions(serializers.HyperlinkedModelSerializerOptions):
""" Options for PostHyperlinkedModelSerializer """
def __init__(self, meta):
super(HyperlinkedModelSerializerOptions, self).__init__(meta)
self.postonly_fields = getattr(meta, 'postonly_fields', ())
class HyperlinkedModelSerializer(HyperlinkedModelSerializer):
_options_class = HyperlinkedModelSerializerOptions
def to_native(self, obj):
""" Serialize objects -> primitives. """
ret = self._dict_class()
ret.fields = {}
for field_name, field in self.fields.items():
# Ignore all postonly_fields fron serialization
if field_name in self.opts.postonly_fields:
continue
field.initialize(parent=self, field_name=field_name)
key = self.get_field_key(field_name)
value = field.field_to_native(obj, field_name)
ret[key] = value
ret.fields[key] = field
return ret
def restore_object(self, attrs, instance=None):
model_attrs, post_attrs = {}, {}
for attr, value in attrs.iteritems():
if attr in self.opts.postonly_fields:
post_attrs[attr] = value
else:
model_attrs[attr] = value
obj = super(HyperlinkedModelSerializer, self).restore_object(model_attrs, instance)
# Method to process ignored postonly_fields
self.process_postonly_fields(obj, post_attrs)
return obj
def process_postonly_fields(self, obj, post_attrs):
""" Placeholder method for processing data sent in POST. """
pass
class MultiSelectField(serializers.ChoiceField): class MultiSelectField(serializers.ChoiceField):
widget = widgets.CheckboxSelectMultiple widget = widgets.CheckboxSelectMultiple

View file

@ -2,6 +2,7 @@ from django.forms import widgets
from django.utils.translation import ugettext, ugettext_lazy as _ from django.utils.translation import ugettext, ugettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from orchestra.api.serializers import HyperlinkedModelSerializer
from orchestra.apps.accounts.serializers import AccountSerializerMixin from orchestra.apps.accounts.serializers import AccountSerializerMixin
from orchestra.core.validators import validate_password from orchestra.core.validators import validate_password
@ -17,12 +18,14 @@ class RelatedDatabaseUserSerializer(serializers.HyperlinkedModelSerializer):
return DatabaseUser.objects.get(username=data['username']) return DatabaseUser.objects.get(username=data['username'])
class DatabaseSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer): class DatabaseSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
users = RelatedDatabaseUserSerializer(many=True, allow_add_remove=True) users = RelatedDatabaseUserSerializer(many=True, allow_add_remove=True)
# TODO clean user.type = db.type
class Meta: class Meta:
model = Database model = Database
fields = ('url', 'name', 'type', 'users') fields = ('url', 'name', 'type', 'users')
postonly_fields = ('name', 'type')
class RelatedDatabaseSerializer(serializers.HyperlinkedModelSerializer): class RelatedDatabaseSerializer(serializers.HyperlinkedModelSerializer):
@ -34,15 +37,17 @@ class RelatedDatabaseSerializer(serializers.HyperlinkedModelSerializer):
return Database.objects.get(name=data['name']) return Database.objects.get(name=data['name'])
class DatabaseUserSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer): class DatabaseUserSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
password = serializers.CharField(max_length=128, label=_('Password'), password = serializers.CharField(max_length=128, label=_('Password'),
validators=[validate_password], write_only=True, validators=[validate_password], write_only=True,
widget=widgets.PasswordInput) widget=widgets.PasswordInput)
databases = RelatedDatabaseSerializer(many=True, allow_add_remove=True, required=False) databases = RelatedDatabaseSerializer(many=True, allow_add_remove=True, required=False)
# TODO clean user.type = db.type
class Meta: class Meta:
model = DatabaseUser model = DatabaseUser
fields = ('url', 'username', 'password', 'type', 'databases') fields = ('url', 'username', 'password', 'type', 'databases')
postonly_fields = ('username', 'type')
def save_object(self, obj, **kwargs): def save_object(self, obj, **kwargs):
# FIXME this method will be called when saving nested serializers :( # FIXME this method will be called when saving nested serializers :(

View file

@ -166,6 +166,8 @@ class MySQLBackendMixin(object):
self.assertEqual('', sshrun(self.MASTER_SERVER, self.assertEqual('', sshrun(self.MASTER_SERVER,
"""mysql mysql -e 'SELECT * FROM user WHERE user="%(username)s";'""" % context, display=False).stdout) """mysql mysql -e 'SELECT * FROM user WHERE user="%(username)s";'""" % context, display=False).stdout)
# TODO remove used from database
class RESTDatabaseMixin(DatabaseTestMixin): class RESTDatabaseMixin(DatabaseTestMixin):
def setUp(self): def setUp(self):

View file

@ -1,6 +1,7 @@
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from rest_framework import serializers from rest_framework import serializers
from orchestra.api.serializers import HyperlinkedModelSerializer
from orchestra.apps.accounts.serializers import AccountSerializerMixin from orchestra.apps.accounts.serializers import AccountSerializerMixin
from .helpers import domain_for_validation from .helpers import domain_for_validation
@ -17,13 +18,14 @@ class RecordSerializer(serializers.ModelSerializer):
return data.get('value') return data.get('value')
class DomainSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer): class DomainSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
""" Validates if this zone generates a correct zone file """ """ Validates if this zone generates a correct zone file """
records = RecordSerializer(required=False, many=True, allow_add_remove=True) records = RecordSerializer(required=False, many=True, allow_add_remove=True)
class Meta: class Meta:
model = Domain model = Domain
fields = ('url', 'id', 'name', 'records') fields = ('url', 'name', 'records')
postonly_fields = ('name',)
def full_clean(self, instance): def full_clean(self, instance):
""" Checks if everything is consistent """ """ Checks if everything is consistent """

View file

@ -2,15 +2,14 @@ from django.forms import widgets
from django.utils.translation import ugettext, ugettext_lazy as _ from django.utils.translation import ugettext, ugettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from orchestra.api.serializers import HyperlinkedModelSerializer
from orchestra.apps.accounts.serializers import AccountSerializerMixin from orchestra.apps.accounts.serializers import AccountSerializerMixin
from orchestra.core.validators import validate_password from orchestra.core.validators import validate_password
from .models import List from .models import List
# TODO create PasswordSerializerMixin class ListSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
class ListSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
password = serializers.CharField(max_length=128, label=_('Password'), password = serializers.CharField(max_length=128, label=_('Password'),
validators=[validate_password], write_only=True, required=False, validators=[validate_password], write_only=True, required=False,
widget=widgets.PasswordInput) widget=widgets.PasswordInput)
@ -18,6 +17,7 @@ class ListSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSeriali
class Meta: class Meta:
model = List model = List
fields = ('url', 'name', 'address_name', 'address_domain', 'admin_email') fields = ('url', 'name', 'address_name', 'address_domain', 'admin_email')
postonly_fields = ('name',)
def validate_password(self, attrs, source): def validate_password(self, attrs, source):
""" POST only password """ """ POST only password """

View file

@ -2,13 +2,14 @@ from django.forms import widgets
from django.utils.translation import ugettext, ugettext_lazy as _ from django.utils.translation import ugettext, ugettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from orchestra.api.serializers import HyperlinkedModelSerializer
from orchestra.apps.accounts.serializers import AccountSerializerMixin from orchestra.apps.accounts.serializers import AccountSerializerMixin
from orchestra.core.validators import validate_password from orchestra.core.validators import validate_password
from .models import Mailbox, Address from .models import Mailbox, Address
class MailboxSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer): class MailboxSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
password = serializers.CharField(max_length=128, label=_('Password'), password = serializers.CharField(max_length=128, label=_('Password'),
validators=[validate_password], write_only=True, required=False, validators=[validate_password], write_only=True, required=False,
widget=widgets.PasswordInput) widget=widgets.PasswordInput)
@ -18,6 +19,7 @@ class MailboxSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSeri
fields = ( fields = (
'url', 'name', 'password', 'filtering', 'custom_filtering', 'addresses', 'is_active' 'url', 'name', 'password', 'filtering', 'custom_filtering', 'addresses', 'is_active'
) )
postonly_fields = ('name',)
def validate_password(self, attrs, source): def validate_password(self, attrs, source):
""" POST only password """ """ POST only password """
@ -54,4 +56,3 @@ class AddressSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSeri
if not attrs['mailboxes'] and not attrs['forward']: if not attrs['mailboxes'] and not attrs['forward']:
raise serializers.ValidationError("mailboxes or forward addresses should be provided") raise serializers.ValidationError("mailboxes or forward addresses should be provided")
return attrs return attrs

View file

@ -3,6 +3,7 @@ from django.forms import widgets
from django.utils.translation import ugettext, ugettext_lazy as _ from django.utils.translation import ugettext, ugettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from orchestra.api.serializers import HyperlinkedModelSerializer
from orchestra.apps.accounts.serializers import AccountSerializerMixin from orchestra.apps.accounts.serializers import AccountSerializerMixin
from orchestra.core.validators import validate_password from orchestra.core.validators import validate_password
@ -18,7 +19,7 @@ class GroupSerializer(serializers.ModelSerializer):
return SystemUser.objects.get(username=data['username']) return SystemUser.objects.get(username=data['username'])
class SystemUserSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer): class SystemUserSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
password = serializers.CharField(max_length=128, label=_('Password'), password = serializers.CharField(max_length=128, label=_('Password'),
validators=[validate_password], write_only=True, required=False, validators=[validate_password], write_only=True, required=False,
widget=widgets.PasswordInput) widget=widgets.PasswordInput)
@ -29,6 +30,7 @@ class SystemUserSerializer(AccountSerializerMixin, serializers.HyperlinkedModelS
fields = ( fields = (
'url', 'username', 'password', 'home', 'shell', 'groups', 'is_active', 'url', 'username', 'password', 'home', 'shell', 'groups', 'is_active',
) )
postonly_fields = ('username',)
def validate_password(self, attrs, source): def validate_password(self, attrs, source):
""" POST only password """ """ POST only password """

View file

@ -1,14 +1,16 @@
from rest_framework import serializers from rest_framework import serializers
from orchestra.api.fields import OptionField from orchestra.api.fields import OptionField
from orchestra.api.serializers import HyperlinkedModelSerializer
from orchestra.apps.accounts.serializers import AccountSerializerMixin from orchestra.apps.accounts.serializers import AccountSerializerMixin
from .models import WebApp from .models import WebApp
class WebAppSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer): class WebAppSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
options = OptionField(required=False) options = OptionField(required=False)
class Meta: class Meta:
model = WebApp model = WebApp
fields = ('url', 'name', 'type', 'options') fields = ('url', 'name', 'type', 'options')
postonly_fields = ('name', 'type')

View file

@ -1,6 +1,7 @@
from rest_framework import serializers from rest_framework import serializers
from orchestra.api.fields import OptionField from orchestra.api.fields import OptionField
from orchestra.api.serializers import HyperlinkedModelSerializer
from orchestra.apps.accounts.serializers import AccountSerializerMixin from orchestra.apps.accounts.serializers import AccountSerializerMixin
from .models import Website, Content from .models import Website, Content
@ -15,7 +16,7 @@ class ContentSerializer(serializers.HyperlinkedModelSerializer):
return '%s-%s' % (data.get('website'), data.get('path')) return '%s-%s' % (data.get('website'), data.get('path'))
class WebsiteSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer): class WebsiteSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
contents = ContentSerializer(required=False, many=True, allow_add_remove=True, contents = ContentSerializer(required=False, many=True, allow_add_remove=True,
source='content_set') source='content_set')
options = OptionField(required=False) options = OptionField(required=False)
@ -23,3 +24,4 @@ class WebsiteSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSeri
class Meta: class Meta:
model = Website model = Website
fields = ('url', 'name', 'port', 'domains', 'is_active', 'contents', 'options') fields = ('url', 'name', 'port', 'domains', 'is_active', 'contents', 'options')
postonly_fileds = ('name',)