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
|
||||
# Amend lines???
|
||||
|
||||
|
||||
# Add icon on select contact view
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
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 .serializers import SetPasswordSerializer
|
||||
|
||||
|
||||
class SetPasswordApiMixin(object):
|
||||
@action(serializer_class=SetPasswordSerializer)
|
||||
@detail_route(serializer_class=SetPasswordSerializer)
|
||||
def set_password(self, request, pk):
|
||||
obj = self.get_object()
|
||||
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 wrapper(self, request, view=view, *args, **kwargs):
|
||||
def wrapper(self, request, *args, **kwargs):
|
||||
""" wrapper function that inserts HTTP links on view """
|
||||
links = []
|
||||
for name in view_names:
|
||||
|
|
|
@ -12,37 +12,42 @@ from .helpers import insert_links
|
|||
|
||||
|
||||
class LogApiMixin(object):
|
||||
def post(self, request, *args, **kwargs):
|
||||
def create(self, request, *args, **kwargs):
|
||||
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.')
|
||||
self.log_addition(request, message, ADDITION)
|
||||
self.log(request, message, ADDITION, instance=self.serializer.instance)
|
||||
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
|
||||
response = super(LogApiMixin, self).put(request, *args, **kwargs)
|
||||
message = _('Changed')
|
||||
response = super(LogApiMixin, self).update(request, *args, **kwargs)
|
||||
message = _('Changed data')
|
||||
self.log(request, message, CHANGE)
|
||||
return response
|
||||
|
||||
def patch(self, request, *args, **kwargs):
|
||||
def partial_update(self, request, *args, **kwargs):
|
||||
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)
|
||||
self.log(request, message, CHANGE)
|
||||
return response
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
from django.contrib.admin.models import DELETION
|
||||
message = _('Deleted')
|
||||
self.log(request, message, DELETION)
|
||||
response = super(LogApiMixin, self).put(request, *args, **kwargs)
|
||||
response = super(LogApiMixin, self).destroy(request, *args, **kwargs)
|
||||
return response
|
||||
|
||||
def log(self, request, message, action):
|
||||
def log(self, request, message, action, instance=None):
|
||||
from django.contrib.admin.models import LogEntry
|
||||
instance = self.get_object()
|
||||
instance = instance or self.get_object()
|
||||
LogEntry.objects.log_action(
|
||||
user_id=request.user.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):
|
||||
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
|
||||
msg = "%s does not have a regiestered viewset" % prefix_or_model
|
||||
raise KeyError(msg)
|
||||
|
@ -80,7 +85,7 @@ class LinkHeaderRouter(DefaultRouter):
|
|||
# setattr(viewset, 'inserted', getattr(viewset, 'inserted', []))
|
||||
if viewset.serializer_class is None:
|
||||
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:
|
||||
viewset.serializer_class.Meta.fields += (name,)
|
||||
# viewset.inserted.append(name)
|
||||
|
|
|
@ -38,7 +38,7 @@ class APIRoot(views.APIView):
|
|||
kwargs = {}
|
||||
url = reverse(url_name, request=request, format=format, kwargs=kwargs)
|
||||
links.append('<%s>; rel="%s"' % (url, url_name))
|
||||
model = viewset.model
|
||||
model = viewset.queryset.model
|
||||
group = None
|
||||
if model in services:
|
||||
group = 'services'
|
||||
|
@ -61,10 +61,10 @@ class APIRoot(views.APIView):
|
|||
})
|
||||
return Response(body, headers=headers)
|
||||
|
||||
def metadata(self, request):
|
||||
ret = super(APIRoot, self).metadata(request)
|
||||
ret['settings'] = {
|
||||
def options(self, request):
|
||||
metadata = super(APIRoot, self).options(request)
|
||||
metadata.data['settings'] = {
|
||||
name.lower(): getattr(settings, name, None)
|
||||
for name in self.names
|
||||
}
|
||||
return ret
|
||||
return metadata
|
||||
|
|
|
@ -7,47 +7,90 @@ from ..core.validators import validate_password
|
|||
|
||||
class SetPasswordSerializer(serializers.Serializer):
|
||||
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):
|
||||
def __init__(self, meta):
|
||||
super(HyperlinkedModelSerializerOptions, self).__init__(meta)
|
||||
self.postonly_fields = getattr(meta, 'postonly_fields', ())
|
||||
#class HyperlinkedModelSerializerOptions(serializers.HyperlinkedModelSerializerOptions):
|
||||
# def __init__(self, meta):
|
||||
# super(HyperlinkedModelSerializerOptions, self).__init__(meta)
|
||||
# self.postonly_fields = getattr(meta, 'postonly_fields', ())
|
||||
|
||||
|
||||
class HyperlinkedModelSerializer(serializers.HyperlinkedModelSerializer):
|
||||
""" 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 """
|
||||
model_attrs = dict(**attrs)
|
||||
model_attrs = dict(**validated_data)
|
||||
if instance is not None:
|
||||
for attr, value in attrs.items():
|
||||
if attr in self.opts.postonly_fields:
|
||||
for attr, value in validated_data.items():
|
||||
if attr in self.Meta.postonly_fields:
|
||||
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):
|
||||
widget = widgets.CheckboxSelectMultiple
|
||||
class SetPasswordHyperlinkedSerializer(HyperlinkedModelSerializer):
|
||||
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):
|
||||
""" 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 validate_password(self, attrs, source):
|
||||
""" POST only password """
|
||||
if self.instance:
|
||||
if 'password' in attrs:
|
||||
raise serializers.ValidationError(_("Can not set password"))
|
||||
elif 'password' not in attrs:
|
||||
raise serializers.ValidationError(_("Password required"))
|
||||
return attrs
|
||||
|
||||
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
|
||||
def validate(self, attrs):
|
||||
""" remove password in case is not a real model field """
|
||||
try:
|
||||
self.Meta.model._meta.get_field_by_name('password')
|
||||
except models.FieldDoesNotExist:
|
||||
pass
|
||||
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 \
|
||||
billiard==3.3.0.18 \
|
||||
Markdown==2.4 \
|
||||
djangorestframework==2.4.4 \
|
||||
djangorestframework==3.1.1 \
|
||||
paramiko==1.15.1 \
|
||||
ecdsa==0.11 \
|
||||
Pygments==1.6 \
|
||||
django-filter==0.7 \
|
||||
django-filter==0.9.2 \
|
||||
passlib==1.6.2 \
|
||||
jsonfield==0.9.22 \
|
||||
lxml==3.3.5 \
|
||||
|
|
|
@ -14,7 +14,7 @@ class AccountApiMixin(object):
|
|||
|
||||
|
||||
class AccountViewSet(LogApiMixin, SetPasswordApiMixin, viewsets.ModelViewSet):
|
||||
model = Account
|
||||
queryset = Account.objects.all()
|
||||
serializer_class = AccountSerializer
|
||||
singleton_pk = lambda _,request: request.user.pk
|
||||
|
||||
|
|
|
@ -20,6 +20,6 @@ class AccountSerializerMixin(object):
|
|||
if request:
|
||||
self.account = request.user
|
||||
|
||||
def save_object(self, obj, **kwargs):
|
||||
obj.account = self.account
|
||||
super(AccountSerializerMixin, self).save_object(obj, **kwargs)
|
||||
def create(self, validated_data):
|
||||
validated_data['account'] = self.account
|
||||
return super(AccountSerializerMixin, self).create(validated_data)
|
||||
|
|
|
@ -12,7 +12,7 @@ from .serializers import BillSerializer
|
|||
|
||||
|
||||
class BillViewSet(LogApiMixin, AccountApiMixin, viewsets.ModelViewSet):
|
||||
model = Bill
|
||||
queryset = Bill.objects.all()
|
||||
serializer_class = BillSerializer
|
||||
|
||||
@detail_route(methods=['get'])
|
||||
|
|
|
@ -8,7 +8,7 @@ from .serializers import ContactSerializer
|
|||
|
||||
|
||||
class ContactViewSet(LogApiMixin, AccountApiMixin, viewsets.ModelViewSet):
|
||||
model = Contact
|
||||
queryset = Contact.objects.all()
|
||||
serializer_class = ContactSerializer
|
||||
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ class EmailUsageListFilter(SimpleListFilter):
|
|||
parameter_name = 'email_usages'
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
return Contact.email_usage.field.choices
|
||||
return Contact.EMAIL_USAGES
|
||||
|
||||
def queryset(self, request, queryset):
|
||||
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 orchestra.api.serializers import MultiSelectField
|
||||
#from orchestra.api.serializers import MultiSelectField
|
||||
from orchestra.contrib.accounts.serializers import AccountSerializerMixin
|
||||
|
||||
from .models import Contact
|
||||
|
||||
|
||||
class ContactSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
|
||||
email_usage = MultiSelectField(choices=Contact.EMAIL_USAGES)
|
||||
email_usage = serializers.MultipleChoiceField(choices=Contact.EMAIL_USAGES)
|
||||
|
||||
class Meta:
|
||||
model = Contact
|
||||
fields = (
|
||||
|
|
|
@ -8,13 +8,13 @@ from .serializers import DatabaseSerializer, DatabaseUserSerializer
|
|||
|
||||
|
||||
class DatabaseViewSet(LogApiMixin, AccountApiMixin, viewsets.ModelViewSet):
|
||||
model = Database
|
||||
queryset = Database.objects.all()
|
||||
serializer_class = DatabaseSerializer
|
||||
filter_fields = ('name',)
|
||||
|
||||
|
||||
class DatabaseUserViewSet(LogApiMixin, AccountApiMixin, SetPasswordApiMixin, viewsets.ModelViewSet):
|
||||
model = DatabaseUser
|
||||
queryset = DatabaseUser.objects.all()
|
||||
serializer_class = DatabaseUserSerializer
|
||||
filter_fields = ('username',)
|
||||
|
||||
|
|
|
@ -3,9 +3,8 @@ from django.shortcuts import get_object_or_404
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from orchestra.api.serializers import HyperlinkedModelSerializer
|
||||
from orchestra.api.serializers import HyperlinkedModelSerializer, SetPasswordHyperlinkedSerializer
|
||||
from orchestra.contrib.accounts.serializers import AccountSerializerMixin
|
||||
from orchestra.core.validators import validate_password
|
||||
|
||||
from .models import Database, DatabaseUser
|
||||
|
||||
|
@ -21,7 +20,7 @@ class RelatedDatabaseUserSerializer(AccountSerializerMixin, serializers.Hyperlin
|
|||
|
||||
|
||||
class DatabaseSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
|
||||
users = RelatedDatabaseUserSerializer(many=True, allow_add_remove=True)
|
||||
users = RelatedDatabaseUserSerializer(many=True) #allow_add_remove=True
|
||||
|
||||
class Meta:
|
||||
model = Database
|
||||
|
@ -29,6 +28,7 @@ class DatabaseSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
|
|||
postonly_fields = ('name', 'type')
|
||||
|
||||
def validate(self, attrs):
|
||||
attrs = super(DatabaseSerializer, self).validate(attrs)
|
||||
for user in attrs['users']:
|
||||
if user.type != 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'])
|
||||
|
||||
|
||||
class DatabaseUserSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
|
||||
password = serializers.CharField(max_length=128, label=_('Password'),
|
||||
validators=[validate_password], write_only=True,
|
||||
widget=widgets.PasswordInput)
|
||||
databases = RelatedDatabaseSerializer(many=True, allow_add_remove=True, required=False)
|
||||
class DatabaseUserSerializer(AccountSerializerMixin, SetPasswordHyperlinkedSerializer):
|
||||
databases = RelatedDatabaseSerializer(many=True, required=False) # allow_add_remove=True
|
||||
|
||||
class Meta:
|
||||
model = DatabaseUser
|
||||
fields = ('url', 'username', 'password', 'type', 'databases')
|
||||
postonly_fields = ('username', 'type')
|
||||
postonly_fields = ('username', 'type', 'password')
|
||||
|
||||
def validate(self, attrs):
|
||||
attrs = super(DatabaseUserSerializer, self).validate(attrs)
|
||||
for database in attrs.get('databases', []):
|
||||
if database.type != attrs['type']:
|
||||
raise serializers.ValidationError("Database type must be" % attrs['type'])
|
||||
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.decorators import link
|
||||
from rest_framework.decorators import detail_route
|
||||
from rest_framework.response import Response
|
||||
|
||||
from orchestra.api import router
|
||||
|
@ -11,28 +11,28 @@ from .serializers import DomainSerializer
|
|||
|
||||
|
||||
class DomainViewSet(AccountApiMixin, viewsets.ModelViewSet):
|
||||
model = Domain
|
||||
serializer_class = DomainSerializer
|
||||
filter_fields = ('name',)
|
||||
queryset = Domain.objects.all()
|
||||
|
||||
def get_queryset(self):
|
||||
qs = super(DomainViewSet, self).get_queryset()
|
||||
return qs.prefetch_related('records')
|
||||
|
||||
@link()
|
||||
@detail_route()
|
||||
def view_zone(self, request, pk=None):
|
||||
domain = self.get_object()
|
||||
return Response({
|
||||
'zone': domain.render_zone()
|
||||
})
|
||||
|
||||
def metadata(self, request):
|
||||
ret = super(DomainViewSet, self).metadata(request)
|
||||
def options(self, request):
|
||||
metadata = super(DomainViewSet, self).options(request)
|
||||
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
|
||||
}
|
||||
return ret
|
||||
return metadata
|
||||
|
||||
|
||||
router.register(r'domains', DomainViewSet)
|
||||
|
|
|
@ -21,7 +21,7 @@ class RecordSerializer(serializers.ModelSerializer):
|
|||
|
||||
class DomainSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
|
||||
""" 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:
|
||||
model = Domain
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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 orchestra.api import router, LogApiMixin
|
||||
|
@ -10,16 +10,16 @@ from .serializers import TicketSerializer, QueueSerializer
|
|||
|
||||
|
||||
class TicketViewSet(LogApiMixin, viewsets.ModelViewSet):
|
||||
model = Ticket
|
||||
queryset = Ticket.objects.all()
|
||||
serializer_class = TicketSerializer
|
||||
|
||||
@action()
|
||||
@detail_route()
|
||||
def mark_as_read(self, request, pk=None):
|
||||
ticket = self.get_object()
|
||||
ticket.mark_as_read_by(request.user)
|
||||
return Response({'status': 'Ticket marked as read'})
|
||||
|
||||
@action()
|
||||
@detail_route()
|
||||
def mark_as_unread(self, request, pk=None):
|
||||
ticket = self.get_object()
|
||||
ticket.mark_as_unread_by(request.user)
|
||||
|
@ -36,7 +36,7 @@ class QueueViewSet(LogApiMixin,
|
|||
mixins.ListModelMixin,
|
||||
mixins.RetrieveModelMixin,
|
||||
viewsets.GenericViewSet):
|
||||
model = Queue
|
||||
queryset = Queue.objects.all()
|
||||
serializer_class = QueueSerializer
|
||||
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ class MessageSerializer(serializers.HyperlinkedModelSerializer):
|
|||
class TicketSerializer(serializers.HyperlinkedModelSerializer):
|
||||
""" Validates if this zone generates a correct zone file """
|
||||
messages = MessageSerializer(required=False, many=True)
|
||||
is_read = serializers.SerializerMethodField('get_is_read')
|
||||
is_read = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Ticket
|
||||
|
|
|
@ -8,7 +8,7 @@ from .serializers import ListSerializer
|
|||
|
||||
|
||||
class ListViewSet(LogApiMixin, AccountApiMixin, SetPasswordApiMixin, viewsets.ModelViewSet):
|
||||
model = List
|
||||
queryset = List.objects.all()
|
||||
serializer_class = ListSerializer
|
||||
filter_fields = ('name',)
|
||||
|
||||
|
|
|
@ -8,6 +8,17 @@ from orchestra.core.validators import validate_name
|
|||
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?
|
||||
|
||||
class List(models.Model):
|
||||
|
@ -27,6 +38,8 @@ class List(models.Model):
|
|||
"Unselect this instead of deleting accounts."))
|
||||
password = None
|
||||
|
||||
objects = ListQuerySet.as_manager()
|
||||
|
||||
class Meta:
|
||||
unique_together = ('address_name', 'address_domain')
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
from django.core.validators import RegexValidator
|
||||
from django.forms import widgets
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework import serializers
|
||||
|
||||
from orchestra.api.serializers import HyperlinkedModelSerializer
|
||||
from orchestra.api.serializers import SetPasswordHyperlinkedSerializer
|
||||
from orchestra.contrib.accounts.serializers import AccountSerializerMixin
|
||||
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'])
|
||||
|
||||
|
||||
class ListSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
|
||||
class ListSerializer(AccountSerializerMixin, SetPasswordHyperlinkedSerializer):
|
||||
password = serializers.CharField(max_length=128, label=_('Password'),
|
||||
validators=[validate_password], write_only=True, required=False,
|
||||
widget=widgets.PasswordInput)
|
||||
write_only=True, style={'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)
|
||||
|
||||
class Meta:
|
||||
model = List
|
||||
fields = ('url', 'name', 'address_name', 'address_domain', 'admin_email')
|
||||
postonly_fields = ('name',)
|
||||
|
||||
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
|
||||
fields = ('url', 'name', 'password', 'address_name', 'address_domain', 'admin_email')
|
||||
postonly_fields = ('name', 'password')
|
||||
|
||||
def validate_address_domain(self, attrs, source):
|
||||
address_domain = attrs.get(source)
|
||||
address_name = attrs.get('address_name')
|
||||
if self.object:
|
||||
address_domain = address_domain or self.object.address_domain
|
||||
address_name = address_name or self.object.address_name
|
||||
if self.instance:
|
||||
address_domain = address_domain or self.instance.address_domain
|
||||
address_name = address_name or self.instance.address_name
|
||||
if address_name and not address_domain:
|
||||
raise serializers.ValidationError(
|
||||
_("address_domains should should be provided when providing an addres_name"))
|
||||
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):
|
||||
model = Address
|
||||
queryset = Address.objects.all()
|
||||
serializer_class = AddressSerializer
|
||||
|
||||
|
||||
|
||||
class MailboxViewSet(LogApiMixin, SetPasswordApiMixin, AccountApiMixin, viewsets.ModelViewSet):
|
||||
model = Mailbox
|
||||
queryset = Mailbox.objects.all()
|
||||
serializer_class = MailboxSerializer
|
||||
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ class UNIXUserMaildirBackend(ServiceController):
|
|||
|
||||
def delete(self, 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("""
|
||||
{ sleep 2 && killall -u %(user)s -s KILL; } &
|
||||
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 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.core.validators import validate_password
|
||||
|
||||
from .models import Mailbox, Address
|
||||
|
||||
|
@ -32,10 +31,7 @@ class RelatedAddressSerializer(AccountSerializerMixin, serializers.HyperlinkedMo
|
|||
return get_object_or_404(queryset, name=data['name'])
|
||||
|
||||
|
||||
class MailboxSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
|
||||
password = serializers.CharField(max_length=128, label=_('Password'),
|
||||
validators=[validate_password], write_only=True, required=False,
|
||||
widget=widgets.PasswordInput)
|
||||
class MailboxSerializer(AccountSerializerMixin, SetPasswordHyperlinkedSerializer):
|
||||
addresses = RelatedAddressSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
|
@ -43,22 +39,7 @@ class MailboxSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
|
|||
fields = (
|
||||
'url', 'name', 'password', 'filtering', 'custom_filtering', 'addresses', 'is_active'
|
||||
)
|
||||
postonly_fields = ('name',)
|
||||
|
||||
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)
|
||||
postonly_fields = ('name', 'password')
|
||||
|
||||
|
||||
class RelatedMailboxSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
|
||||
|
@ -73,13 +54,14 @@ class RelatedMailboxSerializer(AccountSerializerMixin, serializers.HyperlinkedMo
|
|||
|
||||
class AddressSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
|
||||
domain = RelatedDomainSerializer()
|
||||
mailboxes = RelatedMailboxSerializer(many=True, allow_add_remove=True, required=False)
|
||||
mailboxes = RelatedMailboxSerializer(many=True, required=False) #allow_add_remove=True
|
||||
|
||||
class Meta:
|
||||
model = Address
|
||||
fields = ('url', 'name', 'domain', 'mailboxes', 'forward')
|
||||
|
||||
def validate(self, attrs):
|
||||
attrs = super(AddressSerializer, self).validate(attrs)
|
||||
if not attrs['mailboxes'] and not attrs['forward']:
|
||||
raise serializers.ValidationError("A mailbox or forward address should be provided.")
|
||||
return attrs
|
||||
|
|
|
@ -25,7 +25,9 @@ class Operation():
|
|||
self.backend = backend
|
||||
# instance should maintain any dynamic attribute until backend execution
|
||||
# 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)
|
||||
print('aa', getattr(self.instance, 'password', 'NOOOO'), id(self.instance))
|
||||
self.action = action
|
||||
self.servers = servers
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ from .models import BackendLog
|
|||
@receiver(post_save, dispatch_uid='orchestration.post_save_collector')
|
||||
def post_save_collector(sender, *args, **kwargs):
|
||||
if sender not in [BackendLog, Operation]:
|
||||
instance = kwargs.get('instance')
|
||||
OperationsMiddleware.collect(Operation.SAVE, **kwargs)
|
||||
|
||||
|
||||
|
|
|
@ -8,7 +8,8 @@ from .serializers import OrderSerializer
|
|||
|
||||
|
||||
class OrderViewSet(AccountApiMixin, viewsets.ModelViewSet):
|
||||
model = Order
|
||||
queryset = Order.objects.all()
|
||||
serializer_class = OrderSerializer
|
||||
|
||||
|
||||
router.register(r'orders', OrderViewSet)
|
||||
|
|
|
@ -8,13 +8,13 @@ from .serializers import PaymentSourceSerializer, TransactionSerializer
|
|||
|
||||
|
||||
class PaymentSourceViewSet(LogApiMixin, AccountApiMixin, viewsets.ModelViewSet):
|
||||
model = PaymentSource
|
||||
serializer_class = PaymentSourceSerializer
|
||||
queryset = PaymentSource.objects.all()
|
||||
|
||||
|
||||
class TransactionViewSet(LogApiMixin, viewsets.ModelViewSet):
|
||||
model = Transaction
|
||||
serializer_class = TransactionSerializer
|
||||
queryset = Transaction.objects.all()
|
||||
|
||||
|
||||
router.register(r'payment-sources', PaymentSourceViewSet)
|
||||
|
|
|
@ -28,6 +28,7 @@ class PaymentSourceSerializer(AccountSerializerMixin, serializers.HyperlinkedMod
|
|||
return serializer_class().to_native(obj.data)
|
||||
return obj.data
|
||||
|
||||
# TODO
|
||||
def metadata(self):
|
||||
meta = super(PaymentSourceSerializer, self).metadata()
|
||||
meta['data'] = {
|
||||
|
|
|
@ -7,8 +7,8 @@ from .models import Resource, ResourceData
|
|||
|
||||
|
||||
class ResourceSerializer(serializers.ModelSerializer):
|
||||
name = serializers.SerializerMethodField('get_name')
|
||||
unit = serializers.Field()
|
||||
name = serializers.SerializerMethodField()
|
||||
unit = serializers.ReadOnlyField()
|
||||
|
||||
class Meta:
|
||||
model = ResourceData
|
||||
|
@ -72,19 +72,19 @@ def insert_resource_serializers():
|
|||
viewset = router.get_viewset(model)
|
||||
viewset.serializer_class.validate_resources = validate_resources
|
||||
|
||||
old_metadata = viewset.metadata
|
||||
def metadata(self, request, resources=resources):
|
||||
old_options = viewset.options
|
||||
def options(self, request, resources=resources):
|
||||
""" Provides available resources description """
|
||||
ret = old_metadata(self, request)
|
||||
ret['available_resources'] = [
|
||||
metadata = old_options(self, request)
|
||||
metadata.data['available_resources'] = [
|
||||
{
|
||||
'name': resource.name,
|
||||
'on_demand': resource.on_demand,
|
||||
'default_allocation': resource.default_allocation
|
||||
} for resource in resources
|
||||
]
|
||||
return ret
|
||||
viewset.metadata = metadata
|
||||
return metadata
|
||||
viewset.options = options
|
||||
|
||||
if database_ready():
|
||||
insert_resource_serializers()
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
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):
|
||||
service = models.CharField(_("service"), max_length=32,
|
||||
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
|
||||
databases = VirtualDatabaseRelation('databases.Database')
|
||||
objects = SaaSQuerySet.as_manager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = "SaaS"
|
||||
|
|
|
@ -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,
|
||||
widget=widgets.ReadOnlyWidget('<strong>Unknown password</strong>'),
|
||||
validators=[
|
||||
validators.validate_password,
|
||||
RegexValidator(r'^[^"\'\\]+$',
|
||||
_('Enter a valid password. '
|
||||
'This value may contain any ascii character except for '
|
||||
|
|
|
@ -9,7 +9,7 @@ from .serializers import SystemUserSerializer
|
|||
|
||||
|
||||
class SystemUserViewSet(LogApiMixin, AccountApiMixin, SetPasswordApiMixin, viewsets.ModelViewSet):
|
||||
model = SystemUser
|
||||
queryset = SystemUser.objects.all()
|
||||
serializer_class = SystemUserSerializer
|
||||
filter_fields = ('username',)
|
||||
|
||||
|
|
|
@ -3,9 +3,8 @@ from django.shortcuts import get_object_or_404
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from orchestra.api.serializers import HyperlinkedModelSerializer
|
||||
from orchestra.api.serializers import SetPasswordHyperlinkedSerializer
|
||||
from orchestra.contrib.accounts.serializers import AccountSerializerMixin
|
||||
from orchestra.core.validators import validate_password
|
||||
|
||||
from .models import SystemUser
|
||||
from .validators import validate_home
|
||||
|
@ -21,36 +20,25 @@ class GroupSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerial
|
|||
return get_object_or_404(queryset, username=data['username'])
|
||||
|
||||
|
||||
class SystemUserSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
|
||||
password = serializers.CharField(max_length=128, label=_('Password'),
|
||||
validators=[validate_password], write_only=True, required=False,
|
||||
widget=widgets.PasswordInput)
|
||||
groups = GroupSerializer(many=True, allow_add_remove=True, required=False)
|
||||
class SystemUserSerializer(AccountSerializerMixin, SetPasswordHyperlinkedSerializer):
|
||||
groups = GroupSerializer(many=True, required=False)
|
||||
|
||||
class Meta:
|
||||
model = SystemUser
|
||||
fields = (
|
||||
'url', 'username', 'password', 'home', 'directory', 'shell', 'groups', 'is_active',
|
||||
)
|
||||
postonly_fields = ('username',)
|
||||
postonly_fields = ('username', 'password')
|
||||
|
||||
def validate(self, attrs):
|
||||
attrs = super(SystemUserSerializer, self).validate(attrs)
|
||||
user = SystemUser(
|
||||
username=attrs.get('username') or self.object.username,
|
||||
shell=attrs.get('shell') or self.object.shell,
|
||||
username=attrs.get('username') or self.instance.username,
|
||||
shell=attrs.get('shell') or self.instance.shell,
|
||||
)
|
||||
validate_home(user, attrs, self.account)
|
||||
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):
|
||||
groups = attrs.get(source)
|
||||
if groups:
|
||||
|
@ -59,9 +47,3 @@ class SystemUserSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
|
|||
raise serializers.ValidationError(
|
||||
_("Do not make the user member of its group"))
|
||||
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):
|
||||
model = WebApp
|
||||
queryset = WebApp.objects.all()
|
||||
serializer_class = WebAppSerializer
|
||||
filter_fields = ('name',)
|
||||
|
||||
def metadata(self, request):
|
||||
ret = super(WebAppViewSet, self).metadata(request)
|
||||
def options(self, request):
|
||||
metadata = super(WebAppViewSet, self).options(request)
|
||||
names = [
|
||||
'WEBAPPS_BASE_DIR', 'WEBAPPS_TYPES', 'WEBAPPS_WEBAPP_OPTIONS',
|
||||
'WEBAPPS_PHP_DISABLED_FUNCTIONS', 'WEBAPPS_DEFAULT_TYPE'
|
||||
]
|
||||
ret['settings'] = {
|
||||
metadata.data['settings'] = {
|
||||
name.lower(): getattr(settings, name, None) for name in names
|
||||
}
|
||||
return ret
|
||||
return metadata
|
||||
|
||||
|
||||
router.register(r'webapps', WebAppViewSet)
|
||||
|
|
|
@ -8,6 +8,9 @@ from .. import settings
|
|||
|
||||
class WebAppServiceMixin(object):
|
||||
model = 'webapps.WebApp'
|
||||
related_models = (
|
||||
('webapps.WebAppOption', 'webapp'),
|
||||
)
|
||||
directive = None
|
||||
|
||||
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.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):
|
||||
options = OptionField(required=False)
|
||||
options = OptionSerializer(required=False)
|
||||
|
||||
class Meta:
|
||||
model = WebApp
|
||||
fields = ('url', 'name', 'type', 'options')
|
||||
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):
|
||||
model = Website
|
||||
queryset = Website.objects.all()
|
||||
serializer_class = WebsiteSerializer
|
||||
filter_fields = ('name',)
|
||||
|
||||
def metadata(self, request):
|
||||
ret = super(WebsiteViewSet, self).metadata(request)
|
||||
def options(self, request):
|
||||
metadata = super(WebsiteViewSet, self).options(request)
|
||||
names = ['WEBSITES_OPTIONS', 'WEBSITES_PORT_CHOICES']
|
||||
ret['settings'] = {
|
||||
metadata.data['settings'] = {
|
||||
name.lower(): getattr(settings, name, None) for name in names
|
||||
}
|
||||
return ret
|
||||
return metadata
|
||||
|
||||
|
||||
router.register(r'websites', WebsiteViewSet)
|
||||
|
|
|
@ -127,13 +127,13 @@ class Apache2Backend(ServiceController):
|
|||
echo -n "$state" > /dev/shm/restart.apache2
|
||||
if [[ $UPDATED == 1 ]]; then
|
||||
if [[ $locked == 0 ]]; then
|
||||
service apache2 satus && service apache2 reload || service apache2 start
|
||||
service apache2 status && service apache2 reload || service apache2 start
|
||||
else
|
||||
echo "Apache2Backend RESTART" >> /dev/shm/restart.apache2
|
||||
fi
|
||||
elif [[ "$state" =~ .*RESTART$ ]]; then
|
||||
rm /dev/shm/restart.apache2
|
||||
service apache2 satus && service apache2 reload || service apache2 start
|
||||
service apache2 status && service apache2 reload || service apache2 start
|
||||
fi""")
|
||||
)
|
||||
super(Apache2Backend, self).commit()
|
||||
|
|
|
@ -2,11 +2,10 @@ from django.core.exceptions import ValidationError
|
|||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework import serializers
|
||||
|
||||
from orchestra.api.fields import OptionField
|
||||
from orchestra.api.serializers import HyperlinkedModelSerializer
|
||||
from orchestra.contrib.accounts.serializers import AccountSerializerMixin
|
||||
|
||||
from .models import Website, Content
|
||||
from .models import Website, Content, WebsiteDirective
|
||||
from .validators import validate_domain_protocol
|
||||
|
||||
|
||||
|
@ -22,7 +21,7 @@ class RelatedDomainSerializer(AccountSerializerMixin, serializers.HyperlinkedMod
|
|||
|
||||
class RelatedWebAppSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
|
||||
class Meta:
|
||||
# model = Content.webapp.field.rel.to
|
||||
model = Content.webapp.field.rel.to
|
||||
fields = ('url', 'name', 'type')
|
||||
|
||||
def from_native(self, data, files=None):
|
||||
|
@ -41,11 +40,23 @@ class ContentSerializer(serializers.HyperlinkedModelSerializer):
|
|||
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):
|
||||
domains = RelatedDomainSerializer(many=True, allow_add_remove=True, required=False)
|
||||
contents = ContentSerializer(required=False, many=True, allow_add_remove=True,
|
||||
domains = RelatedDomainSerializer(many=True, required=False) #allow_add_remove=True
|
||||
contents = ContentSerializer(required=False, many=True, #allow_add_remove=True,
|
||||
source='content_set')
|
||||
directives = OptionField(required=False)
|
||||
directives = DirectiveSerializer(required=False)
|
||||
|
||||
class Meta:
|
||||
model = Website
|
||||
|
@ -62,3 +73,30 @@ class WebsiteSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
|
|||
self.add_error(None, e)
|
||||
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
|
||||
def render(self, *args, **kwargs):
|
||||
value = old_render(self, *args, **kwargs)
|
||||
value = re.sub(r'^<ul id=(.*)>',
|
||||
r'<ul id=\1 style="padding-left:%ipx">' % padding, value, 1)
|
||||
value = re.sub(r'^<ul id=([^>]+)>',
|
||||
r'<ul id=\1 style="padding-left:%ipx">' % padding, value, 1)
|
||||
return mark_safe(value)
|
||||
widget.render = render
|
||||
return widget
|
||||
|
|
|
@ -6,11 +6,12 @@ class OrchestraPermissionBackend(DjangoModelPermissions):
|
|||
""" Permissions according to each user """
|
||||
|
||||
def has_permission(self, request, view):
|
||||
model_cls = getattr(view, 'model', None)
|
||||
if not model_cls:
|
||||
queryset = getattr(view, 'queryset', None)
|
||||
if queryset is None:
|
||||
name = resolve(request.path).url_name
|
||||
return name == 'api-root'
|
||||
|
||||
model_cls = queryset.model
|
||||
perms = self.get_required_permissions(request.method, model_cls)
|
||||
if (request.user and
|
||||
request.user.is_authenticated() and
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
{% extends "rest_framework/base.html" %}
|
||||
{% load rest_framework utils staticfiles %}
|
||||
|
||||
{% block head %}
|
||||
{% block meta %}
|
||||
{{ block.super }}
|
||||
<link rel="icon" href="{% static "orchestra/images/favicon.png" %}" type="image/png" />
|
||||
{% 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 userlinks %}
|
||||
|
|
|
@ -24,12 +24,14 @@ def orchestra_version():
|
|||
def rest_to_admin_url(context):
|
||||
""" returns the admin equivelent url of the current REST API view """
|
||||
view = context['view']
|
||||
model = getattr(view, 'model', None)
|
||||
queryset = getattr(view, 'queryset', None)
|
||||
model = queryset.model if queryset else None
|
||||
url = 'admin:index'
|
||||
args = []
|
||||
if model:
|
||||
url = 'admin:%s_%s' % (model._meta.app_label, model._meta.module_name)
|
||||
pk = view.kwargs.get(view.pk_url_kwarg)
|
||||
opts = model._meta
|
||||
url = 'admin:%s_%s' % (opts.app_label, opts.model_name)
|
||||
pk = view.kwargs.get(view.lookup_field)
|
||||
if pk:
|
||||
url += '_change'
|
||||
args = [pk]
|
||||
|
|
Loading…
Reference in New Issue