Upgraded to DRF3

This commit is contained in:
Marc Aymerich 2015-04-23 14:34:04 +00:00
parent fbb3c6ab03
commit 1a78317028
52 changed files with 374 additions and 348 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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 \

View file

@ -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

View file

@ -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)

View file

@ -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'])

View file

@ -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

View file

@ -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

View file

@ -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 = (

View file

@ -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',)

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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',)

View file

@ -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')

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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'] = {

View file

@ -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()

View 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)

View file

@ -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"

View 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')

View file

@ -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 '

View file

@ -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',)

View file

@ -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)

View file

@ -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)

View file

@ -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):

View file

@ -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

View file

@ -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)

View file

@ -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()

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 %}

View file

@ -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]