Merge pull request #1408 from normanjaeckel/DjangoRESTFramework-Work

Refactored REST api in agenda, core and users app.
This commit is contained in:
Norman Jäckel 2015-01-17 16:27:22 +01:00
commit 413abf2b9a
15 changed files with 134 additions and 60 deletions

View File

@ -16,6 +16,7 @@ Other:
template signals and slides.
- Used Bower and gulp to manage third party JavaScript and Cascading Style
Sheets libraries.
- Added Django REST Framework api.
Version 1.7 (unreleased)

View File

@ -1,11 +1,11 @@
from rest_framework import serializers
from openslides.utils import rest_api
from .models import Item, Speaker
class SpeakerSerializer(serializers.HyperlinkedModelSerializer):
class SpeakerSerializer(rest_api.serializers.HyperlinkedModelSerializer):
"""
Serializer for a agenda.models.Speaker objects.
Serializer for agenda.models.Speaker objects.
"""
class Meta:
model = Speaker
@ -17,18 +17,17 @@ class SpeakerSerializer(serializers.HyperlinkedModelSerializer):
'weight')
class ItemSerializer(serializers.ModelSerializer):
class ItemSerializer(rest_api.serializers.HyperlinkedModelSerializer):
"""
Serializer for a agenda.models.Item objects.
"""
get_title = serializers.CharField(read_only=True)
get_title_supplement = serializers.CharField(read_only=True)
item_no = serializers.CharField(read_only=True)
get_title = rest_api.serializers.CharField(read_only=True)
get_title_supplement = rest_api.serializers.CharField(read_only=True)
item_no = rest_api.serializers.CharField(read_only=True)
speaker_set = SpeakerSerializer(many=True, read_only=True)
tags = rest_api.serializers.HyperlinkedRelatedField(many=True, read_only=True, view_name='tag-detail')
# content_object = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Item
exclude = ('content_type', 'object_id')
# TODO: Problem: User can always see the time shedule. Filter fields with respect of permission.

View File

