Upgraded to DRF3
This commit is contained in:
parent
fbb3c6ab03
commit
1a78317028
2
TODO.md
2
TODO.md
|
@ -285,4 +285,4 @@ https://code.djangoproject.com/ticket/24576
|
||||||
# replace multichoicefield and jsonfield by ArrayField, HStoreField
|
# replace multichoicefield and jsonfield by ArrayField, HStoreField
|
||||||
# Amend lines???
|
# Amend lines???
|
||||||
|
|
||||||
|
# Add icon on select contact view
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import detail_route
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from .serializers import SetPasswordSerializer
|
from .serializers import SetPasswordSerializer
|
||||||
|
|
||||||
|
|
||||||
class SetPasswordApiMixin(object):
|
class SetPasswordApiMixin(object):
|
||||||
@action(serializer_class=SetPasswordSerializer)
|
@detail_route(serializer_class=SetPasswordSerializer)
|
||||||
def set_password(self, request, pk):
|
def set_password(self, request, pk):
|
||||||
obj = self.get_object()
|
obj = self.get_object()
|
||||||
data = request.DATA
|
data = request.DATA
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
import json
|
|
||||||
|
|
||||||
from rest_framework import serializers, exceptions
|
|
||||||
|
|
||||||
|
|
||||||
class OptionField(serializers.WritableField):
|
|
||||||
"""
|
|
||||||
Dict-like representation of a OptionField
|
|
||||||
A bit hacky, objects get deleted on from_native method and Serializer will
|
|
||||||
need a custom override of restore_object method.
|
|
||||||
"""
|
|
||||||
def to_native(self, value):
|
|
||||||
""" dict-like representation of a Property Model"""
|
|
||||||
return dict((prop.name, prop.value) for prop in value.all())
|
|
||||||
|
|
||||||
def from_native(self, value):
|
|
||||||
""" Convert a dict-like representation back to a WebOptionField """
|
|
||||||
parent = self.parent
|
|
||||||
related_manager = getattr(parent.object, self.source or 'options', False)
|
|
||||||
properties = serializers.RelationsList()
|
|
||||||
if value:
|
|
||||||
model = getattr(parent.opts.model, self.source or 'options').related.model
|
|
||||||
if isinstance(value, str):
|
|
||||||
try:
|
|
||||||
value = json.loads(value)
|
|
||||||
except:
|
|
||||||
raise exceptions.ParseError("Malformed property: %s" % str(value))
|
|
||||||
if not related_manager:
|
|
||||||
# POST (new parent object)
|
|
||||||
return [model(name=n, value=v) for n,v in value.items()]
|
|
||||||
# PUT
|
|
||||||
to_save = []
|
|
||||||
for (name, value) in value.items():
|
|
||||||
try:
|
|
||||||
# Update existing property
|
|
||||||
prop = related_manager.get(name=name)
|
|
||||||
except model.DoesNotExist:
|
|
||||||
# Create a new one
|
|
||||||
prop = model(name=name, value=value)
|
|
||||||
else:
|
|
||||||
prop.value = value
|
|
||||||
to_save.append(prop.pk)
|
|
||||||
properties.append(prop)
|
|
||||||
|
|
||||||
# Discart old values
|
|
||||||
if related_manager:
|
|
||||||
properties._deleted = [] # Redefine class attribute
|
|
||||||
for obj in related_manager.all():
|
|
||||||
if not value or obj.pk not in to_save:
|
|
||||||
properties._deleted.append(obj)
|
|
||||||
return properties
|
|
|
@ -3,7 +3,7 @@ from rest_framework.reverse import reverse
|
||||||
|
|
||||||
|
|
||||||
def link_wrap(view, view_names):
|
def link_wrap(view, view_names):
|
||||||
def wrapper(self, request, view=view, *args, **kwargs):
|
def wrapper(self, request, *args, **kwargs):
|
||||||
""" wrapper function that inserts HTTP links on view """
|
""" wrapper function that inserts HTTP links on view """
|
||||||
links = []
|
links = []
|
||||||
for name in view_names:
|
for name in view_names:
|
||||||
|
|
|
@ -12,37 +12,42 @@ from .helpers import insert_links
|
||||||
|
|
||||||
|
|
||||||
class LogApiMixin(object):
|
class LogApiMixin(object):
|
||||||
def post(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
from django.contrib.admin.models import ADDITION
|
from django.contrib.admin.models import ADDITION
|
||||||
response = super(LogApiMixin, self).post(request, *args, **kwargs)
|
response = super(LogApiMixin, self).create(request, *args, **kwargs)
|
||||||
message = _('Added.')
|
message = _('Added.')
|
||||||
self.log_addition(request, message, ADDITION)
|
self.log(request, message, ADDITION, instance=self.serializer.instance)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def put(self, request, *args, **kwargs):
|
def perform_create(self, serializer):
|
||||||
|
""" stores serializer for accessing instance on create() """
|
||||||
|
super(LogApiMixin, self).perform_create(serializer)
|
||||||
|
self.serializer = serializer
|
||||||
|
|
||||||
|
def update(self, request, *args, **kwargs):
|
||||||
from django.contrib.admin.models import CHANGE
|
from django.contrib.admin.models import CHANGE
|
||||||
response = super(LogApiMixin, self).put(request, *args, **kwargs)
|
response = super(LogApiMixin, self).update(request, *args, **kwargs)
|
||||||
message = _('Changed')
|
message = _('Changed data')
|
||||||
self.log(request, message, CHANGE)
|
self.log(request, message, CHANGE)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def patch(self, request, *args, **kwargs):
|
def partial_update(self, request, *args, **kwargs):
|
||||||
from django.contrib.admin.models import CHANGE
|
from django.contrib.admin.models import CHANGE
|
||||||
response = super(LogApiMixin, self).put(request, *args, **kwargs)
|
response = super(LogApiMixin, self).partial_update(request, *args, **kwargs)
|
||||||
message = _('Changed %s') % str(response.data)
|
message = _('Changed %s') % str(response.data)
|
||||||
self.log(request, message, CHANGE)
|
self.log(request, message, CHANGE)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def delete(self, request, *args, **kwargs):
|
def destroy(self, request, *args, **kwargs):
|
||||||
from django.contrib.admin.models import DELETION
|
from django.contrib.admin.models import DELETION
|
||||||
message = _('Deleted')
|
message = _('Deleted')
|
||||||
self.log(request, message, DELETION)
|
self.log(request, message, DELETION)
|
||||||
response = super(LogApiMixin, self).put(request, *args, **kwargs)
|
response = super(LogApiMixin, self).destroy(request, *args, **kwargs)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def log(self, request, message, action):
|
def log(self, request, message, action, instance=None):
|
||||||
from django.contrib.admin.models import LogEntry
|
from django.contrib.admin.models import LogEntry
|
||||||
instance = self.get_object()
|
instance = instance or self.get_object()
|
||||||
LogEntry.objects.log_action(
|
LogEntry.objects.log_action(
|
||||||
user_id=request.user.pk,
|
user_id=request.user.pk,
|
||||||
content_type_id=get_content_type_for_model(instance).pk,
|
content_type_id=get_content_type_for_model(instance).pk,
|
||||||
|
@ -69,7 +74,7 @@ class LinkHeaderRouter(DefaultRouter):
|
||||||
|
|
||||||
def get_viewset(self, prefix_or_model):
|
def get_viewset(self, prefix_or_model):
|
||||||
for _prefix, viewset, __ in self.registry:
|
for _prefix, viewset, __ in self.registry:
|
||||||
if _prefix == prefix_or_model or viewset.model == prefix_or_model:
|
if _prefix == prefix_or_model or viewset.queryset.model == prefix_or_model:
|
||||||
return viewset
|
return viewset
|
||||||
msg = "%s does not have a regiestered viewset" % prefix_or_model
|
msg = "%s does not have a regiestered viewset" % prefix_or_model
|
||||||
raise KeyError(msg)
|
raise KeyError(msg)
|
||||||
|
@ -80,7 +85,7 @@ class LinkHeaderRouter(DefaultRouter):
|
||||||
# setattr(viewset, 'inserted', getattr(viewset, 'inserted', []))
|
# setattr(viewset, 'inserted', getattr(viewset, 'inserted', []))
|
||||||
if viewset.serializer_class is None:
|
if viewset.serializer_class is None:
|
||||||
viewset.serializer_class = viewset().get_serializer_class()
|
viewset.serializer_class = viewset().get_serializer_class()
|
||||||
viewset.serializer_class.base_fields.update({name: field(**kwargs)})
|
viewset.serializer_class._declared_fields.update({name: field(**kwargs)})
|
||||||
# if not name in viewset.inserted:
|
# if not name in viewset.inserted:
|
||||||
viewset.serializer_class.Meta.fields += (name,)
|
viewset.serializer_class.Meta.fields += (name,)
|
||||||
# viewset.inserted.append(name)
|
# viewset.inserted.append(name)
|
||||||
|
|
|
@ -38,7 +38,7 @@ class APIRoot(views.APIView):
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
url = reverse(url_name, request=request, format=format, kwargs=kwargs)
|
url = reverse(url_name, request=request, format=format, kwargs=kwargs)
|
||||||
links.append('<%s>; rel="%s"' % (url, url_name))
|
links.append('<%s>; rel="%s"' % (url, url_name))
|
||||||
model = viewset.model
|
model = viewset.queryset.model
|
||||||
group = None
|
group = None
|
||||||
if model in services:
|
if model in services:
|
||||||
group = 'services'
|
group = 'services'
|
||||||
|
@ -61,10 +61,10 @@ class APIRoot(views.APIView):
|
||||||
})
|
})
|
||||||
return Response(body, headers=headers)
|
return Response(body, headers=headers)
|
||||||
|
|
||||||
def metadata(self, request):
|
def options(self, request):
|
||||||
ret = super(APIRoot, self).metadata(request)
|
metadata = super(APIRoot, self).options(request)
|
||||||
ret['settings'] = {
|
metadata.data['settings'] = {
|
||||||
name.lower(): getattr(settings, name, None)
|
name.lower(): getattr(settings, name, None)
|
||||||
for name in self.names
|
for name in self.names
|
||||||
}
|
}
|
||||||
return ret
|
return metadata
|
||||||
|
|
|
@ -7,47 +7,90 @@ from ..core.validators import validate_password
|
||||||
|
|
||||||
class SetPasswordSerializer(serializers.Serializer):
|
class SetPasswordSerializer(serializers.Serializer):
|
||||||
password = serializers.CharField(max_length=128, label=_('Password'),
|
password = serializers.CharField(max_length=128, label=_('Password'),
|
||||||
widget=widgets.PasswordInput, validators=[validate_password])
|
style={'widget': widgets.PasswordInput}, validators=[validate_password])
|
||||||
|
|
||||||
|
|
||||||
class HyperlinkedModelSerializerOptions(serializers.HyperlinkedModelSerializerOptions):
|
#class HyperlinkedModelSerializerOptions(serializers.HyperlinkedModelSerializerOptions):
|
||||||
def __init__(self, meta):
|
# def __init__(self, meta):
|
||||||
super(HyperlinkedModelSerializerOptions, self).__init__(meta)
|
# super(HyperlinkedModelSerializerOptions, self).__init__(meta)
|
||||||
self.postonly_fields = getattr(meta, 'postonly_fields', ())
|
# self.postonly_fields = getattr(meta, 'postonly_fields', ())
|
||||||
|
|
||||||
|
|
||||||
class HyperlinkedModelSerializer(serializers.HyperlinkedModelSerializer):
|
class HyperlinkedModelSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
""" support for postonly_fields, fields whose value can only be set on post """
|
""" support for postonly_fields, fields whose value can only be set on post """
|
||||||
_options_class = HyperlinkedModelSerializerOptions
|
# _options_class = HyperlinkedModelSerializerOptions
|
||||||
|
|
||||||
def restore_object(self, attrs, instance=None):
|
def validate(self, attrs):
|
||||||
|
""" calls model.clean() """
|
||||||
|
attrs = super(HyperlinkedModelSerializer, self).validate(attrs)
|
||||||
|
instance = self.Meta.model(**attrs)
|
||||||
|
instance.clean()
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
# TODO raise validationError instead of silently removing fields
|
||||||
|
def update(self, instance, validated_data):
|
||||||
""" removes postonly_fields from attrs when not posting """
|
""" removes postonly_fields from attrs when not posting """
|
||||||
model_attrs = dict(**attrs)
|
model_attrs = dict(**validated_data)
|
||||||
if instance is not None:
|
if instance is not None:
|
||||||
for attr, value in attrs.items():
|
for attr, value in validated_data.items():
|
||||||
if attr in self.opts.postonly_fields:
|
if attr in self.Meta.postonly_fields:
|
||||||
model_attrs.pop(attr)
|
model_attrs.pop(attr)
|
||||||
return super(HyperlinkedModelSerializer, self).restore_object(model_attrs, instance)
|
return super(HyperlinkedModelSerializer, self).update(instance, model_attrs)
|
||||||
|
|
||||||
|
|
||||||
class MultiSelectField(serializers.ChoiceField):
|
class SetPasswordHyperlinkedSerializer(HyperlinkedModelSerializer):
|
||||||
widget = widgets.CheckboxSelectMultiple
|
password = serializers.CharField(max_length=128, label=_('Password'),
|
||||||
|
validators=[validate_password], write_only=True, required=False,
|
||||||
|
style={'widget': widgets.PasswordInput})
|
||||||
|
|
||||||
def field_from_native(self, data, files, field_name, into):
|
def validate_password(self, attrs, source):
|
||||||
""" convert multiselect data into comma separated string """
|
""" POST only password """
|
||||||
if field_name in data:
|
if self.instance:
|
||||||
data = data.copy()
|
if 'password' in attrs:
|
||||||
try:
|
raise serializers.ValidationError(_("Can not set password"))
|
||||||
# data is a querydict when using forms
|
elif 'password' not in attrs:
|
||||||
data[field_name] = ','.join(data.getlist(field_name))
|
raise serializers.ValidationError(_("Password required"))
|
||||||
except AttributeError:
|
return attrs
|
||||||
data[field_name] = ','.join(data[field_name])
|
|
||||||
return super(MultiSelectField, self).field_from_native(data, files, field_name, into)
|
|
||||||
|
|
||||||
def valid_value(self, value):
|
def validate(self, attrs):
|
||||||
""" checks for each item if is a valid value """
|
""" remove password in case is not a real model field """
|
||||||
for val in value.split(','):
|
try:
|
||||||
valid = super(MultiSelectField, self).valid_value(val)
|
self.Meta.model._meta.get_field_by_name('password')
|
||||||
if not valid:
|
except models.FieldDoesNotExist:
|
||||||
return False
|
pass
|
||||||
return True
|
else:
|
||||||
|
password = attrs.pop('password', None)
|
||||||
|
attrs = super(SetPasswordSerializer, self).validate()
|
||||||
|
if password is not None:
|
||||||
|
attrs['password'] = password
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
password = validated_data.pop('password')
|
||||||
|
instance = self.Meta.model(**validated_data)
|
||||||
|
instance.set_password(password)
|
||||||
|
instance.save()
|
||||||
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
#class MultiSelectField(serializers.ChoiceField):
|
||||||
|
# widget = widgets.CheckboxSelectMultiple
|
||||||
|
#
|
||||||
|
# def field_from_native(self, data, files, field_name, into):
|
||||||
|
# """ convert multiselect data into comma separated string """
|
||||||
|
# if field_name in data:
|
||||||
|
# data = data.copy()
|
||||||
|
# try:
|
||||||
|
# # data is a querydict when using forms
|
||||||
|
# data[field_name] = ','.join(data.getlist(field_name))
|
||||||
|
# except AttributeError:
|
||||||
|
# data[field_name] = ','.join(data[field_name])
|
||||||
|
# return super(MultiSelectField, self).field_from_native(data, files, field_name, into)
|
||||||
|
#
|
||||||
|
# def valid_value(self, value):
|
||||||
|
# """ checks for each item if is a valid value """
|
||||||
|
# for val in value.split(','):
|
||||||
|
# valid = super(MultiSelectField, self).valid_value(val)
|
||||||
|
# if not valid:
|
||||||
|
# return False
|
||||||
|
# return True
|
||||||
|
|
|
@ -147,11 +147,11 @@ function install_requirements () {
|
||||||
kombu==3.0.23 \
|
kombu==3.0.23 \
|
||||||
billiard==3.3.0.18 \
|
billiard==3.3.0.18 \
|
||||||
Markdown==2.4 \
|
Markdown==2.4 \
|
||||||
djangorestframework==2.4.4 \
|
djangorestframework==3.1.1 \
|
||||||
paramiko==1.15.1 \
|
paramiko==1.15.1 \
|
||||||
ecdsa==0.11 \
|
ecdsa==0.11 \
|
||||||
Pygments==1.6 \
|
Pygments==1.6 \
|
||||||
django-filter==0.7 \
|
django-filter==0.9.2 \
|
||||||
passlib==1.6.2 \
|
passlib==1.6.2 \
|
||||||
jsonfield==0.9.22 \
|
jsonfield==0.9.22 \
|
||||||
lxml==3.3.5 \
|
lxml==3.3.5 \
|
||||||
|
|
|
@ -14,7 +14,7 @@ class AccountApiMixin(object):
|
||||||
|
|
||||||
|
|
||||||
class AccountViewSet(LogApiMixin, SetPasswordApiMixin, viewsets.ModelViewSet):
|
class AccountViewSet(LogApiMixin, SetPasswordApiMixin, viewsets.ModelViewSet):
|
||||||
model = Account
|
queryset = Account.objects.all()
|
||||||
serializer_class = AccountSerializer
|
serializer_class = AccountSerializer
|
||||||
singleton_pk = lambda _,request: request.user.pk
|
singleton_pk = lambda _,request: request.user.pk
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,6 @@ class AccountSerializerMixin(object):
|
||||||
if request:
|
if request:
|
||||||
self.account = request.user
|
self.account = request.user
|
||||||
|
|
||||||
def save_object(self, obj, **kwargs):
|
def create(self, validated_data):
|
||||||
obj.account = self.account
|
validated_data['account'] = self.account
|
||||||
super(AccountSerializerMixin, self).save_object(obj, **kwargs)
|
return super(AccountSerializerMixin, self).create(validated_data)
|
||||||
|
|
|
@ -12,7 +12,7 @@ from .serializers import BillSerializer
|
||||||
|
|
||||||
|
|
||||||
class BillViewSet(LogApiMixin, AccountApiMixin, viewsets.ModelViewSet):
|
class BillViewSet(LogApiMixin, AccountApiMixin, viewsets.ModelViewSet):
|
||||||
model = Bill
|
queryset = Bill.objects.all()
|
||||||
serializer_class = BillSerializer
|
serializer_class = BillSerializer
|
||||||
|
|
||||||
@detail_route(methods=['get'])
|
@detail_route(methods=['get'])
|
||||||
|
|
|
@ -8,7 +8,7 @@ from .serializers import ContactSerializer
|
||||||
|
|
||||||
|
|
||||||
class ContactViewSet(LogApiMixin, AccountApiMixin, viewsets.ModelViewSet):
|
class ContactViewSet(LogApiMixin, AccountApiMixin, viewsets.ModelViewSet):
|
||||||
model = Contact
|
queryset = Contact.objects.all()
|
||||||
serializer_class = ContactSerializer
|
serializer_class = ContactSerializer
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ class EmailUsageListFilter(SimpleListFilter):
|
||||||
parameter_name = 'email_usages'
|
parameter_name = 'email_usages'
|
||||||
|
|
||||||
def lookups(self, request, model_admin):
|
def lookups(self, request, model_admin):
|
||||||
return Contact.email_usage.field.choices
|
return Contact.EMAIL_USAGES
|
||||||
|
|
||||||
def queryset(self, request, queryset):
|
def queryset(self, request, queryset):
|
||||||
value = self.value()
|
value = self.value()
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,13 +1,14 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from orchestra.api.serializers import MultiSelectField
|
#from orchestra.api.serializers import MultiSelectField
|
||||||
from orchestra.contrib.accounts.serializers import AccountSerializerMixin
|
from orchestra.contrib.accounts.serializers import AccountSerializerMixin
|
||||||
|
|
||||||
from .models import Contact
|
from .models import Contact
|
||||||
|
|
||||||
|
|
||||||
class ContactSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
|
class ContactSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
|
||||||
email_usage = MultiSelectField(choices=Contact.EMAIL_USAGES)
|
email_usage = serializers.MultipleChoiceField(choices=Contact.EMAIL_USAGES)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Contact
|
model = Contact
|
||||||
fields = (
|
fields = (
|
||||||
|
|
|
@ -8,13 +8,13 @@ from .serializers import DatabaseSerializer, DatabaseUserSerializer
|
||||||
|
|
||||||
|
|
||||||
class DatabaseViewSet(LogApiMixin, AccountApiMixin, viewsets.ModelViewSet):
|
class DatabaseViewSet(LogApiMixin, AccountApiMixin, viewsets.ModelViewSet):
|
||||||
model = Database
|
queryset = Database.objects.all()
|
||||||
serializer_class = DatabaseSerializer
|
serializer_class = DatabaseSerializer
|
||||||
filter_fields = ('name',)
|
filter_fields = ('name',)
|
||||||
|
|
||||||
|
|
||||||
class DatabaseUserViewSet(LogApiMixin, AccountApiMixin, SetPasswordApiMixin, viewsets.ModelViewSet):
|
class DatabaseUserViewSet(LogApiMixin, AccountApiMixin, SetPasswordApiMixin, viewsets.ModelViewSet):
|
||||||
model = DatabaseUser
|
queryset = DatabaseUser.objects.all()
|
||||||
serializer_class = DatabaseUserSerializer
|
serializer_class = DatabaseUserSerializer
|
||||||
filter_fields = ('username',)
|
filter_fields = ('username',)
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,8 @@ from django.shortcuts import get_object_or_404
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from orchestra.api.serializers import HyperlinkedModelSerializer
|
from orchestra.api.serializers import HyperlinkedModelSerializer, SetPasswordHyperlinkedSerializer
|
||||||
from orchestra.contrib.accounts.serializers import AccountSerializerMixin
|
from orchestra.contrib.accounts.serializers import AccountSerializerMixin
|
||||||
from orchestra.core.validators import validate_password
|
|
||||||
|
|
||||||
from .models import Database, DatabaseUser
|
from .models import Database, DatabaseUser
|
||||||
|
|
||||||
|
@ -21,7 +20,7 @@ class RelatedDatabaseUserSerializer(AccountSerializerMixin, serializers.Hyperlin
|
||||||
|
|
||||||
|
|
||||||
class DatabaseSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
|
class DatabaseSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
|
||||||
users = RelatedDatabaseUserSerializer(many=True, allow_add_remove=True)
|
users = RelatedDatabaseUserSerializer(many=True) #allow_add_remove=True
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Database
|
model = Database
|
||||||
|
@ -29,6 +28,7 @@ class DatabaseSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
|
||||||
postonly_fields = ('name', 'type')
|
postonly_fields = ('name', 'type')
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
|
attrs = super(DatabaseSerializer, self).validate(attrs)
|
||||||
for user in attrs['users']:
|
for user in attrs['users']:
|
||||||
if user.type != attrs['type']:
|
if user.type != attrs['type']:
|
||||||
raise serializers.ValidationError("User type must be" % attrs['type'])
|
raise serializers.ValidationError("User type must be" % attrs['type'])
|
||||||
|
@ -45,25 +45,17 @@ class RelatedDatabaseSerializer(AccountSerializerMixin, serializers.HyperlinkedM
|
||||||
return get_object_or_404(queryset, name=data['name'])
|
return get_object_or_404(queryset, name=data['name'])
|
||||||
|
|
||||||
|
|
||||||
class DatabaseUserSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
|
class DatabaseUserSerializer(AccountSerializerMixin, SetPasswordHyperlinkedSerializer):
|
||||||
password = serializers.CharField(max_length=128, label=_('Password'),
|
databases = RelatedDatabaseSerializer(many=True, required=False) # allow_add_remove=True
|
||||||
validators=[validate_password], write_only=True,
|
|
||||||
widget=widgets.PasswordInput)
|
|
||||||
databases = RelatedDatabaseSerializer(many=True, allow_add_remove=True, required=False)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = DatabaseUser
|
model = DatabaseUser
|
||||||
fields = ('url', 'username', 'password', 'type', 'databases')
|
fields = ('url', 'username', 'password', 'type', 'databases')
|
||||||
postonly_fields = ('username', 'type')
|
postonly_fields = ('username', 'type', 'password')
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
|
attrs = super(DatabaseUserSerializer, self).validate(attrs)
|
||||||
for database in attrs.get('databases', []):
|
for database in attrs.get('databases', []):
|
||||||
if database.type != attrs['type']:
|
if database.type != attrs['type']:
|
||||||
raise serializers.ValidationError("Database type must be" % attrs['type'])
|
raise serializers.ValidationError("Database type must be" % attrs['type'])
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
def save_object(self, obj, **kwargs):
|
|
||||||
# FIXME this method will be called when saving nested serializers :(
|
|
||||||
if not obj.pk:
|
|
||||||
obj.set_password(obj.password)
|
|
||||||
super(DatabaseUserSerializer, self).save_object(obj, **kwargs)
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
from rest_framework.decorators import link
|
from rest_framework.decorators import detail_route
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from orchestra.api import router
|
from orchestra.api import router
|
||||||
|
@ -11,28 +11,28 @@ from .serializers import DomainSerializer
|
||||||
|
|
||||||
|
|
||||||
class DomainViewSet(AccountApiMixin, viewsets.ModelViewSet):
|
class DomainViewSet(AccountApiMixin, viewsets.ModelViewSet):
|
||||||
model = Domain
|
|
||||||
serializer_class = DomainSerializer
|
serializer_class = DomainSerializer
|
||||||
filter_fields = ('name',)
|
filter_fields = ('name',)
|
||||||
|
queryset = Domain.objects.all()
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
qs = super(DomainViewSet, self).get_queryset()
|
qs = super(DomainViewSet, self).get_queryset()
|
||||||
return qs.prefetch_related('records')
|
return qs.prefetch_related('records')
|
||||||
|
|
||||||
@link()
|
@detail_route()
|
||||||
def view_zone(self, request, pk=None):
|
def view_zone(self, request, pk=None):
|
||||||
domain = self.get_object()
|
domain = self.get_object()
|
||||||
return Response({
|
return Response({
|
||||||
'zone': domain.render_zone()
|
'zone': domain.render_zone()
|
||||||
})
|
})
|
||||||
|
|
||||||
def metadata(self, request):
|
def options(self, request):
|
||||||
ret = super(DomainViewSet, self).metadata(request)
|
metadata = super(DomainViewSet, self).options(request)
|
||||||
names = ['DOMAINS_DEFAULT_A', 'DOMAINS_DEFAULT_MX', 'DOMAINS_DEFAULT_NS']
|
names = ['DOMAINS_DEFAULT_A', 'DOMAINS_DEFAULT_MX', 'DOMAINS_DEFAULT_NS']
|
||||||
ret['settings'] = {
|
metadata.data['settings'] = {
|
||||||
name.lower(): getattr(settings, name, None) for name in names
|
name.lower(): getattr(settings, name, None) for name in names
|
||||||
}
|
}
|
||||||
return ret
|
return metadata
|
||||||
|
|
||||||
|
|
||||||
router.register(r'domains', DomainViewSet)
|
router.register(r'domains', DomainViewSet)
|
||||||
|
|
|
@ -21,7 +21,7 @@ class RecordSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class DomainSerializer(AccountSerializerMixin, 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
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from rest_framework import viewsets, mixins
|
from rest_framework import viewsets, mixins
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import detail_route
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from orchestra.api import router, LogApiMixin
|
from orchestra.api import router, LogApiMixin
|
||||||
|
@ -10,16 +10,16 @@ from .serializers import TicketSerializer, QueueSerializer
|
||||||
|
|
||||||
|
|
||||||
class TicketViewSet(LogApiMixin, viewsets.ModelViewSet):
|
class TicketViewSet(LogApiMixin, viewsets.ModelViewSet):
|
||||||
model = Ticket
|
queryset = Ticket.objects.all()
|
||||||
serializer_class = TicketSerializer
|
serializer_class = TicketSerializer
|
||||||
|
|
||||||
@action()
|
@detail_route()
|
||||||
def mark_as_read(self, request, pk=None):
|
def mark_as_read(self, request, pk=None):
|
||||||
ticket = self.get_object()
|
ticket = self.get_object()
|
||||||
ticket.mark_as_read_by(request.user)
|
ticket.mark_as_read_by(request.user)
|
||||||
return Response({'status': 'Ticket marked as read'})
|
return Response({'status': 'Ticket marked as read'})
|
||||||
|
|
||||||
@action()
|
@detail_route()
|
||||||
def mark_as_unread(self, request, pk=None):
|
def mark_as_unread(self, request, pk=None):
|
||||||
ticket = self.get_object()
|
ticket = self.get_object()
|
||||||
ticket.mark_as_unread_by(request.user)
|
ticket.mark_as_unread_by(request.user)
|
||||||
|
@ -36,7 +36,7 @@ class QueueViewSet(LogApiMixin,
|
||||||
mixins.ListModelMixin,
|
mixins.ListModelMixin,
|
||||||
mixins.RetrieveModelMixin,
|
mixins.RetrieveModelMixin,
|
||||||
viewsets.GenericViewSet):
|
viewsets.GenericViewSet):
|
||||||
model = Queue
|
queryset = Queue.objects.all()
|
||||||
serializer_class = QueueSerializer
|
serializer_class = QueueSerializer
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ class MessageSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
class TicketSerializer(serializers.HyperlinkedModelSerializer):
|
class TicketSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
""" Validates if this zone generates a correct zone file """
|
""" Validates if this zone generates a correct zone file """
|
||||||
messages = MessageSerializer(required=False, many=True)
|
messages = MessageSerializer(required=False, many=True)
|
||||||
is_read = serializers.SerializerMethodField('get_is_read')
|
is_read = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Ticket
|
model = Ticket
|
||||||
|
|
|
@ -8,7 +8,7 @@ from .serializers import ListSerializer
|
||||||
|
|
||||||
|
|
||||||
class ListViewSet(LogApiMixin, AccountApiMixin, SetPasswordApiMixin, viewsets.ModelViewSet):
|
class ListViewSet(LogApiMixin, AccountApiMixin, SetPasswordApiMixin, viewsets.ModelViewSet):
|
||||||
model = List
|
queryset = List.objects.all()
|
||||||
serializer_class = ListSerializer
|
serializer_class = ListSerializer
|
||||||
filter_fields = ('name',)
|
filter_fields = ('name',)
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,17 @@ from orchestra.core.validators import validate_name
|
||||||
from . import settings
|
from . import settings
|
||||||
|
|
||||||
|
|
||||||
|
class ListQuerySet(models.QuerySet):
|
||||||
|
def create(self, **kwargs):
|
||||||
|
""" Sets password if provided, all within a single DB operation """
|
||||||
|
password = kwargs.pop('password')
|
||||||
|
instance = self.model(**kwargs)
|
||||||
|
if password:
|
||||||
|
instance.set_password(password)
|
||||||
|
instance.save()
|
||||||
|
return instance
|
||||||
|
|
||||||
|
|
||||||
# TODO address and domain, perhaps allow only domain?
|
# TODO address and domain, perhaps allow only domain?
|
||||||
|
|
||||||
class List(models.Model):
|
class List(models.Model):
|
||||||
|
@ -27,6 +38,8 @@ class List(models.Model):
|
||||||
"Unselect this instead of deleting accounts."))
|
"Unselect this instead of deleting accounts."))
|
||||||
password = None
|
password = None
|
||||||
|
|
||||||
|
objects = ListQuerySet.as_manager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ('address_name', 'address_domain')
|
unique_together = ('address_name', 'address_domain')
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
|
from django.core.validators import RegexValidator
|
||||||
from django.forms import widgets
|
from django.forms import widgets
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from orchestra.api.serializers import HyperlinkedModelSerializer
|
from orchestra.api.serializers import SetPasswordHyperlinkedSerializer
|
||||||
from orchestra.contrib.accounts.serializers import AccountSerializerMixin
|
from orchestra.contrib.accounts.serializers import AccountSerializerMixin
|
||||||
from orchestra.core.validators import validate_password
|
from orchestra.core.validators import validate_password
|
||||||
|
|
||||||
|
@ -20,38 +21,31 @@ class RelatedDomainSerializer(AccountSerializerMixin, serializers.HyperlinkedMod
|
||||||
return get_object_or_404(queryset, name=data['name'])
|
return get_object_or_404(queryset, name=data['name'])
|
||||||
|
|
||||||
|
|
||||||
class ListSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
|
class ListSerializer(AccountSerializerMixin, SetPasswordHyperlinkedSerializer):
|
||||||
password = serializers.CharField(max_length=128, label=_('Password'),
|
password = serializers.CharField(max_length=128, label=_('Password'),
|
||||||
validators=[validate_password], write_only=True, required=False,
|
write_only=True, style={'widget': widgets.PasswordInput},
|
||||||
widget=widgets.PasswordInput)
|
validators=[
|
||||||
|
validate_password,
|
||||||
|
RegexValidator(r'^[^"\'\\]+$',
|
||||||
|
_('Enter a valid password. '
|
||||||
|
'This value may contain any ascii character except for '
|
||||||
|
' \'/"/\\/ characters.'), 'invalid'),
|
||||||
|
])
|
||||||
|
|
||||||
address_domain = RelatedDomainSerializer(required=False)
|
address_domain = RelatedDomainSerializer(required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = List
|
model = List
|
||||||
fields = ('url', 'name', 'address_name', 'address_domain', 'admin_email')
|
fields = ('url', 'name', 'password', 'address_name', 'address_domain', 'admin_email')
|
||||||
postonly_fields = ('name',)
|
postonly_fields = ('name', 'password')
|
||||||
|
|
||||||
def validate_password(self, attrs, source):
|
|
||||||
""" POST only password """
|
|
||||||
if self.object:
|
|
||||||
if 'password' in attrs:
|
|
||||||
raise serializers.ValidationError(_("Can not set password"))
|
|
||||||
elif 'password' not in attrs:
|
|
||||||
raise serializers.ValidationError(_("Password required"))
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
def validate_address_domain(self, attrs, source):
|
def validate_address_domain(self, attrs, source):
|
||||||
address_domain = attrs.get(source)
|
address_domain = attrs.get(source)
|
||||||
address_name = attrs.get('address_name')
|
address_name = attrs.get('address_name')
|
||||||
if self.object:
|
if self.instance:
|
||||||
address_domain = address_domain or self.object.address_domain
|
address_domain = address_domain or self.instance.address_domain
|
||||||
address_name = address_name or self.object.address_name
|
address_name = address_name or self.instance.address_name
|
||||||
if address_name and not address_domain:
|
if address_name and not address_domain:
|
||||||
raise serializers.ValidationError(
|
raise serializers.ValidationError(
|
||||||
_("address_domains should should be provided when providing an addres_name"))
|
_("address_domains should should be provided when providing an addres_name"))
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
def save_object(self, obj, **kwargs):
|
|
||||||
if not obj.pk:
|
|
||||||
obj.set_password(self.init_data.get('password', ''))
|
|
||||||
super(ListSerializer, self).save_object(obj, **kwargs)
|
|
||||||
|
|
|
@ -8,13 +8,13 @@ from .serializers import AddressSerializer, MailboxSerializer
|
||||||
|
|
||||||
|
|
||||||
class AddressViewSet(LogApiMixin, AccountApiMixin, viewsets.ModelViewSet):
|
class AddressViewSet(LogApiMixin, AccountApiMixin, viewsets.ModelViewSet):
|
||||||
model = Address
|
queryset = Address.objects.all()
|
||||||
serializer_class = AddressSerializer
|
serializer_class = AddressSerializer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class MailboxViewSet(LogApiMixin, SetPasswordApiMixin, AccountApiMixin, viewsets.ModelViewSet):
|
class MailboxViewSet(LogApiMixin, SetPasswordApiMixin, AccountApiMixin, viewsets.ModelViewSet):
|
||||||
model = Mailbox
|
queryset = Mailbox.objects.all()
|
||||||
serializer_class = MailboxSerializer
|
serializer_class = MailboxSerializer
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,7 @@ class UNIXUserMaildirBackend(ServiceController):
|
||||||
|
|
||||||
def delete(self, mailbox):
|
def delete(self, mailbox):
|
||||||
context = self.get_context(mailbox)
|
context = self.get_context(mailbox)
|
||||||
self.append('mv %(home)s %(home)s.deleted' % context)
|
self.append('mv %(home)s %(home)s.deleted || exit_code=1' % context)
|
||||||
self.append(textwrap.dedent("""
|
self.append(textwrap.dedent("""
|
||||||
{ sleep 2 && killall -u %(user)s -s KILL; } &
|
{ sleep 2 && killall -u %(user)s -s KILL; } &
|
||||||
killall -u %(user)s || true
|
killall -u %(user)s || true
|
||||||
|
|
|
@ -3,9 +3,8 @@ from django.shortcuts import get_object_or_404
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from orchestra.api.serializers import HyperlinkedModelSerializer
|
from orchestra.api.serializers import SetPasswordHyperlinkedSerializer
|
||||||
from orchestra.contrib.accounts.serializers import AccountSerializerMixin
|
from orchestra.contrib.accounts.serializers import AccountSerializerMixin
|
||||||
from orchestra.core.validators import validate_password
|
|
||||||
|
|
||||||
from .models import Mailbox, Address
|
from .models import Mailbox, Address
|
||||||
|
|
||||||
|
@ -32,10 +31,7 @@ class RelatedAddressSerializer(AccountSerializerMixin, serializers.HyperlinkedMo
|
||||||
return get_object_or_404(queryset, name=data['name'])
|
return get_object_or_404(queryset, name=data['name'])
|
||||||
|
|
||||||
|
|
||||||
class MailboxSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
|
class MailboxSerializer(AccountSerializerMixin, SetPasswordHyperlinkedSerializer):
|
||||||
password = serializers.CharField(max_length=128, label=_('Password'),
|
|
||||||
validators=[validate_password], write_only=True, required=False,
|
|
||||||
widget=widgets.PasswordInput)
|
|
||||||
addresses = RelatedAddressSerializer(many=True, read_only=True)
|
addresses = RelatedAddressSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -43,22 +39,7 @@ class MailboxSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
|
||||||
fields = (
|
fields = (
|
||||||
'url', 'name', 'password', 'filtering', 'custom_filtering', 'addresses', 'is_active'
|
'url', 'name', 'password', 'filtering', 'custom_filtering', 'addresses', 'is_active'
|
||||||
)
|
)
|
||||||
postonly_fields = ('name',)
|
postonly_fields = ('name', 'password')
|
||||||
|
|
||||||
def validate_password(self, attrs, source):
|
|
||||||
""" POST only password """
|
|
||||||
if self.object:
|
|
||||||
if 'password' in attrs:
|
|
||||||
raise serializers.ValidationError(_("Can not set password"))
|
|
||||||
elif 'password' not in attrs:
|
|
||||||
raise serializers.ValidationError(_("Password required"))
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
def save_object(self, obj, **kwargs):
|
|
||||||
# FIXME this method will be called when saving nested serializers :(
|
|
||||||
if not obj.pk:
|
|
||||||
obj.set_password(obj.password)
|
|
||||||
super(MailboxSerializer, self).save_object(obj, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class RelatedMailboxSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
|
class RelatedMailboxSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
|
||||||
|
@ -73,13 +54,14 @@ class RelatedMailboxSerializer(AccountSerializerMixin, serializers.HyperlinkedMo
|
||||||
|
|
||||||
class AddressSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
|
class AddressSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
|
||||||
domain = RelatedDomainSerializer()
|
domain = RelatedDomainSerializer()
|
||||||
mailboxes = RelatedMailboxSerializer(many=True, allow_add_remove=True, required=False)
|
mailboxes = RelatedMailboxSerializer(many=True, required=False) #allow_add_remove=True
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Address
|
model = Address
|
||||||
fields = ('url', 'name', 'domain', 'mailboxes', 'forward')
|
fields = ('url', 'name', 'domain', 'mailboxes', 'forward')
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
|
attrs = super(AddressSerializer, self).validate(attrs)
|
||||||
if not attrs['mailboxes'] and not attrs['forward']:
|
if not attrs['mailboxes'] and not attrs['forward']:
|
||||||
raise serializers.ValidationError("A mailbox or forward address should be provided.")
|
raise serializers.ValidationError("A mailbox or forward address should be provided.")
|
||||||
return attrs
|
return attrs
|
||||||
|
|
|
@ -25,7 +25,9 @@ class Operation():
|
||||||
self.backend = backend
|
self.backend = backend
|
||||||
# instance should maintain any dynamic attribute until backend execution
|
# instance should maintain any dynamic attribute until backend execution
|
||||||
# deep copy is prefered over copy otherwise objects will share same atributes (queryset cache)
|
# deep copy is prefered over copy otherwise objects will share same atributes (queryset cache)
|
||||||
|
print('aa', getattr(instance, 'password', 'NOOOO'), id(instance))
|
||||||
self.instance = copy.deepcopy(instance)
|
self.instance = copy.deepcopy(instance)
|
||||||
|
print('aa', getattr(self.instance, 'password', 'NOOOO'), id(self.instance))
|
||||||
self.action = action
|
self.action = action
|
||||||
self.servers = servers
|
self.servers = servers
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ from .models import BackendLog
|
||||||
@receiver(post_save, dispatch_uid='orchestration.post_save_collector')
|
@receiver(post_save, dispatch_uid='orchestration.post_save_collector')
|
||||||
def post_save_collector(sender, *args, **kwargs):
|
def post_save_collector(sender, *args, **kwargs):
|
||||||
if sender not in [BackendLog, Operation]:
|
if sender not in [BackendLog, Operation]:
|
||||||
|
instance = kwargs.get('instance')
|
||||||
OperationsMiddleware.collect(Operation.SAVE, **kwargs)
|
OperationsMiddleware.collect(Operation.SAVE, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,8 @@ from .serializers import OrderSerializer
|
||||||
|
|
||||||
|
|
||||||
class OrderViewSet(AccountApiMixin, viewsets.ModelViewSet):
|
class OrderViewSet(AccountApiMixin, viewsets.ModelViewSet):
|
||||||
model = Order
|
queryset = Order.objects.all()
|
||||||
serializer_class = OrderSerializer
|
serializer_class = OrderSerializer
|
||||||
|
|
||||||
|
|
||||||
router.register(r'orders', OrderViewSet)
|
router.register(r'orders', OrderViewSet)
|
||||||
|
|
|
@ -8,13 +8,13 @@ from .serializers import PaymentSourceSerializer, TransactionSerializer
|
||||||
|
|
||||||
|
|
||||||
class PaymentSourceViewSet(LogApiMixin, AccountApiMixin, viewsets.ModelViewSet):
|
class PaymentSourceViewSet(LogApiMixin, AccountApiMixin, viewsets.ModelViewSet):
|
||||||
model = PaymentSource
|
|
||||||
serializer_class = PaymentSourceSerializer
|
serializer_class = PaymentSourceSerializer
|
||||||
|
queryset = PaymentSource.objects.all()
|
||||||
|
|
||||||
|
|
||||||
class TransactionViewSet(LogApiMixin, viewsets.ModelViewSet):
|
class TransactionViewSet(LogApiMixin, viewsets.ModelViewSet):
|
||||||
model = Transaction
|
|
||||||
serializer_class = TransactionSerializer
|
serializer_class = TransactionSerializer
|
||||||
|
queryset = Transaction.objects.all()
|
||||||
|
|
||||||
|
|
||||||
router.register(r'payment-sources', PaymentSourceViewSet)
|
router.register(r'payment-sources', PaymentSourceViewSet)
|
||||||
|
|
|
@ -28,6 +28,7 @@ class PaymentSourceSerializer(AccountSerializerMixin, serializers.HyperlinkedMod
|
||||||
return serializer_class().to_native(obj.data)
|
return serializer_class().to_native(obj.data)
|
||||||
return obj.data
|
return obj.data
|
||||||
|
|
||||||
|
# TODO
|
||||||
def metadata(self):
|
def metadata(self):
|
||||||
meta = super(PaymentSourceSerializer, self).metadata()
|
meta = super(PaymentSourceSerializer, self).metadata()
|
||||||
meta['data'] = {
|
meta['data'] = {
|
||||||
|
|
|
@ -7,8 +7,8 @@ from .models import Resource, ResourceData
|
||||||
|
|
||||||
|
|
||||||
class ResourceSerializer(serializers.ModelSerializer):
|
class ResourceSerializer(serializers.ModelSerializer):
|
||||||
name = serializers.SerializerMethodField('get_name')
|
name = serializers.SerializerMethodField()
|
||||||
unit = serializers.Field()
|
unit = serializers.ReadOnlyField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ResourceData
|
model = ResourceData
|
||||||
|
@ -72,19 +72,19 @@ def insert_resource_serializers():
|
||||||
viewset = router.get_viewset(model)
|
viewset = router.get_viewset(model)
|
||||||
viewset.serializer_class.validate_resources = validate_resources
|
viewset.serializer_class.validate_resources = validate_resources
|
||||||
|
|
||||||
old_metadata = viewset.metadata
|
old_options = viewset.options
|
||||||
def metadata(self, request, resources=resources):
|
def options(self, request, resources=resources):
|
||||||
""" Provides available resources description """
|
""" Provides available resources description """
|
||||||
ret = old_metadata(self, request)
|
metadata = old_options(self, request)
|
||||||
ret['available_resources'] = [
|
metadata.data['available_resources'] = [
|
||||||
{
|
{
|
||||||
'name': resource.name,
|
'name': resource.name,
|
||||||
'on_demand': resource.on_demand,
|
'on_demand': resource.on_demand,
|
||||||
'default_allocation': resource.default_allocation
|
'default_allocation': resource.default_allocation
|
||||||
} for resource in resources
|
} for resource in resources
|
||||||
]
|
]
|
||||||
return ret
|
return metadata
|
||||||
viewset.metadata = metadata
|
viewset.options = options
|
||||||
|
|
||||||
if database_ready():
|
if database_ready():
|
||||||
insert_resource_serializers()
|
insert_resource_serializers()
|
||||||
|
|
17
orchestra/contrib/saas/api.py
Normal file
17
orchestra/contrib/saas/api.py
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
from rest_framework import viewsets
|
||||||
|
|
||||||
|
from orchestra.api import router, LogApiMixin
|
||||||
|
from orchestra.contrib.accounts.api import AccountApiMixin
|
||||||
|
|
||||||
|
from . import settings
|
||||||
|
from .models import SaaS
|
||||||
|
from .serializers import SaaSSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class SaaSViewSet(LogApiMixin, AccountApiMixin, viewsets.ModelViewSet):
|
||||||
|
queryset = SaaS.objects.all()
|
||||||
|
serializer_class = SaaSSerializer
|
||||||
|
filter_fields = ('name',)
|
||||||
|
|
||||||
|
|
||||||
|
router.register(r'saas', SaaSViewSet)
|
|
@ -11,6 +11,17 @@ from .fields import VirtualDatabaseRelation
|
||||||
from .services import SoftwareService
|
from .services import SoftwareService
|
||||||
|
|
||||||
|
|
||||||
|
class SaaSQuerySet(models.QuerySet):
|
||||||
|
def create(self, **kwargs):
|
||||||
|
""" Sets password if provided, all within a single DB operation """
|
||||||
|
password = kwargs.pop('password')
|
||||||
|
saas = SaaS(**kwargs)
|
||||||
|
if password:
|
||||||
|
saas.set_password(password)
|
||||||
|
saas.save()
|
||||||
|
return saas
|
||||||
|
|
||||||
|
|
||||||
class SaaS(models.Model):
|
class SaaS(models.Model):
|
||||||
service = models.CharField(_("service"), max_length=32,
|
service = models.CharField(_("service"), max_length=32,
|
||||||
choices=SoftwareService.get_choices())
|
choices=SoftwareService.get_choices())
|
||||||
|
@ -27,6 +38,7 @@ class SaaS(models.Model):
|
||||||
|
|
||||||
# Some SaaS sites may need a database, with this virtual field we tell the ORM to delete them
|
# Some SaaS sites may need a database, with this virtual field we tell the ORM to delete them
|
||||||
databases = VirtualDatabaseRelation('databases.Database')
|
databases = VirtualDatabaseRelation('databases.Database')
|
||||||
|
objects = SaaSQuerySet.as_manager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "SaaS"
|
verbose_name = "SaaS"
|
||||||
|
|
29
orchestra/contrib/saas/serializers.py
Normal file
29
orchestra/contrib/saas/serializers.py
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
from django.forms import widgets
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.core.validators import RegexValidator
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from orchestra.api.serializers import SetPasswordHyperlinkedSerializer
|
||||||
|
from orchestra.contrib.accounts.serializers import AccountSerializerMixin
|
||||||
|
from orchestra.core import validators
|
||||||
|
|
||||||
|
from .models import SaaS
|
||||||
|
|
||||||
|
|
||||||
|
class SaaSSerializer(AccountSerializerMixin, SetPasswordHyperlinkedSerializer):
|
||||||
|
data = serializers.DictField(required=False)
|
||||||
|
password = serializers.CharField(write_only=True, required=False,
|
||||||
|
style={'widget': widgets.PasswordInput},
|
||||||
|
validators=[
|
||||||
|
validators.validate_password,
|
||||||
|
RegexValidator(r'^[^"\'\\]+$',
|
||||||
|
_('Enter a valid password. '
|
||||||
|
'This value may contain any ascii character except for '
|
||||||
|
' \'/"/\\/ characters.'), 'invalid'),
|
||||||
|
])
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = SaaS
|
||||||
|
fields = ('url', 'name', 'service', 'is_active', 'data', 'password')
|
||||||
|
postonly_fields = ('name', 'service', 'password')
|
|
@ -19,6 +19,7 @@ class SoftwareServiceForm(PluginDataForm):
|
||||||
password = forms.CharField(label=_("Password"), required=False,
|
password = forms.CharField(label=_("Password"), required=False,
|
||||||
widget=widgets.ReadOnlyWidget('<strong>Unknown password</strong>'),
|
widget=widgets.ReadOnlyWidget('<strong>Unknown password</strong>'),
|
||||||
validators=[
|
validators=[
|
||||||
|
validators.validate_password,
|
||||||
RegexValidator(r'^[^"\'\\]+$',
|
RegexValidator(r'^[^"\'\\]+$',
|
||||||
_('Enter a valid password. '
|
_('Enter a valid password. '
|
||||||
'This value may contain any ascii character except for '
|
'This value may contain any ascii character except for '
|
||||||
|
|
|
@ -9,7 +9,7 @@ from .serializers import SystemUserSerializer
|
||||||
|
|
||||||
|
|
||||||
class SystemUserViewSet(LogApiMixin, AccountApiMixin, SetPasswordApiMixin, viewsets.ModelViewSet):
|
class SystemUserViewSet(LogApiMixin, AccountApiMixin, SetPasswordApiMixin, viewsets.ModelViewSet):
|
||||||
model = SystemUser
|
queryset = SystemUser.objects.all()
|
||||||
serializer_class = SystemUserSerializer
|
serializer_class = SystemUserSerializer
|
||||||
filter_fields = ('username',)
|
filter_fields = ('username',)
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,8 @@ from django.shortcuts import get_object_or_404
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from orchestra.api.serializers import HyperlinkedModelSerializer
|
from orchestra.api.serializers import SetPasswordHyperlinkedSerializer
|
||||||
from orchestra.contrib.accounts.serializers import AccountSerializerMixin
|
from orchestra.contrib.accounts.serializers import AccountSerializerMixin
|
||||||
from orchestra.core.validators import validate_password
|
|
||||||
|
|
||||||
from .models import SystemUser
|
from .models import SystemUser
|
||||||
from .validators import validate_home
|
from .validators import validate_home
|
||||||
|
@ -21,36 +20,25 @@ class GroupSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerial
|
||||||
return get_object_or_404(queryset, username=data['username'])
|
return get_object_or_404(queryset, username=data['username'])
|
||||||
|
|
||||||
|
|
||||||
class SystemUserSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
|
class SystemUserSerializer(AccountSerializerMixin, SetPasswordHyperlinkedSerializer):
|
||||||
password = serializers.CharField(max_length=128, label=_('Password'),
|
groups = GroupSerializer(many=True, required=False)
|
||||||
validators=[validate_password], write_only=True, required=False,
|
|
||||||
widget=widgets.PasswordInput)
|
|
||||||
groups = GroupSerializer(many=True, allow_add_remove=True, required=False)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SystemUser
|
model = SystemUser
|
||||||
fields = (
|
fields = (
|
||||||
'url', 'username', 'password', 'home', 'directory', 'shell', 'groups', 'is_active',
|
'url', 'username', 'password', 'home', 'directory', 'shell', 'groups', 'is_active',
|
||||||
)
|
)
|
||||||
postonly_fields = ('username',)
|
postonly_fields = ('username', 'password')
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
|
attrs = super(SystemUserSerializer, self).validate(attrs)
|
||||||
user = SystemUser(
|
user = SystemUser(
|
||||||
username=attrs.get('username') or self.object.username,
|
username=attrs.get('username') or self.instance.username,
|
||||||
shell=attrs.get('shell') or self.object.shell,
|
shell=attrs.get('shell') or self.instance.shell,
|
||||||
)
|
)
|
||||||
validate_home(user, attrs, self.account)
|
validate_home(user, attrs, self.account)
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
def validate_password(self, attrs, source):
|
|
||||||
""" POST only password """
|
|
||||||
if self.object:
|
|
||||||
if 'password' in attrs:
|
|
||||||
raise serializers.ValidationError(_("Can not set password"))
|
|
||||||
elif 'password' not in attrs:
|
|
||||||
raise serializers.ValidationError(_("Password required"))
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
def validate_groups(self, attrs, source):
|
def validate_groups(self, attrs, source):
|
||||||
groups = attrs.get(source)
|
groups = attrs.get(source)
|
||||||
if groups:
|
if groups:
|
||||||
|
@ -59,9 +47,3 @@ class SystemUserSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
|
||||||
raise serializers.ValidationError(
|
raise serializers.ValidationError(
|
||||||
_("Do not make the user member of its group"))
|
_("Do not make the user member of its group"))
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
def save_object(self, obj, **kwargs):
|
|
||||||
# FIXME this method will be called when saving nested serializers :(
|
|
||||||
if not obj.pk:
|
|
||||||
obj.set_password(obj.password)
|
|
||||||
super(SystemUserSerializer, self).save_object(obj, **kwargs)
|
|
||||||
|
|
|
@ -9,20 +9,20 @@ from .serializers import WebAppSerializer
|
||||||
|
|
||||||
|
|
||||||
class WebAppViewSet(LogApiMixin, AccountApiMixin, viewsets.ModelViewSet):
|
class WebAppViewSet(LogApiMixin, AccountApiMixin, viewsets.ModelViewSet):
|
||||||
model = WebApp
|
queryset = WebApp.objects.all()
|
||||||
serializer_class = WebAppSerializer
|
serializer_class = WebAppSerializer
|
||||||
filter_fields = ('name',)
|
filter_fields = ('name',)
|
||||||
|
|
||||||
def metadata(self, request):
|
def options(self, request):
|
||||||
ret = super(WebAppViewSet, self).metadata(request)
|
metadata = super(WebAppViewSet, self).options(request)
|
||||||
names = [
|
names = [
|
||||||
'WEBAPPS_BASE_DIR', 'WEBAPPS_TYPES', 'WEBAPPS_WEBAPP_OPTIONS',
|
'WEBAPPS_BASE_DIR', 'WEBAPPS_TYPES', 'WEBAPPS_WEBAPP_OPTIONS',
|
||||||
'WEBAPPS_PHP_DISABLED_FUNCTIONS', 'WEBAPPS_DEFAULT_TYPE'
|
'WEBAPPS_PHP_DISABLED_FUNCTIONS', 'WEBAPPS_DEFAULT_TYPE'
|
||||||
]
|
]
|
||||||
ret['settings'] = {
|
metadata.data['settings'] = {
|
||||||
name.lower(): getattr(settings, name, None) for name in names
|
name.lower(): getattr(settings, name, None) for name in names
|
||||||
}
|
}
|
||||||
return ret
|
return metadata
|
||||||
|
|
||||||
|
|
||||||
router.register(r'webapps', WebAppViewSet)
|
router.register(r'webapps', WebAppViewSet)
|
||||||
|
|
|
@ -8,6 +8,9 @@ from .. import settings
|
||||||
|
|
||||||
class WebAppServiceMixin(object):
|
class WebAppServiceMixin(object):
|
||||||
model = 'webapps.WebApp'
|
model = 'webapps.WebApp'
|
||||||
|
related_models = (
|
||||||
|
('webapps.WebAppOption', 'webapp'),
|
||||||
|
)
|
||||||
directive = None
|
directive = None
|
||||||
|
|
||||||
def create_webapp_dir(self, context):
|
def create_webapp_dir(self, context):
|
||||||
|
|
|
@ -1,14 +1,55 @@
|
||||||
from orchestra.api.fields import OptionField
|
from rest_framework import serializers
|
||||||
|
|
||||||
from orchestra.api.serializers import HyperlinkedModelSerializer
|
from orchestra.api.serializers import HyperlinkedModelSerializer
|
||||||
from orchestra.contrib.accounts.serializers import AccountSerializerMixin
|
from orchestra.contrib.accounts.serializers import AccountSerializerMixin
|
||||||
|
|
||||||
from .models import WebApp
|
from .models import WebApp, WebAppOption
|
||||||
|
|
||||||
|
|
||||||
|
class OptionSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = WebAppOption
|
||||||
|
fields = ('name', 'value')
|
||||||
|
|
||||||
|
def to_representation(self, instance):
|
||||||
|
return {prop.name: prop.value for prop in instance.all()}
|
||||||
|
|
||||||
|
def to_internal_value(self, data):
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
class WebAppSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
|
class WebAppSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
|
||||||
options = OptionField(required=False)
|
options = OptionSerializer(required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = WebApp
|
model = WebApp
|
||||||
fields = ('url', 'name', 'type', 'options')
|
fields = ('url', 'name', 'type', 'options')
|
||||||
postonly_fields = ('name', 'type')
|
postonly_fields = ('name', 'type')
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
options_data = validated_data.pop('options')
|
||||||
|
webapp = super(WebAppSerializer, self).create(validated_data)
|
||||||
|
for key, value in options_data.items():
|
||||||
|
WebAppOption.objects.create(webapp=webapp, name=key, value=value)
|
||||||
|
return webap
|
||||||
|
|
||||||
|
def update(self, instance, validated_data):
|
||||||
|
options_data = validated_data.pop('options')
|
||||||
|
instance = super(WebAppSerializer, self).update(validated_data)
|
||||||
|
existing = {}
|
||||||
|
for obj in instance.options.all():
|
||||||
|
existing[obj.name] = obj
|
||||||
|
posted = set()
|
||||||
|
for key, value in options_data.items():
|
||||||
|
posted.add(key)
|
||||||
|
try:
|
||||||
|
option = existing[key]
|
||||||
|
except KeyError:
|
||||||
|
option = instance.options.create(name=key, value=value)
|
||||||
|
else:
|
||||||
|
if option.value != value:
|
||||||
|
option.value = value
|
||||||
|
option.save(update_fields=('value',))
|
||||||
|
for to_delete in set(existing.keys())-posted:
|
||||||
|
existing[to_delete].delete()
|
||||||
|
return instance
|
||||||
|
|
|
@ -9,17 +9,17 @@ from .serializers import WebsiteSerializer
|
||||||
|
|
||||||
|
|
||||||
class WebsiteViewSet(LogApiMixin, AccountApiMixin, viewsets.ModelViewSet):
|
class WebsiteViewSet(LogApiMixin, AccountApiMixin, viewsets.ModelViewSet):
|
||||||
model = Website
|
queryset = Website.objects.all()
|
||||||
serializer_class = WebsiteSerializer
|
serializer_class = WebsiteSerializer
|
||||||
filter_fields = ('name',)
|
filter_fields = ('name',)
|
||||||
|
|
||||||
def metadata(self, request):
|
def options(self, request):
|
||||||
ret = super(WebsiteViewSet, self).metadata(request)
|
metadata = super(WebsiteViewSet, self).options(request)
|
||||||
names = ['WEBSITES_OPTIONS', 'WEBSITES_PORT_CHOICES']
|
names = ['WEBSITES_OPTIONS', 'WEBSITES_PORT_CHOICES']
|
||||||
ret['settings'] = {
|
metadata.data['settings'] = {
|
||||||
name.lower(): getattr(settings, name, None) for name in names
|
name.lower(): getattr(settings, name, None) for name in names
|
||||||
}
|
}
|
||||||
return ret
|
return metadata
|
||||||
|
|
||||||
|
|
||||||
router.register(r'websites', WebsiteViewSet)
|
router.register(r'websites', WebsiteViewSet)
|
||||||
|
|
|
@ -127,13 +127,13 @@ class Apache2Backend(ServiceController):
|
||||||
echo -n "$state" > /dev/shm/restart.apache2
|
echo -n "$state" > /dev/shm/restart.apache2
|
||||||
if [[ $UPDATED == 1 ]]; then
|
if [[ $UPDATED == 1 ]]; then
|
||||||
if [[ $locked == 0 ]]; then
|
if [[ $locked == 0 ]]; then
|
||||||
service apache2 satus && service apache2 reload || service apache2 start
|
service apache2 status && service apache2 reload || service apache2 start
|
||||||
else
|
else
|
||||||
echo "Apache2Backend RESTART" >> /dev/shm/restart.apache2
|
echo "Apache2Backend RESTART" >> /dev/shm/restart.apache2
|
||||||
fi
|
fi
|
||||||
elif [[ "$state" =~ .*RESTART$ ]]; then
|
elif [[ "$state" =~ .*RESTART$ ]]; then
|
||||||
rm /dev/shm/restart.apache2
|
rm /dev/shm/restart.apache2
|
||||||
service apache2 satus && service apache2 reload || service apache2 start
|
service apache2 status && service apache2 reload || service apache2 start
|
||||||
fi""")
|
fi""")
|
||||||
)
|
)
|
||||||
super(Apache2Backend, self).commit()
|
super(Apache2Backend, self).commit()
|
||||||
|
|
|
@ -2,11 +2,10 @@ from django.core.exceptions import ValidationError
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from orchestra.api.fields import OptionField
|
|
||||||
from orchestra.api.serializers import HyperlinkedModelSerializer
|
from orchestra.api.serializers import HyperlinkedModelSerializer
|
||||||
from orchestra.contrib.accounts.serializers import AccountSerializerMixin
|
from orchestra.contrib.accounts.serializers import AccountSerializerMixin
|
||||||
|
|
||||||
from .models import Website, Content
|
from .models import Website, Content, WebsiteDirective
|
||||||
from .validators import validate_domain_protocol
|
from .validators import validate_domain_protocol
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,7 +21,7 @@ class RelatedDomainSerializer(AccountSerializerMixin, serializers.HyperlinkedMod
|
||||||
|
|
||||||
class RelatedWebAppSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
|
class RelatedWebAppSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
# model = Content.webapp.field.rel.to
|
model = Content.webapp.field.rel.to
|
||||||
fields = ('url', 'name', 'type')
|
fields = ('url', 'name', 'type')
|
||||||
|
|
||||||
def from_native(self, data, files=None):
|
def from_native(self, data, files=None):
|
||||||
|
@ -41,11 +40,23 @@ class ContentSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
return '%s-%s' % (data.get('website'), data.get('path'))
|
return '%s-%s' % (data.get('website'), data.get('path'))
|
||||||
|
|
||||||
|
|
||||||
|
class DirectiveSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = WebsiteDirective
|
||||||
|
fields = ('name', 'value')
|
||||||
|
|
||||||
|
def to_representation(self, instance):
|
||||||
|
return {prop.name: prop.value for prop in instance.all()}
|
||||||
|
|
||||||
|
def to_internal_value(self, data):
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
class WebsiteSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
|
class WebsiteSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
|
||||||
domains = RelatedDomainSerializer(many=True, allow_add_remove=True, required=False)
|
domains = RelatedDomainSerializer(many=True, required=False) #allow_add_remove=True
|
||||||
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')
|
||||||
directives = OptionField(required=False)
|
directives = DirectiveSerializer(required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Website
|
model = Website
|
||||||
|
@ -62,3 +73,30 @@ class WebsiteSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
|
||||||
self.add_error(None, e)
|
self.add_error(None, e)
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
options_data = validated_data.pop('options')
|
||||||
|
webapp = super(WebsiteSerializer, self).create(validated_data)
|
||||||
|
for key, value in options_data.items():
|
||||||
|
WebAppOption.objects.create(webapp=webapp, name=key, value=value)
|
||||||
|
return webap
|
||||||
|
|
||||||
|
def update(self, instance, validated_data):
|
||||||
|
options_data = validated_data.pop('options')
|
||||||
|
instance = super(WebsiteSerializer, self).update(validated_data)
|
||||||
|
existing = {}
|
||||||
|
for obj in instance.options.all():
|
||||||
|
existing[obj.name] = obj
|
||||||
|
posted = set()
|
||||||
|
for key, value in options_data.items():
|
||||||
|
posted.add(key)
|
||||||
|
try:
|
||||||
|
option = existing[key]
|
||||||
|
except KeyError:
|
||||||
|
option = instance.options.create(name=key, value=value)
|
||||||
|
else:
|
||||||
|
if option.value != value:
|
||||||
|
option.value = value
|
||||||
|
option.save(update_fields=('value',))
|
||||||
|
for to_delete in set(existing.keys())-posted:
|
||||||
|
existing[to_delete].delete()
|
||||||
|
return instance
|
||||||
|
|
|
@ -59,8 +59,8 @@ def paddingCheckboxSelectMultiple(padding):
|
||||||
old_render = widget.render
|
old_render = widget.render
|
||||||
def render(self, *args, **kwargs):
|
def render(self, *args, **kwargs):
|
||||||
value = old_render(self, *args, **kwargs)
|
value = old_render(self, *args, **kwargs)
|
||||||
value = re.sub(r'^<ul id=(.*)>',
|
value = re.sub(r'^<ul id=([^>]+)>',
|
||||||
r'<ul id=\1 style="padding-left:%ipx">' % padding, value, 1)
|
r'<ul id=\1 style="padding-left:%ipx">' % padding, value, 1)
|
||||||
return mark_safe(value)
|
return mark_safe(value)
|
||||||
widget.render = render
|
widget.render = render
|
||||||
return widget
|
return widget
|
||||||
|
|
|
@ -6,11 +6,12 @@ class OrchestraPermissionBackend(DjangoModelPermissions):
|
||||||
""" Permissions according to each user """
|
""" Permissions according to each user """
|
||||||
|
|
||||||
def has_permission(self, request, view):
|
def has_permission(self, request, view):
|
||||||
model_cls = getattr(view, 'model', None)
|
queryset = getattr(view, 'queryset', None)
|
||||||
if not model_cls:
|
if queryset is None:
|
||||||
name = resolve(request.path).url_name
|
name = resolve(request.path).url_name
|
||||||
return name == 'api-root'
|
return name == 'api-root'
|
||||||
|
|
||||||
|
model_cls = queryset.model
|
||||||
perms = self.get_required_permissions(request.method, model_cls)
|
perms = self.get_required_permissions(request.method, model_cls)
|
||||||
if (request.user and
|
if (request.user and
|
||||||
request.user.is_authenticated() and
|
request.user.is_authenticated() and
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
{% extends "rest_framework/base.html" %}
|
{% extends "rest_framework/base.html" %}
|
||||||
{% load rest_framework utils staticfiles %}
|
{% load rest_framework utils staticfiles %}
|
||||||
|
|
||||||
{% block head %}
|
{% block meta %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
<link rel="icon" href="{% static "orchestra/images/favicon.png" %}" type="image/png" />
|
<link rel="icon" href="{% static "orchestra/images/favicon.png" %}" type="image/png" />
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block title %}{{ SITE_VERBOSE_NAME }} REST API{% endblock %}
|
{% block title %}{{ SITE_VERBOSE_NAME }} REST API{% endblock %}
|
||||||
{% block branding %}<a class='brand' href="{% url 'api-root' %}">{{ SITE_VERBOSE_NAME }} REST API <span class="version">{% version %}</span></a>{% endblock %}
|
{% block branding %}<a class='brand' href="{% url 'api-root' %}">{{ SITE_VERBOSE_NAME }} REST API <span class="version">{% version %}</span></a>{% endblock %}
|
||||||
{% block userlinks %}
|
{% block userlinks %}
|
||||||
|
|
|
@ -24,12 +24,14 @@ def orchestra_version():
|
||||||
def rest_to_admin_url(context):
|
def rest_to_admin_url(context):
|
||||||
""" returns the admin equivelent url of the current REST API view """
|
""" returns the admin equivelent url of the current REST API view """
|
||||||
view = context['view']
|
view = context['view']
|
||||||
model = getattr(view, 'model', None)
|
queryset = getattr(view, 'queryset', None)
|
||||||
|
model = queryset.model if queryset else None
|
||||||
url = 'admin:index'
|
url = 'admin:index'
|
||||||
args = []
|
args = []
|
||||||
if model:
|
if model:
|
||||||
url = 'admin:%s_%s' % (model._meta.app_label, model._meta.module_name)
|
opts = model._meta
|
||||||
pk = view.kwargs.get(view.pk_url_kwarg)
|
url = 'admin:%s_%s' % (opts.app_label, opts.model_name)
|
||||||
|
pk = view.kwargs.get(view.lookup_field)
|
||||||
if pk:
|
if pk:
|
||||||
url += '_change'
|
url += '_change'
|
||||||
args = [pk]
|
args = [pk]
|
||||||
|
|
Loading…
Reference in a new issue