Merge pull request #1482 from normanjaeckel/Groups

Updated REST API for group create, update and delete.
This commit is contained in:
Oskar Hahn 2015-02-25 12:12:13 +01:00
commit de52634561
3 changed files with 171 additions and 15 deletions

View File

@ -4,7 +4,7 @@ from django.utils.translation import ugettext as _, ugettext_lazy
from openslides.utils.rest_api import ModelSerializer, PrimaryKeyRelatedField, RelatedField, ValidationError from openslides.utils.rest_api import ModelSerializer, PrimaryKeyRelatedField, RelatedField, ValidationError
from .models import Group, User from .models import Group, Permission, User
class UserShortSerializer(ModelSerializer): class UserShortSerializer(ModelSerializer):
@ -122,18 +122,40 @@ class PermissionRelatedField(RelatedField):
""" """
A custom field to use for the permission relationship. A custom field to use for the permission relationship.
""" """
default_error_messages = {
'incorrect_value': ugettext_lazy('Incorrect value "{value}". Expected app_label.codename string.'),
'does_not_exist': ugettext_lazy('Invalid permission "{value}". Object does not exist.')}
def to_representation(self, value): def to_representation(self, value):
""" """
Returns the permission name (app_label.codename). Returns the permission code string (app_label.codename).
""" """
return '.'.join((value.content_type.app_label, value.codename,)) return '.'.join((value.content_type.app_label, value.codename,))
def to_internal_value(self, data):
"""
Returns the permission object represented by data. The argument data is
what is sent by the client. This method expects permission code strings
(app_label.codename) like to_representation() returns.
"""
try:
app_label, codename = data.split('.')
except ValueError:
self.fail('incorrect_value', value=data)
try:
permission = Permission.objects.get(content_type__app_label=app_label, codename=codename)
except Permission.DoesNotExist:
self.fail('does_not_exist', value=data)
return permission
class GroupSerializer(ModelSerializer): class GroupSerializer(ModelSerializer):
""" """
Serializer for django.contrib.auth.models.Group objects. Serializer for django.contrib.auth.models.Group objects.
""" """
permissions = PermissionRelatedField(many=True, read_only=True) permissions = PermissionRelatedField(
many=True,
queryset=Permission.objects.all())
class Meta: class Meta:
model = Group model = Group

View File

@ -4,8 +4,9 @@ from django.contrib.auth import logout as auth_logout
from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils.translation import activate, ugettext_lazy from django.utils.translation import activate, ugettext_lazy
from rest_framework import status
from openslides.utils.rest_api import ModelViewSet from openslides.utils.rest_api import ModelViewSet, Response
from openslides.utils.views import ( from openslides.utils.views import (
CSVImportView, CSVImportView,
FormView, FormView,
@ -121,6 +122,19 @@ class GroupViewSet(ModelViewSet):
request.user.has_perm('users.can_see_extra_data')))): request.user.has_perm('users.can_see_extra_data')))):
self.permission_denied(request) self.permission_denied(request)
def destroy(self, request, *args, **kwargs):
"""
Protects builtin groups 'Anonymous' (pk=1) and 'Registered' (pk=2)
from being deleted.
"""
instance = self.get_object()
if instance.pk in (1, 2,):
self.permission_denied(request)
else:
self.perform_destroy(instance)
response = Response(status=status.HTTP_204_NO_CONTENT)
return response
class UserSettingsView(LoginMixin, UpdateView): class UserSettingsView(LoginMixin, UpdateView):
required_permission = None required_permission = None

View File

