diff --git a/openslides/agenda/apps.py b/openslides/agenda/apps.py index 5a1d22f3f..3a9b98c26 100644 --- a/openslides/agenda/apps.py +++ b/openslides/agenda/apps.py @@ -15,8 +15,10 @@ class AgendaAppConfig(AppConfig): from openslides.config.signals import config_signal from openslides.projector.api import register_slide from openslides.projector.signals import projector_overlays + from openslides.utils.rest_api import router from .signals import agenda_list_of_speakers, setup_agenda_config, listen_to_related_object_delete_signal from .slides import agenda_slide + from .views import ItemViewSet # Connect signals. config_signal.connect(setup_agenda_config, dispatch_uid='setup_agenda_config') @@ -26,3 +28,6 @@ class AgendaAppConfig(AppConfig): # Register slides. Item = self.get_model('Item') register_slide('agenda', agenda_slide, Item) + + # Register viewset. + router.register('agenda/item', ItemViewSet) diff --git a/openslides/agenda/serializers.py b/openslides/agenda/serializers.py new file mode 100644 index 000000000..576fc8781 --- /dev/null +++ b/openslides/agenda/serializers.py @@ -0,0 +1,34 @@ +from rest_framework import serializers + +from .models import Item, Speaker + + +class SpeakerSerializer(serializers.HyperlinkedModelSerializer): + """ + Serializer for a agenda.models.Speaker objects. + """ + class Meta: + model = Speaker + fields = ( + 'id', + 'user', + 'begin_time', + 'end_time', + 'weight') + + +class ItemSerializer(serializers.ModelSerializer): + """ + 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) + speaker_set = SpeakerSerializer(many=True, read_only=True) + # 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. diff --git a/openslides/agenda/views.py b/openslides/agenda/views.py index 3de13950d..7780f50ac 100644 --- a/openslides/agenda/views.py +++ b/openslides/agenda/views.py @@ -23,6 +23,7 @@ from openslides.projector.api import ( get_projector_overlays_js, get_overlays, update_projector) +from openslides.utils import rest_api from openslides.utils.exceptions import OpenSlidesError from openslides.utils.pdf import stylesheet from openslides.utils.utils import html_strong @@ -42,6 +43,7 @@ from openslides.utils.views import ( from .csv_import import import_agenda_items from .forms import AppendSpeakerForm, ItemForm, ItemOrderForm, RelatedItemForm from .models import Item, Speaker +from .serializers import ItemSerializer class Overview(TemplateView): @@ -773,3 +775,42 @@ class ItemCSVImportView(CSVImportView): required_permission = 'agenda.can_manage_agenda' success_url_name = 'item_overview' template_name = 'agenda/item_form_csv_import.html' + + +class ItemViewSet(rest_api.viewsets.ModelViewSet): + """ + API endpoint to view, edit and delete agenda items. + """ + model = Item + serializer_class = ItemSerializer + + def check_permissions(self, request): + """ + 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. + """ + 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') + self.permission_denied(request) + + def check_object_permissions(self, request, obj): + """ + Checks if the requesting user has permission to see also an + organizational item if it is one. + """ + if obj.type == obj.ORGANIZATIONAL_ITEM and not request.user.has_perm('agenda.can_see_orga_items'): + self.permission_denied(request) + + def get_queryset(self): + """ + 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')): + queryset = queryset.exclude(type__exact=Item.ORGANIZATIONAL_ITEM) + return queryset diff --git a/openslides/core/apps.py b/openslides/core/apps.py index fb0fa62e0..caafdbf69 100644 --- a/openslides/core/apps.py +++ b/openslides/core/apps.py @@ -13,7 +13,9 @@ class CoreAppConfig(AppConfig): # Import all required stuff. from openslides.config.signals import config_signal from openslides.projector.api import register_slide_model + from openslides.utils.rest_api import router from .signals import setup_general_config + from .views import CustomSlideViewSet # Connect signals. config_signal.connect(setup_general_config, dispatch_uid='setup_general_config') @@ -21,3 +23,6 @@ class CoreAppConfig(AppConfig): # Register slides. CustomSlide = self.get_model('CustomSlide') register_slide_model(CustomSlide, 'core/customslide_slide.html') + + # Register viewset. + router.register('core/customslide', CustomSlideViewSet) diff --git a/openslides/core/serializers.py b/openslides/core/serializers.py new file mode 100644 index 000000000..1a052dd27 --- /dev/null +++ b/openslides/core/serializers.py @@ -0,0 +1,11 @@ +from rest_framework import serializers + +from .models import CustomSlide + + +class CustomSlideSerializer(serializers.ModelSerializer): + """ + Serializer for a core.models.CustomSlide objects. + """ + class Meta: + model = CustomSlide diff --git a/openslides/core/views.py b/openslides/core/views.py index 1e41107ec..fade07f12 100644 --- a/openslides/core/views.py +++ b/openslides/core/views.py @@ -12,14 +12,16 @@ from haystack.views import SearchView as _SearchView from openslides import get_version as get_openslides_version from openslides import get_git_commit_id, RELEASE from openslides.config.api import config +from openslides.utils import rest_api +from openslides.utils import views as utils_views from openslides.utils.plugins import get_plugin_description, get_plugin_verbose_name, get_plugin_version from openslides.utils.signals import template_manipulation -from openslides.utils import views as utils_views from openslides.utils.widgets import Widget from .forms import SelectWidgetsForm from .models import CustomSlide, Tag from .exceptions import TagException +from .serializers import CustomSlideSerializer class DashboardView(utils_views.AjaxMixin, utils_views.TemplateView): @@ -291,3 +293,20 @@ class TagListView(utils_views.AjaxMixin, utils_views.ListView): action=getattr(self, 'action', None), error=getattr(self, 'error', None), **context) + + +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) diff --git a/openslides/global_settings.py b/openslides/global_settings.py index 1c1141e05..ff00236aa 100644 --- a/openslides/global_settings.py +++ b/openslides/global_settings.py @@ -88,6 +88,7 @@ INSTALLED_APPS = ( 'mptt', 'haystack', # full-text-search 'ckeditor', + 'rest_framework', 'openslides.poll', 'openslides.core', 'openslides.account', diff --git a/openslides/urls.py b/openslides/urls.py index 774e2ba0c..31191e2ae 100644 --- a/openslides/urls.py +++ b/openslides/urls.py @@ -3,6 +3,7 @@ from django.conf.urls import include, patterns, url from openslides.core.views import ErrorView from openslides.utils.plugins import get_urlpatterns +from openslides.utils.rest_api import router handler403 = ErrorView.as_view(status_code=403) handler404 = ErrorView.as_view(status_code=404) @@ -31,6 +32,13 @@ urlpatterns += patterns( (r'^ckeditor/', include('ckeditor.urls')), ) +urlpatterns += patterns( + '', + url(r'^api/', include(router.urls)), + # TODO: Remove the next line if you are sure. + # url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')) +) + # TODO: move this patterns into core or the participant app from openslides.users.views import UserSettingsView, UserPasswordSettingsView urlpatterns += patterns( diff --git a/openslides/users/apps.py b/openslides/users/apps.py index 33b66a983..a58dfec76 100644 --- a/openslides/users/apps.py +++ b/openslides/users/apps.py @@ -15,7 +15,9 @@ class UsersAppConfig(AppConfig): from openslides.config.signals import config_signal from openslides.core.signals import post_database_setup from openslides.projector.api import register_slide_model + from openslides.utils.rest_api import router from .signals import create_builtin_groups_and_admin, setup_users_config, user_post_save + from .views import UserViewSet # Load User model. User = self.get_model('User') @@ -27,3 +29,6 @@ class UsersAppConfig(AppConfig): # Register slides. register_slide_model(User, 'participant/user_slide.html') + + # Register viewset. + router.register('users/user', UserViewSet) diff --git a/openslides/users/serializers.py b/openslides/users/serializers.py new file mode 100644 index 000000000..96c5fcbd8 --- /dev/null +++ b/openslides/users/serializers.py @@ -0,0 +1,15 @@ +from rest_framework import serializers + +from .models import User + + +class UserSerializer(serializers.ModelSerializer): + """ + Serializer for a users.models.User objects. + """ + class Meta: + model = User + fields = ( + 'username', + 'first_name', + 'last_name') diff --git a/openslides/users/views.py b/openslides/users/views.py index fe6c87759..665a26beb 100644 --- a/openslides/users/views.py +++ b/openslides/users/views.py @@ -6,6 +6,7 @@ from django.core.urlresolvers import reverse from django.utils.translation import ugettext as _, ugettext_lazy, activate from openslides.config.api import config +from openslides.utils import rest_api from openslides.utils.utils import delete_default_permissions, html_strong from openslides.utils.views import ( CreateView, CSVImportView, DeleteView, DetailView, FormView, ListView, @@ -19,6 +20,7 @@ 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 class UserListView(ListView): @@ -259,6 +261,24 @@ class ResetPasswordView(SingleObjectMixin, QuestionView): return _('The Password for %s was successfully reset.') % html_strong(self.get_object()) +class UserViewSet(rest_api.viewsets.ModelViewSet): + """ + API endpoint to 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. + """ + # TODO: More work on this required. + if not request.user.has_perm('users.can_manage'): + self.permission_denied(request) + + class GroupListView(ListView): """ Overview over all groups. diff --git a/openslides/utils/rest_api.py b/openslides/utils/rest_api.py new file mode 100644 index 000000000..8fc21f00e --- /dev/null +++ b/openslides/utils/rest_api.py @@ -0,0 +1,3 @@ +from rest_framework import permissions, routers, viewsets # noqa + +router = routers.DefaultRouter() diff --git a/requirements_production.txt b/requirements_production.txt index d129cec6f..532cf54f8 100644 --- a/requirements_production.txt +++ b/requirements_production.txt @@ -5,6 +5,7 @@ bleach>=1.4,<1.5 django-ckeditor-updated>=4.2.3,<4.4 django-haystack>=2.1,<2.4 django-mptt>=0.6,<0.7 +djangorestframework>=3.0.1,<3.1 jsonfield>=0.9.19,<1.1 natsort>=3.2,<3.6 reportlab>=3.0,<3.2