authentic/src/authentic2/api_mixins.py

89 lines
3.6 KiB
Python

# authentic2 - versatile identity manager
# Copyright (C) 2010-2018 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.db import transaction
from django.utils.translation import ugettext_lazy as _
from rest_framework import status
from rest_framework.exceptions import APIException, ValidationError
from rest_framework.settings import api_settings
from rest_framework.utils import model_meta
class Conflict(APIException):
status_code = status.HTTP_409_CONFLICT
default_detail = _('Cannot process request because of conflicting resources.')
default_code = 'conflict'
class GetOrCreateMixinView(object):
_lookup_object = None
def get_object(self):
if self._lookup_object is not None:
return self._lookup_object
return super(GetOrCreateMixinView, self).get_object()
def _get_lookup_keys(self, name):
return self.request.GET.getlist(name)
def _lookup_instance(self, keys):
kwargs = {}
for key in keys:
try:
kwargs[key] = self.request.data[key]
except KeyError:
raise ValidationError({api_settings.NON_FIELD_ERRORS_KEY: ['key %r is missing' % key]})
qs = self.get_queryset()
try:
return qs.get(**kwargs)
except qs.model.DoesNotExist:
return None
except qs.model.MultipleObjectsReturned:
raise Conflict(
'retrieved several instances of model %s for key attributes %s' % (qs.model.__name__, kwargs)
)
def _validate_get_keys(self, keys):
# Remove many-to-many relationships from validated_data.
# They are not valid arguments to the default `.create()` method,
# as they require that the instance has already been saved.
info = model_meta.get_field_info(self.get_queryset().model)
errors = []
for key in keys:
if key not in info.fields:
errors.append('unknown key %r' % key)
if key in info.relations:
errors.append('relation key %r cannot be used for lookup' % key)
if errors:
raise ValidationError({api_settings.NON_FIELD_ERRORS_KEY: errors})
@transaction.atomic
def create(self, request, *args, **kwargs):
get_or_create_keys = self._get_lookup_keys('get_or_create')
if get_or_create_keys:
self._validate_get_keys(get_or_create_keys)
self._lookup_object = self._lookup_instance(get_or_create_keys)
if self._lookup_object is not None:
return self.retrieve(request, *args, **kwargs)
update_or_create_keys = self._get_lookup_keys('update_or_create')
if update_or_create_keys:
self._validate_get_keys(update_or_create_keys)
self._lookup_object = self._lookup_instance(update_or_create_keys)
if self._lookup_object is not None:
return self.partial_update(request, *args, **kwargs)
return super(GetOrCreateMixinView, self).create(request, *args, **kwargs)