diff --git a/mayan/apps/user_management/api_views.py b/mayan/apps/user_management/api_views.py index 47b467fe72..09eadbef79 100644 --- a/mayan/apps/user_management/api_views.py +++ b/mayan/apps/user_management/api_views.py @@ -1,15 +1,13 @@ from __future__ import unicode_literals -from django.contrib.auth import get_user_model from django.contrib.auth.models import Group -from django.shortcuts import get_object_or_404 -from rest_framework import generics +from rest_framework import generics, status +from rest_framework.decorators import action +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response -from mayan.apps.acls.models import AccessControlList -from mayan.apps.common.mixins import ExternalObjectMixin -from mayan.apps.rest_api.filters import MayanObjectPermissionsFilter -from mayan.apps.rest_api.permissions import MayanPermission +from mayan.apps.rest_api.viewsets import MayanAPIModelViewSet from .permissions import ( permission_group_create, permission_group_delete, permission_group_edit, @@ -17,141 +15,147 @@ from .permissions import ( permission_user_edit, permission_user_view ) from .serializers import ( - GroupSerializer, UserSerializer#, UserGroupListSerializer + CurrentUserSerializer, GroupUserAddRemoveSerializer, GroupSerializer, + UserGroupAddRemoveSerializer, UserSerializer ) +from .querysets import get_user_queryset -class APICurrentUserView(generics.RetrieveUpdateDestroyAPIView): - """ - delete: Delete the current user. - get: Return the details of the current user. - patch: Partially edit the current user. - put: Edit the current user. - """ - serializer_class = UserSerializer +class CurrentUserAPIView(generics.RetrieveUpdateAPIView): + permission_classes = (IsAuthenticated,) + serializer_class = CurrentUserSerializer def get_object(self): return self.request.user -class APIGroupListView(generics.ListCreateAPIView): - """ - get: Returns a list of all the groups. - post: Create a new group. - """ - filter_backends = (MayanObjectPermissionsFilter,) - mayan_object_permissions = {'GET': (permission_group_view,)} - mayan_view_permissions = {'POST': (permission_group_create,)} - permission_classes = (MayanPermission,) - queryset = Group.objects.order_by('id') +class GroupAPIViewSet(MayanAPIModelViewSet): + lookup_url_kwarg = 'group_id' + object_permission_map = { + 'destroy': permission_group_delete, + 'list': permission_group_view, + 'partial_update': permission_group_edit, + 'retrieve': permission_group_view, + 'update': permission_group_edit, + 'user_add': permission_group_edit, + 'user_list': permission_group_view, + 'user_remove': permission_group_edit + } + queryset = Group.objects.all() serializer_class = GroupSerializer - - -class APIGroupView(generics.RetrieveUpdateDestroyAPIView): - """ - delete: Delete the selected group. - get: Return the details of the selected group. - patch: Partially edit the selected group. - put: Edit the selected group. - """ - lookup_url_kwarg = 'group_pk' - mayan_object_permissions = { - 'GET': (permission_group_view,), - 'PUT': (permission_group_edit,), - 'PATCH': (permission_group_edit,), - 'DELETE': (permission_group_delete,) - } - permission_classes = (MayanPermission,) - queryset = Group.objects.order_by('id') - serializer_class = GroupSerializer - - -class APIUserListView(generics.ListCreateAPIView): - """ - get: Returns a list of all the users. - post: Create a new user. - """ - filter_backends = (MayanObjectPermissionsFilter,) - mayan_object_permissions = {'GET': (permission_user_view,)} - mayan_view_permissions = {'POST': (permission_user_create,)} - permission_classes = (MayanPermission,) - queryset = get_user_model().objects.all() - serializer_class = UserSerializer - - -class APIUserView(generics.RetrieveUpdateDestroyAPIView): - """ - delete: Delete the selected user. - get: Return the details of the selected user. - patch: Partially edit the selected user. - put: Edit the selected user. - """ - lookup_url_kwarg = 'user_pk' - mayan_object_permissions = { - 'GET': (permission_user_view,), - 'PUT': (permission_user_edit,), - 'PATCH': (permission_user_edit,), - 'DELETE': (permission_user_delete,) - } - permission_classes = (MayanPermission,) - queryset = get_user_model().objects.all() - serializer_class = UserSerializer - - -class APIUserGroupList(ExternalObjectMixin, generics.ListCreateAPIView): - """ - get: Returns a list of all the groups to which an user belongs. - post: Add a user to a list of groups. - """ - external_object_pk_url_kwarg = 'user_pk' - filter_backends = (MayanObjectPermissionsFilter,) - mayan_object_permissions = { - 'GET': (permission_group_view,), - 'POST': (permission_group_edit,) + view_permission_map = { + 'create': permission_group_create } - def get_external_object_permission(self): - if self.request.method == 'POST': - return permission_user_edit - else: - return permission_user_view - - def get_external_object_queryset(self): - return get_user_model().objects.exclude(is_staff=True).exclude( - is_superuser=True + @action( + detail=True, lookup_url_kwarg='group_id', methods=('post',), + serializer_class=GroupUserAddRemoveSerializer, + url_name='user-add', url_path='users/add' + ) + def user_add(self, request, *args, **kwargs): + instance = self.get_object() + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.user_add(instance=instance) + headers = self.get_success_headers(data=serializer.data) + return Response( + serializer.data, status=status.HTTP_200_OK, headers=headers ) - def get_serializer(self, *args, **kwargs): - if not self.request: - return None + @action( + detail=True, lookup_url_kwarg='group_id', + serializer_class=UserSerializer, url_name='user-list', + url_path='users' + ) + def user_list(self, request, *args, **kwargs): + queryset = self.get_object().get_users(_user=self.request.user) + page = self.paginate_queryset(queryset) - return super(APIUserGroupList, self).get_serializer(*args, **kwargs) + serializer = self.get_serializer( + queryset, many=True, context={'request': request} + ) - def get_serializer_class(self): - if self.request.method == 'POST': - return UserSerializer - else: - return GroupSerializer + if page is not None: + return self.get_paginated_response(serializer.data) - def get_serializer_context(self): - """ - Extra context provided to the serializer class. - """ - context = super(APIUserGroupList, self).get_serializer_context() - if self.kwargs: - context.update( - { - 'user': self.get_user(), - } - ) + return Response(serializer.data) - return context + @action( + detail=True, lookup_url_kwarg='group_id', + methods=('post',), serializer_class=GroupUserAddRemoveSerializer, + url_name='user-remove', url_path='users/remove' + ) + def user_remove(self, request, *args, **kwargs): + instance = self.get_object() + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.user_remove(instance=instance) + headers = self.get_success_headers(data=serializer.data) + return Response( + serializer.data, status=status.HTTP_200_OK, headers=headers + ) - def get_queryset(self): - return self.get_user().groups.order_by('id') - def get_user(self): - return self.get_external_object() +class UserAPIViewSet(MayanAPIModelViewSet): + lookup_url_kwarg = 'user_id' + object_permission_map = { + 'destroy': permission_user_delete, + 'group-list': permission_user_view, + 'list': permission_user_view, + 'partial_update': permission_user_edit, + 'retrieve': permission_user_view, + 'update': permission_user_edit, + } + queryset = get_user_queryset() + serializer_class = UserSerializer + view_permission_map = { + 'create': permission_user_create + } - def perform_create(self, serializer): - return serializer.save(user=self.get_object(), _user=self.request.user) + @action( + detail=True, lookup_url_kwarg='user_id', methods=('post',), + serializer_class=UserGroupAddRemoveSerializer, + url_name='group-add', url_path='group/add' + ) + def group_add(self, request, *args, **kwargs): + instance = self.get_object() + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.group_add(instance=instance) + headers = self.get_success_headers(data=serializer.data) + return Response( + serializer.data, status=status.HTTP_200_OK, headers=headers + ) + + @action( + detail=True, lookup_url_kwarg='user_id', + serializer_class=GroupSerializer, url_name='group-list', + url_path='groups' + ) + def group_list(self, request, *args, **kwargs): + queryset = self.get_object().get_groups(_user=self.request.user) + page = self.paginate_queryset(queryset) + + serializer = self.get_serializer( + queryset, many=True, context={'request': request} + ) + + if page is not None: + return self.get_paginated_response(serializer.data) + + return Response(serializer.data) + + @action( + detail=True, lookup_url_kwarg='user_id', + methods=('post',), serializer_class=UserGroupAddRemoveSerializer, + url_name='group-remove', url_path='groups/remove' + ) + def group_remove(self, request, *args, **kwargs): + instance = self.get_object() + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.group_remove(instance=instance) + headers = self.get_success_headers(data=serializer.data) + return Response( + serializer.data, status=status.HTTP_200_OK, headers=headers + ) diff --git a/mayan/apps/user_management/apps.py b/mayan/apps/user_management/apps.py index 2ce6953c21..3398cd0658 100644 --- a/mayan/apps/user_management/apps.py +++ b/mayan/apps/user_management/apps.py @@ -33,7 +33,10 @@ from .links import ( link_user_set_password, link_user_setup, text_user_label, separator_user_label ) -from .methods import method_get_absolute_url +from .methods import ( + method_group_get_users, method_group_user_add, method_group_user_remove, + method_user_get_absolute_url, method_user_get_groups +) from .permissions import ( permission_group_delete, permission_group_edit, permission_group_view, permission_user_delete, permission_user_edit, @@ -63,6 +66,14 @@ class UserManagementApp(MayanAppConfig): serializer_class='mayan.apps.user_management.serializers.UserSerializer' ) + # Silence UnorderedObjectListWarning + # "Pagination may yield inconsistent result" + # Remove on Django 2.x + Group._meta.ordering = ('name',) + Group.add_to_class(name='get_users', value=method_group_get_users) + Group.add_to_class(name='user_add', value=method_group_user_add) + Group.add_to_class(name='user_remove', value=method_group_user_remove) + MetadataLookup( description=_('All the groups.'), name='groups', value=lookup_get_groups @@ -124,7 +135,10 @@ class UserManagementApp(MayanAppConfig): ) User.add_to_class( - name='get_absolute_url', value=method_get_absolute_url + name='get_absolute_url', value=method_user_get_absolute_url + ) + User.add_to_class( + name='get_groups', value=method_user_get_groups ) menu_list_facet.bind_links( diff --git a/mayan/apps/user_management/methods.py b/mayan/apps/user_management/methods.py index 23bd71b0a8..9f572ac68d 100644 --- a/mayan/apps/user_management/methods.py +++ b/mayan/apps/user_management/methods.py @@ -1,9 +1,59 @@ from __future__ import unicode_literals +from django.apps import apps +from django.db import transaction from django.shortcuts import reverse +from .events import event_group_edited, event_user_edited +from .permissions import permission_user_view +from .querysets import get_user_queryset -def method_get_absolute_url(self): + +def method_group_get_users(self, _user): + AccessControlList = apps.get_model( + app_label='acls', model_name='AccessControlList' + ) + + return AccessControlList.objects.restrict_queryset( + permission=permission_user_view, queryset=get_user_queryset(), + user=_user + ) + + +def method_group_user_add(self, user, _user): + with transaction.atomic(): + self.user_set.add(user) + event_group_edited.commit( + actor=_user, target=self + ) + event_user_edited.commit( + actor=_user, target=user + ) + + +def method_group_user_remove(self, user, _user): + with transaction.atomic(): + self.user_set.remove(user) + event_group_edited.commit( + actor=_user, target=self + ) + event_user_edited.commit( + actor=_user, target=user + ) + + +def method_user_get_absolute_url(self): return reverse( viewname='user_management:user_details', kwargs={'user_id': self.pk} ) + + +def method_user_get_groups(self, _user): + AccessControlList = apps.get_model( + app_label='acls', model_name='AccessControlList' + ) + + return AccessControlList.objects.restrict_queryset( + permission=permission_user_view, queryset=self.groups.all(), + user=_user + ) diff --git a/mayan/apps/user_management/querysets.py b/mayan/apps/user_management/querysets.py new file mode 100644 index 0000000000..9816561b43 --- /dev/null +++ b/mayan/apps/user_management/querysets.py @@ -0,0 +1,7 @@ +from __future__ import absolute_import, unicode_literals + +from django.contrib.auth import get_user_model + + +def get_user_queryset(): + return get_user_model().objects.filter(is_superuser=False, is_staff=False) diff --git a/mayan/apps/user_management/serializers.py b/mayan/apps/user_management/serializers.py index 6cd8205d46..b1ff9183a0 100644 --- a/mayan/apps/user_management/serializers.py +++ b/mayan/apps/user_management/serializers.py @@ -3,41 +3,112 @@ from __future__ import unicode_literals from django.contrib.auth import get_user_model from django.contrib.auth.models import Group from django.contrib.auth.password_validation import validate_password -from django.core.exceptions import PermissionDenied from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from rest_framework.exceptions import ValidationError -from mayan.apps.acls.models import AccessControlList +from mayan.apps.rest_api.mixins import ExternalObjectListSerializerMixin -from .permissions import permission_group_edit, permission_group_view +from .permissions import permission_group_edit, permission_user_edit +from .querysets import get_user_queryset class GroupSerializer(serializers.HyperlinkedModelSerializer): - users_count = serializers.SerializerMethodField() + user_add_url = serializers.HyperlinkedIdentityField( + lookup_url_kwarg='group_id', view_name='rest_api:group-user-add' + ) + + user_list_url = serializers.HyperlinkedIdentityField( + lookup_url_kwarg='group_id', view_name='rest_api:group-user-list' + ) + + user_remove_url = serializers.HyperlinkedIdentityField( + lookup_url_kwarg='group_id', view_name='rest_api:group-user-remove' + ) class Meta: extra_kwargs = { 'url': { - 'lookup_field': 'pk', 'lookup_url_kwarg': 'group_pk', + 'lookup_url_kwarg': 'group_id', 'view_name': 'rest_api:group-detail' } } - fields = ('id', 'name', 'url', 'users_count') + fields = ( + 'id', 'name', 'url', 'user_add_url', 'user_list_url', + 'user_remove_url' + ) model = Group - def get_users_count(self, instance): - return instance.user_set.count() + +class GroupUserAddRemoveSerializer(ExternalObjectListSerializerMixin, serializers.Serializer): + user_id = serializers.CharField( + help_text=_( + 'Primary key of the user that will be added or removed.' + ), required=False, write_only=True + ) + users_id_list = serializers.CharField( + help_text=_( + 'Comma separated list of user primary keys that will be added or ' + 'removed.' + ), required=False, write_only=True + ) + + class Meta: + external_object_list_queryset = get_user_queryset() + external_object_list_permission = permission_user_edit + external_object_list_pk_field = 'user_id' + external_object_list_pk_list_field = 'user_id_list' + + def user_add(self, instance): + queryset = self.get_external_object_list() + for user in queryset: + instance.user_add(user=user, _user=self.context['request'].user) + + def user_remove(self, instance): + queryset = self.get_external_object_list() + for user in queryset: + instance.user_remove(user=user, _user=self.context['request'].user) + + +class UserGroupAddRemoveSerializer(ExternalObjectListSerializerMixin, serializers.Serializer): + group_id = serializers.CharField( + help_text=_( + 'Primary key of the group that will be added or removed.' + ), required=False, write_only=True + ) + groups_id_list = serializers.CharField( + help_text=_( + 'Comma separated list of group primary keys that will be added or ' + 'removed.' + ), required=False, write_only=True + ) + + class Meta: + external_object_list_queryset = Group.objects.all() + external_object_list_permission = permission_group_edit + external_object_list_pk_field = 'group_id' + external_object_list_pk_list_field = 'group_id_list' + + def group_add(self, instance): + queryset = self.get_external_object_list() + for group in queryset: + instance.group_add(group=group, _group=self.context['request'].group) + + def group_remove(self, instance): + queryset = self.get_external_object_list() + for group in queryset: + instance.group_remove(group=group, _group=self.context['request'].group) class UserSerializer(serializers.HyperlinkedModelSerializer): - groups = GroupSerializer(many=True, read_only=True, required=False) - groups_pk_list = serializers.CharField( - help_text=_( - 'Comma separated list of group primary keys to assign this ' - 'user to.' - ), required=False, write_only=True + group_add_url = serializers.HyperlinkedIdentityField( + lookup_url_kwarg='user_id', view_name='rest_api:user-group-add' + ) + group_list_url = serializers.HyperlinkedIdentityField( + lookup_url_kwarg='user_id', view_name='rest_api:user-group-list' + ) + group_remove_url = serializers.HyperlinkedIdentityField( + lookup_url_kwarg='user_id', view_name='rest_api:user-group-remove' ) password = serializers.CharField( required=False, style={'input_type': 'password'}, write_only=True @@ -46,32 +117,19 @@ class UserSerializer(serializers.HyperlinkedModelSerializer): class Meta: extra_kwargs = { 'url': { - 'lookup_field': 'pk', 'lookup_url_kwarg': 'user_pk', + 'lookup_url_kwarg': 'user_id', 'view_name': 'rest_api:user-detail' } } fields = ( - 'first_name', 'date_joined', 'email', 'groups', 'groups_pk_list', - 'id', 'is_active', 'last_login', 'last_name', 'password', 'url', - 'username' + 'first_name', 'date_joined', 'email', 'group_add_url', + 'group_list_url', 'group_remove_url', 'id', 'is_active', + 'last_login', 'last_name', 'password', 'url', 'username' ) model = get_user_model() - read_only_fields = ('groups', 'is_active', 'last_login', 'date_joined') - write_only_fields = ('password', 'group_pk_list') - - def _add_groups(self, instance, groups_pk_list): - instance.groups.clear() - - queryset = AccessControlList.objects.restrict_queryset( - permission=permission_group_edit, - queryset=Group.objects.filter(pk__in=groups_pk_list.split(',')), - user=self.context['request'].user - ) - - instance.groups.add(*queryset) + read_only_fields = ('is_active', 'last_login', 'date_joined') def create(self, validated_data): - groups_pk_list = validated_data.pop('groups_pk_list', '') password = validated_data.pop('password', None) instance = super(UserSerializer, self).create(validated_data) @@ -79,23 +137,15 @@ class UserSerializer(serializers.HyperlinkedModelSerializer): instance.set_password(password) instance.save() - if groups_pk_list: - self._add_groups(instance=instance, groups_pk_list=groups_pk_list) - return instance def update(self, instance, validated_data): - groups_pk_list = validated_data.pop('groups_pk_list', '') - if 'password' in validated_data: instance.set_password(validated_data['password']) validated_data.pop('password') instance = super(UserSerializer, self).update(instance, validated_data) - if groups_pk_list: - self._add_groups(instance=instance, groups_pk_list=groups_pk_list) - return instance def validate(self, data): @@ -103,3 +153,14 @@ class UserSerializer(serializers.HyperlinkedModelSerializer): validate_password(data['password'], self.instance) return data + + +class CurrentUserSerializer(UserSerializer): + class Meta(UserSerializer.Meta): + # Remove some fields that don't apply to the current user + _field_list = ( + 'group_add_url', 'group_list_url', 'group_remove_url', 'url' + ) + fields = [ + field for field in UserSerializer.Meta.fields if field not in _field_list + ] diff --git a/mayan/apps/user_management/tests/test_api.py b/mayan/apps/user_management/tests/test_api.py index d30e474828..1699b6802b 100644 --- a/mayan/apps/user_management/tests/test_api.py +++ b/mayan/apps/user_management/tests/test_api.py @@ -15,523 +15,530 @@ from ..permissions import ( ) from .literals import ( - TEST_GROUP_2_NAME, TEST_GROUP_2_NAME_EDITED, TEST_USER_2_EMAIL, - TEST_USER_2_PASSWORD, TEST_USER_2_USERNAME, TEST_USER_2_USERNAME_EDITED, - TEST_USER_2_PASSWORD_EDITED + TEST_GROUP_NAME, TEST_GROUP_NAME_EDITED, TEST_USER_EMAIL, TEST_USER_USERNAME, + TEST_USER_USERNAME_EDITED, TEST_USER_PASSWORD, TEST_USER_PASSWORD_EDITED ) -from .mixins import UserTestMixin +from .mixins import GroupTestMixin, UserTestMixin -class UserAPITestCase(UserTestMixin, BaseAPITestCase): - def setUp(self): - super(UserAPITestCase, self).setUp() - self.login_user() - - def _request_api_test_user_create(self): +class GroupAPITestCase(UserTestMixin, GroupTestMixin, BaseAPITestCase): + def _request_test_group_create_api_view(self): return self.post( - viewname='rest_api:user-list', data={ - 'email': TEST_USER_2_EMAIL, 'password': TEST_USER_2_PASSWORD, - 'username': TEST_USER_2_USERNAME, + viewname='rest_api:group-list', data={ + 'name': TEST_GROUP_NAME } ) - def test_user_create_no_permission(self): - response = self._request_api_test_user_create() + def test_group_create_api_view_no_permission(self): + group_count = Group.objects.count() + + response = self._request_test_group_create_api_view() self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - # Default two users, the test admin and the test user - self.assertEqual(get_user_model().objects.count(), 2) - def test_user_create_with_permission(self): - self.grant_permission(permission=permission_user_create) - response = self._request_api_test_user_create() + self.assertEqual(group_count, Group.objects.count()) + + def test_group_create_api_view_with_permission(self): + group_count = Group.objects.count() + + self.grant_permission(permission=permission_group_create) + response = self._request_test_group_create_api_view() self.assertEqual(response.status_code, status.HTTP_201_CREATED) - user = get_user_model().objects.get(pk=response.data['id']) - self.assertEqual(user.username, TEST_USER_2_USERNAME) - self.assertEqual(get_user_model().objects.count(), 3) - def _request_api_create_test_user_with_extra_data(self): - return self.post( - viewname='rest_api:user-list', data={ - 'email': TEST_USER_2_EMAIL, 'password': TEST_USER_2_PASSWORD, - 'username': TEST_USER_2_USERNAME, - 'groups_id_list': self.test_groups_id_list + self.assertNotEqual(group_count, Group.objects.count()) + + def _request_test_group_delete_api_view(self): + return self.delete( + viewname='rest_api:group-detail', + kwargs={'group_id': self.test_group.pk} + ) + + def test_group_delete_api_view_no_permission(self): + self._create_test_group() + + group_count = Group.objects.count() + + response = self._request_test_group_delete_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.assertEqual(group_count, Group.objects.count()) + + def test_group_delete_api_view_with_access(self): + self._create_test_group() + + group_count = Group.objects.count() + + self.grant_access( + obj=self.test_group, permission=permission_group_delete + ) + response = self._request_test_group_delete_api_view() + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + + self.assertNotEqual(group_count, Group.objects.count()) + + def _request_test_group_detail_api_view(self): + return self.get( + viewname='rest_api:group-detail', + kwargs={'group_id': self.test_group.pk} + ) + + def test_group_detail_api_view_no_permission(self): + self._create_test_group() + + response = self._request_test_group_detail_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.assertNotEqual( + self.test_group.name, response.data.get('name', None) + ) + + def test_group_detail_api_view_with_access(self): + self._create_test_group() + + self.grant_access( + obj=self.test_group, permission=permission_group_view + ) + response = self._request_test_group_detail_api_view() + self.assertEqual(response.status_code, status.HTTP_200_OK) + + self.assertEqual(self.test_group.name, response.data.get('name', None)) + + def _request_test_group_edit_patch_api_view(self): + return self.patch( + viewname='rest_api:group-detail', + kwargs={'group_id': self.test_group.pk}, + data={ + 'name': TEST_GROUP_NAME_EDITED } ) - """ - def test_user_create_with_group_no_permission(self): + def test_group_edit_patch_api_view_no_permission(self): self._create_test_group() - self.test_groups_id_list = '{}'.format(self.test_group.pk) - response = self._request_api_create_test_user_with_extra_data() - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + group_name = self.test_group.name - def test_user_create_with_group_with_user_access(self): + response = self._request_test_group_edit_patch_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.test_group.refresh_from_db() + self.assertEqual(group_name, self.test_group.name) + + def test_group_edit_via_patch_with_access(self): self._create_test_group() - self.test_groups_id_list = '{}'.format(self.test_group.pk) + + group_name = self.test_group.name self.grant_access( - obj=self.test_user, permission=permission_user_create + obj=self.test_group, permission=permission_group_edit ) - response = self._request_api_create_test_user_with_extra_data() + response = self._request_test_group_edit_patch_api_view() + self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.test_group.refresh_from_db() + self.assertNotEqual(group_name, self.test_group.name) - user = get_user_model().objects.get(pk=response.data['id']) - self.assertEqual(user.username, TEST_USER_2_USERNAME) - self.assertQuerysetEqual(user.groups.all(), (repr(self.test_group),)) + def _request_test_group_list_api_view(self): + return self.get(viewname='rest_api:group-list') - - def test_user_create_with_group_with_user_access(self): + def test_group_list_api_view_no_permission(self): + self._create_test_group() + + response = self._request_test_group_list_api_view() + self.assertNotContains( + response=response, text=self.test_group.name, + status_code=status.HTTP_200_OK + ) + + def test_group_list_api_view_with_access(self): self._create_test_group() - self.test_groups_id_list = '{}'.format(self.test_group.pk) self.grant_access( - obj=self.test_user, permission=permission_user_create + obj=self.test_group, permission=permission_group_view + ) + response = self._request_test_group_list_api_view() + self.assertContains( + response=response, text=self.test_group.name, + status_code=status.HTTP_200_OK ) - response = self._request_api_create_test_user_with_extra_data() - self.assertEqual(response.status_code, status.HTTP_201_CREATED) + def _request_test_group_user_add_patch_api_view(self): + return self.post( + viewname='rest_api:group-user-add', + kwargs={'group_id': self.test_group.pk}, + data={ + 'user_id': self.test_user.pk + } + ) - user = get_user_model().objects.get(pk=response.data['id']) - self.assertEqual(user.username, TEST_USER_2_USERNAME) - self.assertQuerysetEqual(user.groups.all(), (repr(self.test_group),)) - """ + def _setup_group_user_add(self): + self._create_test_group() + self._create_test_user() - def test_user_create_with_groups_no_permission(self): - group_1 = Group.objects.create(name='test group 1') - group_2 = Group.objects.create(name='test group 2') - self.test_groups_id_list = '{},{}'.format(group_1.pk, group_2.pk) - response = self._request_api_create_test_user_with_extra_data() + def test_group_user_add_api_view_no_permission(self): + self._setup_group_user_add() + + response = self._request_test_group_user_add_patch_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.test_group.refresh_from_db() + self.assertTrue(self.test_user not in self.test_group.user_set.all()) + + def test_group_user_add_with_group_access(self): + self._setup_group_user_add() + + self.grant_access( + obj=self.test_group, permission=permission_group_edit + ) + response = self._request_test_group_user_add_patch_api_view() + self.assertEqual(response.status_code, status.HTTP_200_OK) + + self.test_group.refresh_from_db() + self.assertTrue(self.test_user not in self.test_group.user_set.all()) + + def test_group_user_add_with_user_access(self): + self._setup_group_user_add() + + self.grant_access( + obj=self.test_user, permission=permission_user_edit + ) + response = self._request_test_group_user_add_patch_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.test_group.refresh_from_db() + self.assertTrue(self.test_user not in self.test_group.user_set.all()) + + def test_group_user_add_with_full_access(self): + self._setup_group_user_add() + + self.grant_access( + obj=self.test_group, permission=permission_group_edit + ) + self.grant_access( + obj=self.test_user, permission=permission_user_edit + ) + response = self._request_test_group_user_add_patch_api_view() + self.assertEqual(response.status_code, status.HTTP_200_OK) + + self.test_group.refresh_from_db() + self.assertTrue(self.test_user in self.test_group.user_set.all()) + + #TODO: group-user-list tests + + def _request_test_group_user_remove_patch_api_view(self): + return self.post( + viewname='rest_api:group-user-remove', + kwargs={'group_id': self.test_group.pk}, + data={ + 'user_id': self.test_user.pk + } + ) + + def _setup_group_user_remove(self): + self._create_test_group() + self._create_test_user() + self.test_group.user_set.add(self.test_user) + + def test_group_user_remove_api_view_no_permission(self): + self._setup_group_user_remove() + + response = self._request_test_group_user_remove_patch_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.test_group.refresh_from_db() + self.assertTrue(self.test_user in self.test_group.user_set.all()) + + def test_group_user_remove_with_group_access(self): + self._setup_group_user_remove() + + self.grant_access( + obj=self.test_group, permission=permission_group_edit + ) + response = self._request_test_group_user_remove_patch_api_view() + self.assertEqual(response.status_code, status.HTTP_200_OK) + + self.test_group.refresh_from_db() + self.assertTrue(self.test_user in self.test_group.user_set.all()) + + def test_group_user_remove_with_user_access(self): + self._setup_group_user_remove() + + self.grant_access( + obj=self.test_user, permission=permission_user_edit + ) + response = self._request_test_group_user_remove_patch_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.test_group.refresh_from_db() + self.assertTrue(self.test_user in self.test_group.user_set.all()) + + def test_group_user_remove_with_full_access(self): + self._setup_group_user_remove() + + self.grant_access( + obj=self.test_group, permission=permission_group_edit + ) + self.grant_access( + obj=self.test_user, permission=permission_user_edit + ) + response = self._request_test_group_user_remove_patch_api_view() + self.assertEqual(response.status_code, status.HTTP_200_OK) + + self.test_group.refresh_from_db() + self.assertTrue(self.test_user not in self.test_group.user_set.all()) + + +class UserAPITestCase(GroupTestMixin, UserTestMixin, BaseAPITestCase): + def _request_test_user_create_api_view_api_view(self): + return self.post( + viewname='rest_api:user-list', data={ + 'email': TEST_USER_EMAIL, 'password': TEST_USER_PASSWORD, + 'username': TEST_USER_USERNAME, + } + ) + + def test_user_create_api_view_no_permission(self): + user_count = get_user_model().objects.count() + + response = self._request_test_user_create_api_view_api_view() self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_user_create_with_groups_with_user_permission(self): - group_1 = Group.objects.create(name='test group 1') - group_2 = Group.objects.create(name='test group 2') - self.test_groups_id_list = '{},{}'.format(group_1.pk, group_2.pk) + self.assertEqual(user_count, get_user_model().objects.count()) + + def test_user_create_api_view_with_permission(self): + user_count = get_user_model().objects.count() + self.grant_permission(permission=permission_user_create) - response = self._request_api_create_test_user_with_extra_data() - + response = self._request_test_user_create_api_view_api_view() self.assertEqual(response.status_code, status.HTTP_201_CREATED) - user = get_user_model().objects.get(pk=response.data['id']) - self.assertEqual(user.username, TEST_USER_2_USERNAME) - #self.assertQuerysetEqual( - # user.groups.all().order_by('name'), (repr(group_1), repr(group_2)) - #) - self.assertEqual(user.groups.count(), 0) - def test_user_create_with_groups_with_full_access(self): - group_1 = Group.objects.create(name='test group 1') - group_2 = Group.objects.create(name='test group 2') - self.test_groups_id_list = '{},{}'.format(group_1.pk, group_2.pk) - self.grant_permission(permission=permission_user_create) - self.grant_access(obj=group_1, permission=permission_group_edit) - self.grant_access(obj=group_2, permission=permission_group_edit) - response = self._request_api_create_test_user_with_extra_data() + self.assertEqual(user.username, TEST_USER_USERNAME) + self.assertEqual(user_count + 1, get_user_model().objects.count()) - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - - user = get_user_model().objects.get(pk=response.data['id']) - self.assertEqual(user.username, TEST_USER_2_USERNAME) - self.assertQuerysetEqual( - user.groups.all().order_by('name'), (repr(group_1), repr(group_2)) - ) - - # User login - - def test_user_create_login(self): + def test_user_create_api_view_login(self): self._create_test_user() self.assertTrue( self.login( - username=TEST_USER_2_USERNAME, password=TEST_USER_2_PASSWORD + username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD ) ) - # User password change - - def _request_api_user_password_change(self): + def _request_user_password_change(self): return self.patch( viewname='rest_api:user-detail', kwargs={'user_id': self.test_user.pk}, data={ - 'password': TEST_USER_2_PASSWORD_EDITED, + 'password': TEST_USER_PASSWORD_EDITED, } ) - def test_user_create_login_password_change_no_access(self): + def test_user_create_login_password_change_api_view_no_permission(self): self._create_test_user() - self._request_api_user_password_change() + self._request_user_password_change() self.assertFalse( self.client.login( - username=TEST_USER_2_USERNAME, - password=TEST_USER_2_PASSWORD_EDITED + username=TEST_USER_USERNAME, + password=TEST_USER_PASSWORD_EDITED ) ) - def test_user_create_login_password_change_with_access(self): + def test_user_create_login_password_change_api_view_with_access(self): self._create_test_user() self.grant_access(obj=self.test_user, permission=permission_user_edit) - self._request_api_user_password_change() + self._request_user_password_change() self.assertTrue( self.client.login( - username=TEST_USER_2_USERNAME, - password=TEST_USER_2_PASSWORD_EDITED + username=TEST_USER_USERNAME, + password=TEST_USER_PASSWORD_EDITED ) ) - # User edit - - def _request_api_test_user_edit_via_put(self): + def _request_test_user_edit_put_api_view(self): return self.put( viewname='rest_api:user-detail', kwargs={'user_id': self.test_user.pk}, - data={'username': TEST_USER_2_USERNAME_EDITED} + data={'username': TEST_USER_USERNAME_EDITED} ) - def test_user_edit_via_put_no_access(self): + def test_user_edit_put_api_view_no_permission(self): self._create_test_user() - response = self._request_api_test_user_edit_via_put() + username = self.test_user.username + response = self._request_test_user_edit_put_api_view() self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) self.test_user.refresh_from_db() - self.assertEqual(self.test_user.username, TEST_USER_2_USERNAME) + self.assertEqual(username, self.test_user.username) - def test_user_edit_via_put_with_access(self): + def test_user_edit_put_api_view_with_access(self): self._create_test_user() - self.grant_access(obj=self.test_user, permission=permission_user_edit) - response = self._request_api_test_user_edit_via_put() + username = self.test_user.username + self.grant_access(obj=self.test_user, permission=permission_user_edit) + response = self._request_test_user_edit_put_api_view() self.assertEqual(response.status_code, status.HTTP_200_OK) self.test_user.refresh_from_db() - self.assertEqual(self.test_user.username, TEST_USER_2_USERNAME_EDITED) + self.assertNotEqual(username, self.test_user.username) - def _request_api_test_user_edit_via_patch(self): + def _request_test_user_edit_patch_api_view(self): return self.patch( viewname='rest_api:user-detail', kwargs={'user_id': self.test_user.pk}, - data={'username': TEST_USER_2_USERNAME_EDITED} + data={'username': TEST_USER_USERNAME_EDITED} ) - def test_user_edit_via_patch_no_access(self): + def test_user_edit_patch_api_view_no_permission(self): self._create_test_user() - response = self._request_api_test_user_edit_via_patch() + username = self.test_user.username + response = self._request_test_user_edit_patch_api_view() self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) self.test_user.refresh_from_db() - self.assertEqual(self.test_user.username, TEST_USER_2_USERNAME) + self.assertEqual(username, self.test_user.username) - def test_user_edit_via_patch_with_access(self): + def test_user_edit_patch_api_view_with_access(self): self._create_test_user() - self.grant_access(obj=self.test_user, permission=permission_user_edit) - response = self._request_api_test_user_edit_via_patch() + username = self.test_user.username + self.grant_access(obj=self.test_user, permission=permission_user_edit) + response = self._request_test_user_edit_patch_api_view() self.assertEqual(response.status_code, status.HTTP_200_OK) self.test_user.refresh_from_db() - self.assertEqual(self.test_user.username, TEST_USER_2_USERNAME_EDITED) + self.assertNotEqual(username, self.test_user.username) - def _request_api_test_user_edit_via_patch_with_extra_data(self): - return self.patch( - viewname='rest_api:user-detail', - kwargs={'user_id': self.test_user.pk}, - data={'groups_id_list': '{}'.format(self.test_group.pk)} - ) - - def test_user_edit_add_groups_via_patch_no_access(self): - self._create_test_group() - self._create_test_user() - - response = self._request_api_test_user_edit_via_patch_with_extra_data() - - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - - self.test_user.refresh_from_db() - self.assertEqual(self.test_user.username, TEST_USER_2_USERNAME) - - self.assertQuerysetEqual( - self.test_user.groups.all(), () - ) - - def test_user_edit_add_groups_via_patch_with_access(self): - self._create_test_group() - self._create_test_user() - self.grant_access(obj=self.test_user, permission=permission_user_edit) - self.grant_access(obj=self.test_group, permission=permission_group_edit) - response = self._request_api_test_user_edit_via_patch_with_extra_data() - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - self.test_user.refresh_from_db() - self.assertEqual(self.test_user.username, TEST_USER_2_USERNAME) - - self.assertQuerysetEqual( - self.test_user.groups.all(), (repr(self.test_group),) - ) - - # User delete - - def _request_api_test_user_delete(self): + def _request_test_user_delete_api_view(self): return self.delete( viewname='rest_api:user-detail', kwargs={'user_id': self.test_user.pk} ) - def test_user_delete_no_access(self): + def test_user_delete_api_view_no_permission(self): self._create_test_user() - response = self._request_api_test_user_delete() + + response = self._request_test_user_delete_api_view() self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) self.assertTrue( get_user_model().objects.filter(pk=self.test_user.pk).exists() ) - def test_user_delete_with_access(self): + def test_user_delete_api_view_with_access(self): self._create_test_user() + self.grant_access( obj=self.test_user, permission=permission_user_delete ) - response = self._request_api_test_user_delete() + response = self._request_test_user_delete_api_view() self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertFalse( get_user_model().objects.filter(pk=self.test_user.pk).exists() ) - # User group listview - - def _request_api_test_user_group_view(self): + def _request_test_user_group_api_view(self): return self.get( - viewname='rest_api:users-group-list', + viewname='rest_api:user-group-list', kwargs={'user_id': self.test_user.pk} ) - def test_user_group_list_no_access(self): - group = Group.objects.create(name=TEST_GROUP_2_NAME) + def test_user_group_list_api_view_no_permission(self): + self._create_test_group() self._create_test_user() - self.test_user.groups.add(group) - response = self._request_api_test_user_group_view() + self.test_user.groups.add(self.test_group) + + response = self._request_test_user_group_api_view() self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - def test_user_group_list_with_user_access(self): - group = Group.objects.create(name=TEST_GROUP_2_NAME) + def test_user_group_list_api_view_with_user_access(self): + self._create_test_group() self._create_test_user() - self.test_user.groups.add(group) + self.test_user.groups.add(self.test_group) + self.grant_access(obj=self.test_user, permission=permission_user_view) - response = self._request_api_test_user_group_view() + response = self._request_test_user_group_api_view() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['count'], 0) - def test_user_group_list_with_group_access(self): + def test_user_group_list_api_view_with_group_access(self): self._create_test_group() self._create_test_user() self.test_user.groups.add(self.test_group) + self.grant_access( obj=self.test_group, permission=permission_group_view ) - response = self._request_api_test_user_group_view() + response = self._request_test_user_group_api_view() self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - def test_user_group_list_with_access(self): + def test_user_group_list_api_view_with_access(self): self._create_test_group() self._create_test_user() self.test_user.groups.add(self.test_group) + self.grant_access(obj=self.test_user, permission=permission_user_view) self.grant_access( obj=self.test_group, permission=permission_group_view ) - response = self._request_api_test_user_group_view() + response = self._request_test_user_group_api_view() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['count'], 1) - def _request_api_test_user_group_add(self): + def _request_test_user_group_add_api_view(self): return self.patch( viewname='rest_api:user-detail', kwargs={'user_id': self.test_user.pk}, data={'group_id_list': '{}'.format(self.test_group.pk)} ) - def test_user_group_add_no_access(self): + def test_user_group_add_api_view_no_permission(self): self._create_test_group() self._create_test_user() - response = self._request_api_test_user_group_add() + + response = self._request_test_user_group_add_api_view() self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.test_user.refresh_from_db() self.assertEqual(self.test_group.user_set.first(), None) - def test_user_group_add_with_user_access(self): + def test_user_group_add_api_view_with_user_access(self): self._create_test_group() self._create_test_user() + self.grant_access(obj=self.test_user, permission=permission_user_edit) - response = self._request_api_test_user_group_add() + response = self._request_test_user_group_add_api_view() self.assertEqual(response.status_code, status.HTTP_200_OK) + self.test_user.refresh_from_db() self.assertEqual(self.test_group.user_set.first(), None) - def test_user_group_add_with_group_access(self): + def test_user_group_add_api_view_with_group_access(self): self._create_test_group() self._create_test_user() + self.grant_access( obj=self.test_group, permission=permission_group_edit ) - response = self._request_api_test_user_group_add() + response = self._request_test_user_group_add_api_view() self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.test_user.refresh_from_db() self.assertEqual(self.test_group.user_set.first(), None) - def test_user_group_add_with_full_access(self): + def test_user_group_add_api_view_with_full_access(self): self._create_test_group() self._create_test_user() + self.grant_access(obj=self.test_user, permission=permission_user_edit) self.grant_access( obj=self.test_group, permission=permission_group_edit ) - response = self._request_api_test_user_group_add() + response = self._request_test_user_group_add_api_view() self.assertEqual(response.status_code, status.HTTP_200_OK) + self.test_user.refresh_from_db() self.assertEqual(self.test_group.user_set.first(), self.test_user) - - -class GroupAPITestCase(UserTestMixin, BaseAPITestCase): - def setUp(self): - super(GroupAPITestCase, self).setUp() - self.login_user() - - def _request_api_test_group_create_view(self): - return self.post( - viewname='rest_api:group-list', data={ - 'name': TEST_GROUP_2_NAME - } - ) - - def test_group_create_no_permission(self): - response = self._request_api_test_group_create_view() - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertFalse( - TEST_GROUP_2_NAME in list( - Group.objects.values_list('name', flat=True) - ) - ) - - def test_group_create_with_permission(self): - self.grant_permission(permission=permission_group_create) - response = self._request_api_test_group_create_view() - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.assertTrue( - TEST_GROUP_2_NAME in list( - Group.objects.values_list('name', flat=True) - ) - ) - - def _request_api_test_group_delete_view(self): - return self.delete( - viewname='rest_api:group-detail', - kwargs={'group_id': self.test_group.pk} - ) - - def test_group_delete_no_access(self): - self._create_test_group() - response = self._request_api_test_group_delete_view() - - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - self.assertTrue( - TEST_GROUP_2_NAME in list( - Group.objects.values_list('name', flat=True) - ) - ) - - def test_group_delete_with_access(self): - self._create_test_group() - self.grant_access( - obj=self.test_group, permission=permission_group_delete - ) - response = self._request_api_test_group_delete_view() - - self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - self.assertFalse( - TEST_GROUP_2_NAME in list( - Group.objects.values_list('name', flat=True) - ) - ) - - def _request_api_test_group_detail_view(self): - return self.get( - viewname='rest_api:group-detail', - kwargs={'group_id': self.test_group.pk} - ) - - def test_group_detail_no_access(self): - self._create_test_group() - response = self._request_api_test_group_detail_view() - - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - self.assertNotEqual( - self.test_group.name, response.data.get('name', None) - ) - - def test_group_detail_with_access(self): - self._create_test_group() - self.grant_access( - obj=self.test_group, permission=permission_group_view - ) - response = self._request_api_test_group_detail_view() - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(self.test_group.name, response.data.get('name', None)) - - - def _request_api_test_group_edit_via_patch_view(self): - return self.patch( - viewname='rest_api:group-detail', - kwargs={'group_id': self.test_group.pk}, - data={ - 'name': TEST_GROUP_2_NAME_EDITED - } - ) - - def test_group_edit_via_patch_no_access(self): - self._create_test_group() - response = self._request_api_test_group_edit_via_patch_view() - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - - self.test_group.refresh_from_db() - self.assertEqual(self.test_group.name, TEST_GROUP_2_NAME) - - def test_group_edit_via_patch_with_access(self): - self._create_test_group() - self.grant_access( - obj=self.test_group, permission=permission_group_edit - ) - response = self._request_api_test_group_edit_via_patch_view() - self.assertEqual(response.status_code, status.HTTP_200_OK) - - self.test_group.refresh_from_db() - self.assertEqual(self.test_group.name, TEST_GROUP_2_NAME_EDITED) - - def _request_api_test_group_list_view(self): - return self.get(viewname='rest_api:group-list') - - def test_group_list_no_access(self): - self._create_test_group() - response = self._request_api_test_group_list_view() - self.assertNotContains( - response=response, text=self.test_group.name, - status_code=status.HTTP_200_OK - ) - - def test_group_list_with_access(self): - self._create_test_group() - self.grant_access( - obj=self.test_group, permission=permission_group_view - ) - response = self._request_api_test_group_list_view() - self.assertContains( - response=response, text=self.test_group.name, - status_code=status.HTTP_200_OK - ) diff --git a/mayan/apps/user_management/urls.py b/mayan/apps/user_management/urls.py index a0c103277e..f0fd92f299 100644 --- a/mayan/apps/user_management/urls.py +++ b/mayan/apps/user_management/urls.py @@ -2,10 +2,7 @@ from __future__ import unicode_literals from django.conf.urls import url -from .api_views import ( - APICurrentUserView, APIGroupListView, APIGroupView, APIUserGroupList, - APIUserListView, APIUserView -) +from .api_views import CurrentUserAPIView, GroupAPIViewSet, UserAPIViewSet from .views import ( CurrentUserDetailsView, CurrentUserEditView, GroupCreateView, GroupDeleteView, GroupEditView, GroupListView, GroupMembersView, @@ -82,25 +79,13 @@ urlpatterns = [ ) ] -api_urls = [ +api_urlpatterns = [ url( - regex=r'^groups/$', name='group-list', view=APIGroupListView.as_view() - ), - url( - regex=r'^groups/(?P\d+)/$', name='group-detail', - view=APIGroupView.as_view(), - ), - url( - regex=r'^user/$', name='user-current', - view=APICurrentUserView.as_view() - ), - url(regex=r'^users/$', name='user-list', view=APIUserListView.as_view()), - url( - regex=r'^users/(?P\d+)/$', name='user-detail', - view=APIUserView.as_view() - ), - url( - regex=r'^users/(?P\d+)/groups/$', name='users-group-list', - view=APIUserGroupList.as_view() - ), + regex=r'^user/$', name='user-current', view=CurrentUserAPIView.as_view() + ) ] + +api_router_entries = ( + {'prefix': r'groups', 'viewset': GroupAPIViewSet, 'basename': 'group'}, + {'prefix': r'users', 'viewset': UserAPIViewSet, 'basename': 'user'}, +)