@ -783,13 +783,12 @@ class ItemViewSet(rest_api.viewsets.ModelViewSet):
"""
Calls self.permission_denied() if the requesting user has not the
permission to see and in case of create, update or destroy requests
the permission to manage.
the permission to manage and to see organizational items.
"""
if not request.user.has_perm('agenda.can_see_agenda'):
self.permission_denied(request)
elif (self.action in ('create', 'update', 'destroy')
and not request.user.has_perm('agenda.can_manage_agenda')):
# This is the same as self.action not in ('list', 'retrieve')
if (not request.user.has_perm('agenda.can_see_agenda') or
(self.action in ('create', 'update', 'destroy') and not
(request.user.has_perm('agenda.can_manage_agenda') and
request.user.has_perm('agenda.can_see_orga_items')))):
self.permission_denied(request)
def check_object_permissions(self, request, obj):
@ -805,7 +804,6 @@ class ItemViewSet(rest_api.viewsets.ModelViewSet):
Filters organizational items if the user has no permission to see it.
"""
queryset = Item.objects.all()
if (not self.request.user.has_perm('agenda.can_see_orga_items') and
not self.request.user.has_perm('agenda.can_manage_agenda')):
if not self.request.user.has_perm('agenda.can_see_orga_items'):
queryset = queryset.exclude(type__exact=Item.ORGANIZATIONAL_ITEM)
return queryset

View File

@ -17,7 +17,7 @@ class CoreAppConfig(AppConfig):
from openslides.utils.autoupdate import inform_changed_data_receiver
from openslides.utils.rest_api import router
from .signals import setup_general_config
from .views import CustomSlideViewSet
from .views import CustomSlideViewSet, TagViewSet
# Connect signals.
config_signal.connect(setup_general_config, dispatch_uid='setup_general_config')
@ -28,6 +28,7 @@ class CoreAppConfig(AppConfig):
# Register viewset.
router.register('core/customslide', CustomSlideViewSet)
router.register('core/tag', TagViewSet)
# Update data when any model of any installed app is saved or deleted
signals.post_save.connect(inform_changed_data_receiver, dispatch_uid='inform_changed_data_receiver')

View File

@ -4,15 +4,16 @@ from django.utils.translation import ugettext_lazy, ugettext_noop
# TODO: activate the following line after using the apploader
# from django.contrib.auth import get_user_model
from openslides.utils.models import AbsoluteUrlMixin
from openslides.projector.models import SlideMixin
from openslides.utils.models import AbsoluteUrlMixin
from openslides.utils.rest_api import RESTModelMixin
# Imports the default user so that other apps can import it from here.
# TODO: activate this with the new apploader
# User = get_user_model()
class CustomSlide(SlideMixin, AbsoluteUrlMixin, models.Model):
class CustomSlide(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, models.Model):
"""
Model for Slides, only for the projector.
"""
@ -46,7 +47,7 @@ class CustomSlide(SlideMixin, AbsoluteUrlMixin, models.Model):
return url
class Tag(AbsoluteUrlMixin, models.Model):
class Tag(RESTModelMixin, AbsoluteUrlMixin, models.Model):
"""
Model to save tags.
"""

View File

@ -1,11 +1,19 @@
from rest_framework import serializers
from openslides.utils import rest_api
from .models import CustomSlide
from .models import CustomSlide, Tag
class CustomSlideSerializer(serializers.ModelSerializer):
class CustomSlideSerializer(rest_api.serializers.HyperlinkedModelSerializer):
"""
Serializer for a core.models.CustomSlide objects.
Serializer for core.models.CustomSlide objects.
"""
class Meta:
model = CustomSlide
class TagSerializer(rest_api.serializers.HyperlinkedModelSerializer):
"""
Serializer for core.models.Tag objects.
"""
class Meta:
model = Tag

View File

@ -21,7 +21,7 @@ from openslides.utils.widgets import Widget
from .forms import SelectWidgetsForm
from .models import CustomSlide, Tag
from .exceptions import TagException
from .serializers import CustomSlideSerializer
from .serializers import CustomSlideSerializer, TagSerializer
class DashboardView(utils_views.AjaxMixin, utils_views.TemplateView):
@ -217,6 +217,23 @@ class CustomSlideDeleteView(CustomSlideViewMixin, utils_views.DeleteView):
pass
class CustomSlideViewSet(rest_api.viewsets.ModelViewSet):
"""
API endpoint to view, edit and delete custom slides.
"""
model = CustomSlide
queryset = CustomSlide.objects.all()
serializer_class = CustomSlideSerializer
def check_permissions(self, request):
"""
Calls self.permission_denied() if the requesting user has not the
permission to manage.
"""
if not request.user.has_perm('core.can_manage_projector'):
self.permission_denied(request)
class TagListView(utils_views.AjaxMixin, utils_views.ListView):
"""
View to list and manipulate tags.
@ -295,18 +312,19 @@ class TagListView(utils_views.AjaxMixin, utils_views.ListView):
**context)
class CustomSlideViewSet(rest_api.viewsets.ModelViewSet):
class TagViewSet(rest_api.viewsets.ModelViewSet):
"""
API endpoint to view, edit and delete custom slides.
API endpoint to view, edit and delete tags.
"""
model = CustomSlide
queryset = CustomSlide.objects.all()
serializer_class = CustomSlideSerializer
model = Tag
queryset = Tag.objects.all()
serializer_class = TagSerializer
def check_permissions(self, request):
"""
Calls self.permission_denied() if the requesting user has not the
permission to manage.
permission to manage and it's a create, update or detroy request.
"""
if not request.user.has_perm('core.can_manage_projector'):
if (self.action in ('create', 'update', 'destroy')
and not request.user.has_perm('core.can_manage_tags')):
self.permission_denied(request)

View File