@ -2,11 +2,11 @@ from django.core.urlresolvers import reverse
from rest_framework import status from rest_framework import status
from rest_framework.test import APIClient from rest_framework.test import APIClient
from openslides.users.models import User from openslides.users.models import Group, User
from openslides.utils.test import TestCase from openslides.utils.test import TestCase
class UserCreation(TestCase): class UserCreate(TestCase):
""" """
Tests creation of users via REST API. Tests creation of users via REST API.
""" """
@ -22,26 +22,31 @@ class UserCreation(TestCase):
def test_creation_with_group(self): def test_creation_with_group(self):
self.client.login(username='admin', password='admin') self.client.login(username='admin', password='admin')
# These are the builtin groups 'Delegates' and 'Staff'. The pks are valid.
group_pks = (3, 4,)
self.client.post( self.client.post(
reverse('user-list'), reverse('user-list'),
{'last_name': 'Test name aedah1iequoof0Ashed4', {'last_name': 'Test name aedah1iequoof0Ashed4',
'groups': ['3', '4']}) 'groups': group_pks})
user = User.objects.get(username='Test name aedah1iequoof0Ashed4') user = User.objects.get(username='Test name aedah1iequoof0Ashed4')
self.assertTrue(user.groups.filter(pk=3).exists()) self.assertTrue(user.groups.filter(pk=group_pks[0]).exists())
self.assertTrue(user.groups.filter(pk=4).exists()) self.assertTrue(user.groups.filter(pk=group_pks[1]).exists())
def test_creation_with_anonymous_or_registered_group(self): def test_creation_with_anonymous_or_registered_group(self):
self.client.login(username='admin', password='admin') self.client.login(username='admin', password='admin')
# These are the builtin groups 'Anonymous' and 'Registered'.
# The pks are valid. But these groups can not be added to users.
group_pks = (1, 2,)
response = self.client.post( response = self.client.post(
reverse('user-list'), reverse('user-list'),
{'last_name': 'Test name aedah1iequoof0Ashed4', {'last_name': 'Test name aedah1iequoof0Ashed4',
'groups': ['1', '2']}) 'groups': group_pks})
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(response.data, {'groups': ["Invalid pk '1' - object does not exist."]}) self.assertEqual(response.data, {'groups': ["Invalid pk '%d' - object does not exist." % group_pks[0]]})
class UserUpdate(TestCase): class UserUpdate(TestCase):
@ -51,22 +56,26 @@ class UserUpdate(TestCase):
def test_simple_update_via_patch(self): def test_simple_update_via_patch(self):
admin_client = APIClient() admin_client = APIClient()
admin_client.login(username='admin', password='admin') admin_client.login(username='admin', password='admin')
# This is the builtin user 'Administrator' with username 'admin'. The pk is valid.
user_pk = 1
response = admin_client.patch( response = admin_client.patch(
reverse('user-detail', args=['1']), reverse('user-detail', args=[user_pk]),
{'last_name': 'New name tu3ooh5Iez5Aec2laefo'}) {'last_name': 'New name tu3ooh5Iez5Aec2laefo'})
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
user = User.objects.get(pk=1) user = User.objects.get(pk=user_pk)
self.assertEqual(user.last_name, 'New name tu3ooh5Iez5Aec2laefo') self.assertEqual(user.last_name, 'New name tu3ooh5Iez5Aec2laefo')
self.assertEqual(user.username, 'admin') self.assertEqual(user.username, 'admin')
def test_simple_update_via_put(self): def test_simple_update_via_put(self):
admin_client = APIClient() admin_client = APIClient()
admin_client.login(username='admin', password='admin') admin_client.login(username='admin', password='admin')
# This is the builtin user 'Administrator'. The pk is valid.
user_pk = 1
response = admin_client.put( response = admin_client.put(
reverse('user-detail', args=['1']), reverse('user-detail', args=[user_pk]),
{'last_name': 'New name Ohy4eeyei5Sahzah0Os2'}) {'last_name': 'New name Ohy4eeyei5Sahzah0Os2'})
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
@ -81,9 +90,120 @@ class UserDelete(TestCase):
admin_client = APIClient() admin_client = APIClient()
admin_client.login(username='admin', password='admin') admin_client.login(username='admin', password='admin')
User.objects.create(username='Test name bo3zieT3iefahng0ahqu') User.objects.create(username='Test name bo3zieT3iefahng0ahqu')
self.assertTrue(User.objects.filter(username='Test name bo3zieT3iefahng0ahqu').exists())
response = admin_client.delete(reverse('user-detail', args=['2'])) response = admin_client.delete(reverse('user-detail', args=['2']))
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
self.assertFalse(User.objects.filter(username='Test name bo3zieT3iefahng0ahqu').exists()) self.assertFalse(User.objects.filter(username='Test name bo3zieT3iefahng0ahqu').exists())
class GroupCreate(TestCase):
"""
Tests creation of groups via REST API.
"""
def test_creation(self):
self.client.login(username='admin', password='admin')
# This contains two valid permissions of the users app.
permissions = ('users.can_see_name', 'users.can_see_extra_data')
response = self.client.post(
reverse('group-list'),
{'name': 'Test name la8eephu9vaecheiKeif',
'permissions': permissions})
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
group = Group.objects.get(name='Test name la8eephu9vaecheiKeif')
for permission in permissions:
app_label, codename = permission.split('.')
self.assertTrue(group.permissions.get(content_type__app_label=app_label, codename=codename))
def test_failed_creation_invalid_value(self):
self.client.login(username='admin', password='admin')
permissions = ('invalid_permission',)
response = self.client.post(
reverse('group-list'),
{'name': 'Test name ool5aeb6Rai2aiLaith1',
'permissions': permissions})
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(
response.data,
{'permissions': ['Incorrect value "invalid_permission". Expected app_label.codename string.']})
def test_failed_creation_invalid_permission(self):
self.client.login(username='admin', password='admin')
permissions = ('invalid_app.invalid_permission',)
response = self.client.post(
reverse('group-list'),
{'name': 'Test name wei2go2aiV3eophi9Ohg',
'permissions': permissions})
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(
response.data,
{'permissions': ['Invalid permission "invalid_app.invalid_permission". Object does not exist.']})
class GroupUpdate(TestCase):
"""
Tests update of groups via REST API.
"""
def test_simple_update_via_patch(self):
admin_client = APIClient()
admin_client.login(username='admin', password='admin')
# This is the builtin group 'Delegates'. The pk is valid.
group_pk = 3
# This contains one valid permission of the users app.
permissions = ('users.can_see_name',)
response = admin_client.patch(
reverse('group-detail', args=[group_pk]),
{'permissions': permissions})
self.assertEqual(response.status_code, status.HTTP_200_OK)
group = Group.objects.get(pk=group_pk)
for permission in permissions:
app_label, codename = permission.split('.')
self.assertTrue(group.permissions.get(content_type__app_label=app_label, codename=codename))
def test_simple_update_via_put(self):
admin_client = APIClient()
admin_client.login(username='admin', password='admin')
# This is the builtin group 'Delegates'. The pk is valid.
group_pk = 3
# This contains one valid permission of the users app.
permissions = ('users.can_see_name',)
response = admin_client.put(
reverse('group-detail', args=[group_pk]),
{'permissions': permissions})
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(response.data, {'name': ['This field is required.']})
class GroupDelete(TestCase):
"""
Tests delete of groups via REST API.
"""
def test_delete(self):
admin_client = APIClient()
admin_client.login(username='admin', password='admin')
group = Group.objects.create(name='Test name Koh4lohlaewoog9Ahsh5')
response = admin_client.delete(reverse('group-detail', args=[group.pk]))
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
self.assertFalse(Group.objects.filter(name='Test name Koh4lohlaewoog9Ahsh5').exists())
def test_delete_builtin_groups(self):
admin_client = APIClient()
admin_client.login(username='admin', password='admin')
# The pks of builtin groups 'Anonymous' and 'Registered'
group_pks = (1, 2,)
for group_pk in group_pks:
response = admin_client.delete(reverse('group-detail', args=[group_pk]))
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)