diff --git a/src/authentic2/api_views.py b/src/authentic2/api_views.py index 3efc2764f..f3266f402 100644 --- a/src/authentic2/api_views.py +++ b/src/authentic2/api_views.py @@ -25,6 +25,7 @@ from django.contrib.auth import get_user_model from django.contrib.auth.hashers import identify_hasher from django.core.exceptions import MultipleObjectsReturned from django.utils.translation import ugettext as _ +from django.utils.text import slugify from django.utils.encoding import force_text from django.utils.dateparse import parse_datetime from django.views.decorators.vary import vary_on_headers @@ -887,13 +888,31 @@ class RoleMembershipsAPI(ExceptionHandlerMixin, APIView): role_memberships = RoleMembershipsAPI.as_view() +class SlugFromNameDefault: + requires_context = False + serializer_instance = None + + def set_context(self, instance): + self.serializer_instance = instance + + def __call__(self): + name = self.serializer_instance.context['request'].data.get('name', '') + return slugify(name) + + class BaseOrganizationalUnitSerializer(serializers.ModelSerializer): + slug = serializers.SlugField( + required=False, + allow_blank=False, + max_length=256, + default=SlugFromNameDefault(), + ) class Meta: model = get_ou_model() fields = '__all__' -class OrganizationalUnitAPI(ExceptionHandlerMixin, ModelViewSet): +class OrganizationalUnitAPI(api_mixins.GetOrCreateMixinView, ExceptionHandlerMixin, ModelViewSet): permission_classes = (DjangoPermission('a2_rbac.search_organizationalunit'),) serializer_class = BaseOrganizationalUnitSerializer lookup_field = 'uuid' diff --git a/tests/test_api.py b/tests/test_api.py index 483224ad6..cbfa99132 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -30,6 +30,7 @@ from django.contrib.contenttypes.models import ContentType from django.core import mail from django.urls import reverse from django.utils.encoding import force_text +from django.utils.text import slugify from django.utils.timezone import now from django.utils.http import urlencode @@ -1254,6 +1255,63 @@ def test_api_post_role_no_ou(app, superuser): assert role.ou == get_default_ou() +def test_api_post_ou_no_slug(app, superuser): + app.authorization = ('Basic', (superuser.username, superuser.username)) + OU = get_ou_model() + + ou_data = { + 'name': 'Some Organizational Unit', + } + resp = app.post_json('/api/ous/', params=ou_data) + uuid = resp.json['uuid'] + ou = OU.objects.get(uuid=uuid) + assert ou.id != get_default_ou().id + assert ou.slug == slugify(ou.name) + assert ou.slug == slugify(ou_data['name']) + # another call with same ou name + ou_data = { + 'name': 'Some Organizational Unit', + } + resp = app.post_json('/api/ous/', params=ou_data, status=400) + assert resp.json['errors']['__all__'] == [ + "The fields name must make a unique set.", + "The fields slug must make a unique set." + ] + # no slug no name + ou_data = { + 'id': 42, + } + resp = app.post_json('/api/ous/', params=ou_data, status=400) + assert resp.json['errors']['name'] == ['This field is required.'] + + +def test_api_post_ou_get_or_create(app, superuser): + app.authorization = ('Basic', (superuser.username, superuser.username)) + OU = get_ou_model() + # first get-or-create? -> create + ou_data = { + 'name': 'Some Organizational Unit', + } + resp = app.post_json('/api/ous/', params=ou_data) + uuid = resp.json['uuid'] + ou = OU.objects.get(uuid=uuid) + # second get-or-create? -> get + ou_data = { + 'name': 'Some Organizational Unit', + 'slug': ou.slug, + } + resp = app.post_json('/api/ous/?get_or_create=slug', params=ou_data) + assert resp.json['uuid'] == ou.uuid + # update-or-create? -> update + ou_data = { + 'name': 'Another name', + 'slug': ou.slug, + } + resp = app.post_json('/api/ous/?update_or_create=slug', params=ou_data) + assert resp.json['uuid'] == ou.uuid + assert OU.objects.get(uuid=resp.json['uuid']).name == ou_data['name'] + + def test_api_post_role_unauthorized(app, simple_user, ou1): app.authorization = ('Basic', (simple_user.username, simple_user.username)) Role = get_role_model()