@ -8,7 +8,7 @@ class UserMainMenuEntry(MainMenuEntry):
Main menu entry for the participant app.
"""
verbose_name = ugettext_lazy('Users')
required_permission = 'users.can_see'
required_permission = 'users.can_see_extra_data'
default_weight = 50
pattern_name = 'user_list'
icon_css_class = 'icon-user'

View File

@ -11,6 +11,7 @@ from django.utils.translation import ugettext_lazy, ugettext_noop
from openslides.projector.models import SlideMixin
from openslides.utils.models import AbsoluteUrlMixin
from openslides.utils.rest_api import RESTModelMixin
class UserManager(BaseUserManager):
@ -21,7 +22,7 @@ class UserManager(BaseUserManager):
return user
class User(SlideMixin, AbsoluteUrlMixin, PermissionsMixin, AbstractBaseUser):
class User(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, PermissionsMixin, AbstractBaseUser):
USERNAME_FIELD = 'username'
slide_callback_name = 'user'
@ -72,7 +73,8 @@ class User(SlideMixin, AbsoluteUrlMixin, PermissionsMixin, AbstractBaseUser):
class Meta:
permissions = (
('can_see', ugettext_noop('Can see users')),
('can_see_name', ugettext_noop('Can see names of users')),
('can_see_extra_data', ugettext_noop('Can see extra data of users')),
('can_manage', ugettext_noop('Can manage users')),
)
ordering = ('last_name',)

View File

@ -1,9 +1,9 @@
from rest_framework import serializers
from openslides.utils import rest_api
from .models import User
class UserSerializer(serializers.ModelSerializer):
class UserShortSerializer(rest_api.serializers.ModelSerializer):
"""
Serializer for a users.models.User objects.
"""
@ -11,5 +11,26 @@ class UserSerializer(serializers.ModelSerializer):
model = User
fields = (
'username',
'title',
'first_name',
'last_name')
'last_name',
'structure_level')
class UserFullSerializer(rest_api.serializers.ModelSerializer):
"""
Serializer for a users.models.User objects.
"""
class Meta:
model = User
fields = (
'is_present',
'username',
'title',
'first_name',
'last_name',
'structure_level',
'about_me',
'comment',
'default_password',
'is_active')

View File

@ -138,15 +138,27 @@ def create_builtin_groups_and_admin(sender, **kwargs):
perm_16 = Permission.objects.get(content_type=ct_assignment, codename='can_see_assignment')
ct_users = ContentType.objects.get(app_label='users', model='user')
perm_17 = Permission.objects.get(content_type=ct_users, codename='can_see')
perm_users_can_see_name = Permission.objects.get(content_type=ct_users, codename='can_see_name')
perm_users_can_see_extra_data = Permission.objects.get(content_type=ct_users, codename='can_see_extra_data')
ct_mediafile = ContentType.objects.get(app_label='mediafile', model='mediafile')
perm_18 = Permission.objects.get(content_type=ct_mediafile, codename='can_see')
base_permission_list = (
perm_11,
perm_12,
perm_13,
perm_14,
perm_15,
perm_16,
perm_users_can_see_name,
perm_users_can_see_extra_data,
perm_18)
group_anonymous = Group.objects.create(name=ugettext_noop('Anonymous'), pk=1)
group_anonymous.permissions.add(perm_11, perm_12, perm_13, perm_14, perm_15, perm_16, perm_17, perm_18)
group_anonymous.permissions.add(*base_permission_list)
group_registered = Group.objects.create(name=ugettext_noop('Registered'), pk=2)
group_registered.permissions.add(perm_11, perm_12, perm_13, perm_14, perm_15, perm_16, perm_17, perm_18, can_speak)
group_registered.permissions.add(can_speak, *base_permission_list)
# Delegates (pk 3)
perm_31 = Permission.objects.get(content_type=ct_motion, codename='can_create_motion')
@ -178,8 +190,9 @@ def create_builtin_groups_and_admin(sender, **kwargs):
group_staff.permissions.add(perm_31, perm_33, perm_34, perm_35)
# add staff permissions
group_staff.permissions.add(perm_41, perm_42, perm_43, perm_44, perm_45, perm_46, perm_47, perm_48, can_manage_tags)
# add can_see_user permission
group_staff.permissions.add(perm_17) # TODO: Remove this redundancy after cleanup of the permission system
# add can_see_name and can_see_extra_data permissions
# TODO: Remove this redundancy after cleanup of the permission system.
group_staff.permissions.add(perm_users_can_see_name, perm_users_can_see_extra_data)
# Admin user
create_or_reset_admin_user()

View File

@ -20,14 +20,14 @@ from .forms import (GroupForm, UserCreateForm, UserMultipleCreateForm,
UsersettingsForm, UserUpdateForm)
from .models import Group, User
from .pdf import users_to_pdf, users_passwords_to_pdf
from .serializers import UserSerializer
from .serializers import UserFullSerializer, UserShortSerializer
class UserListView(ListView):
"""
Show all users.
"""
required_permission = 'users.can_see'
required_permission = 'users.can_see_extra_data'
context_object_name = 'users'
def get_queryset(self):
@ -52,7 +52,7 @@ class UserDetailView(DetailView, PermissionMixin):
"""
Classed based view to show a specific user in the interface.
"""
required_permission = 'users.can_see'
required_permission = 'users.can_see_extra_data'
model = User
context_object_name = 'shown_user'
@ -202,7 +202,7 @@ class UsersListPDF(PDFView):
"""
Generate the userliste as PDF.
"""
required_permission = 'users.can_see'
required_permission = 'users.can_see_extra_data'
filename = ugettext_lazy("user-list")
document_title = ugettext_lazy('List of Users')
@ -263,21 +263,32 @@ class ResetPasswordView(SingleObjectMixin, QuestionView):
class UserViewSet(rest_api.viewsets.ModelViewSet):
"""
API endpoint to view, edit and delete users.
API endpoint to create, view, edit and delete users.
"""
model = User
queryset = User.objects.all()
serializer_class = UserSerializer
def check_permissions(self, request):
"""
Calls self.permission_denied() if the requesting user has not the
permission to manage.
Calls self.permission_denied() if the requesting user has not all
permissions to see users.
"""
# TODO: More work on this required.
if not request.user.has_perm('users.can_manage'):
if (not request.user.has_perm('users.can_see_name') or
(self.action in ('create', 'update', 'destroy') and not
(request.user.has_perm('users.can_manage') and
request.user.has_perm('users.can_see_extra_data')))):
self.permission_denied(request)
def get_serializer_class(self):
"""
Returns different serializer classes with respect to users permissions.
"""
if self.request.user.has_perm('users.can_see_extra_data'):
serializer_class = UserFullSerializer
else:
serializer_class = UserShortSerializer
return serializer_class
class GroupListView(ListView):
"""

View File

@ -1,5 +1,5 @@
from django.core.urlresolvers import reverse
from rest_framework import permissions, routers, viewsets # noqa
from rest_framework import permissions, routers, serializers, viewsets # noqa
router = routers.DefaultRouter()

View File

@ -55,7 +55,8 @@ class DefaultGroups(TestCase):
'agenda.can_see_orga_items',
'motion.can_see_motion',
'assignment.can_see_assignment',
'users.can_see',
'users.can_see_name',
'users.can_see_extra_data',
'mediafile.can_see')
for perm_string in default_perms:
perm_string_list = []

View File

@ -189,16 +189,16 @@ class LockoutProtection(TestCase):
field=None,
errors='You can not remove the permission to manage users from the last group you are in.')
def test_remove_permission_can_see_user_from_registered(self):
self.assertTrue(self.user.has_perm('users.can_see'))
def test_remove_permission_user_can_see_name_from_registered(self):
self.assertTrue(self.user.has_perm('users.can_see_name'))
# Remove perm from registered group
can_see_perm = Permission.objects.get(
content_type=ContentType.objects.get(app_label='users', model='user'),
codename='can_see')
codename='can_see_name')
get_registered_group().permissions.remove(can_see_perm)
# Reload user
self.user = User.objects.get(pk=1)
self.assertTrue(self.user.has_perm('users.can_see'))
self.assertTrue(self.user.has_perm('users.can_see_name'))
class TestUserSettings(TestCase):