Merge pull request #1427 from normanjaeckel/DjangoRESTFrameworkConfigMediafile
Django REST framework: Motion, mediafile, config app and rework.
This commit is contained in:
commit
117c34c5f0
@ -437,6 +437,6 @@ class Speaker(RESTModelMixin, AbsoluteUrlMixin, models.Model):
|
||||
|
||||
def get_root_rest_element(self):
|
||||
"""
|
||||
Returns the item to this instance which is the root rest element.
|
||||
Returns the item to this instance which is the root REST element.
|
||||
"""
|
||||
return self.item
|
||||
|
@ -1,10 +1,11 @@
|
||||
from openslides.utils import rest_api
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from openslides.utils.rest_api import serializers
|
||||
|
||||
from .models import Item, Speaker
|
||||
|
||||
|
||||
class SpeakerSerializer(rest_api.serializers.HyperlinkedModelSerializer):
|
||||
class SpeakerSerializer(serializers.HyperlinkedModelSerializer):
|
||||
"""
|
||||
Serializer for agenda.models.Speaker objects.
|
||||
"""
|
||||
@ -18,7 +19,7 @@ class SpeakerSerializer(rest_api.serializers.HyperlinkedModelSerializer):
|
||||
'weight')
|
||||
|
||||
|
||||
class RelatedItemRelatedField(rest_api.serializers.RelatedField):
|
||||
class RelatedItemRelatedField(serializers.RelatedField):
|
||||
"""
|
||||
A custom field to use for the `content_object` generic relationship.
|
||||
"""
|
||||
@ -35,17 +36,37 @@ class RelatedItemRelatedField(rest_api.serializers.RelatedField):
|
||||
return reverse(view_name, kwargs={'pk': value.pk}, request=request)
|
||||
|
||||
|
||||
class ItemSerializer(rest_api.serializers.HyperlinkedModelSerializer):
|
||||
class ItemSerializer(serializers.HyperlinkedModelSerializer):
|
||||
"""
|
||||
Serializer for agenda.models.Item objects.
|
||||
"""
|
||||
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')
|
||||
get_title = serializers.CharField(read_only=True)
|
||||
get_title_supplement = serializers.CharField(read_only=True)
|
||||
content_object = RelatedItemRelatedField(read_only=True)
|
||||
item_no = serializers.CharField(read_only=True)
|
||||
speaker_set = SpeakerSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Item
|
||||
exclude = ('content_type', 'object_id')
|
||||
fields = (
|
||||
'url',
|
||||
'item_number',
|
||||
'item_no',
|
||||
'title',
|
||||
'get_title',
|
||||
'get_title_supplement',
|
||||
'text',
|
||||
'comment',
|
||||
'closed',
|
||||
'type',
|
||||
'duration',
|
||||
'speaker_set',
|
||||
'speaker_list_closed',
|
||||
'content_object',
|
||||
'weight',
|
||||
'lft',
|
||||
'rght',
|
||||
'tree_id',
|
||||
'level',
|
||||
'parent',
|
||||
'tags',)
|
||||
|
@ -23,9 +23,9 @@ from openslides.projector.api import (
|
||||
get_active_slide,
|
||||
get_projector_overlays_js,
|
||||
get_overlays)
|
||||
from openslides.utils import rest_api
|
||||
from openslides.utils.exceptions import OpenSlidesError
|
||||
from openslides.utils.pdf import stylesheet
|
||||
from openslides.utils.rest_api import viewsets
|
||||
from openslides.utils.utils import html_strong
|
||||
from openslides.utils.views import (
|
||||
AjaxMixin,
|
||||
@ -773,9 +773,9 @@ class ItemCSVImportView(CSVImportView):
|
||||
template_name = 'agenda/item_form_csv_import.html'
|
||||
|
||||
|
||||
class ItemViewSet(rest_api.viewsets.ModelViewSet):
|
||||
class ItemViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
API endpoint to retrieve, create, edit and delete agenda items.
|
||||
API endpoint to list, retrieve, create, update and destroy agenda items.
|
||||
"""
|
||||
model = Item
|
||||
serializer_class = ItemSerializer
|
||||
@ -783,8 +783,9 @@ class ItemViewSet(rest_api.viewsets.ModelViewSet):
|
||||
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 and to see organizational items.
|
||||
permission to see the agenda and in case of create, update or destroy
|
||||
requests the permission to manage the agenda and to see organizational
|
||||
items.
|
||||
"""
|
||||
if (not request.user.has_perm('agenda.can_see_agenda') or
|
||||
(self.action in ('create', 'update', 'destroy') and not
|
||||
@ -802,7 +803,7 @@ class ItemViewSet(rest_api.viewsets.ModelViewSet):
|
||||
|
||||
def get_queryset(self):
|
||||
"""
|
||||
Filters organizational items if the user has no permission to see it.
|
||||
Filters organizational items if the user has no permission to see them.
|
||||
"""
|
||||
queryset = Item.objects.all()
|
||||
if not self.request.user.has_perm('agenda.can_see_orga_items'):
|
||||
|
@ -36,7 +36,7 @@ class AssignmentCandidate(RESTModelMixin, models.Model):
|
||||
|
||||
def get_root_rest_element(self):
|
||||
"""
|
||||
Returns the assignment to this instance which is the root rest element.
|
||||
Returns the assignment to this instance which is the root REST element.
|
||||
"""
|
||||
return self.assignment
|
||||
|
||||
@ -274,7 +274,7 @@ class AssignmentVote(RESTModelMixin, BaseVote):
|
||||
|
||||
def get_root_rest_element(self):
|
||||
"""
|
||||
Returns the assignment to this instance which is the root rest element.
|
||||
Returns the assignment to this instance which is the root REST element.
|
||||
"""
|
||||
return self.option.poll.assignment
|
||||
|
||||
@ -289,7 +289,7 @@ class AssignmentOption(RESTModelMixin, BaseOption):
|
||||
|
||||
def get_root_rest_element(self):
|
||||
"""
|
||||
Returns the assignment to this instance which is the root rest element.
|
||||
Returns the assignment to this instance which is the root REST element.
|
||||
"""
|
||||
return self.poll.assignment
|
||||
|
||||
@ -348,6 +348,6 @@ class AssignmentPoll(RESTModelMixin, SlideMixin, CollectDefaultVotesMixin,
|
||||
|
||||
def get_root_rest_element(self):
|
||||
"""
|
||||
Returns the assignment to this instance which is the root rest element.
|
||||
Returns the assignment to this instance which is the root REST element.
|
||||
"""
|
||||
return self.assignment
|
||||
|
@ -1,4 +1,4 @@
|
||||
from openslides.utils import rest_api
|
||||
from openslides.utils.rest_api import serializers
|
||||
|
||||
from .models import (
|
||||
models,
|
||||
@ -9,7 +9,7 @@ from .models import (
|
||||
AssignmentVote)
|
||||
|
||||
|
||||
class AssignmentCandidateSerializer(rest_api.serializers.HyperlinkedModelSerializer):
|
||||
class AssignmentCandidateSerializer(serializers.HyperlinkedModelSerializer):
|
||||
"""
|
||||
Serializer for assignment.models.AssignmentCandidate objects.
|
||||
"""
|
||||
@ -22,7 +22,7 @@ class AssignmentCandidateSerializer(rest_api.serializers.HyperlinkedModelSeriali
|
||||
'blocked')
|
||||
|
||||
|
||||
class AssignmentVoteSerializer(rest_api.serializers.HyperlinkedModelSerializer):
|
||||
class AssignmentVoteSerializer(serializers.HyperlinkedModelSerializer):
|
||||
"""
|
||||
Serializer for assignment.models.AssignmentVote objects.
|
||||
"""
|
||||
@ -33,7 +33,7 @@ class AssignmentVoteSerializer(rest_api.serializers.HyperlinkedModelSerializer):
|
||||
'value')
|
||||
|
||||
|
||||
class AssignmentOptionSerializer(rest_api.serializers.HyperlinkedModelSerializer):
|
||||
class AssignmentOptionSerializer(serializers.HyperlinkedModelSerializer):
|
||||
"""
|
||||
Serializer for assignment.models.AssignmentOption objects.
|
||||
"""
|
||||
@ -46,9 +46,9 @@ class AssignmentOptionSerializer(rest_api.serializers.HyperlinkedModelSerializer
|
||||
'assignmentvote_set')
|
||||
|
||||
|
||||
class FilterPollListSerializer(rest_api.serializers.ListSerializer):
|
||||
class FilterPollListSerializer(serializers.ListSerializer):
|
||||
"""
|
||||
Customized serilizer to filter polls and exclude unpublished ones.
|
||||
Customized serializer to filter polls (exclude unpublished).
|
||||
"""
|
||||
def to_representation(self, data):
|
||||
"""
|
||||
@ -62,7 +62,7 @@ class FilterPollListSerializer(rest_api.serializers.ListSerializer):
|
||||
return [self.child.to_representation(item) for item in iterable]
|
||||
|
||||
|
||||
class AssignmentAllPollSerializer(rest_api.serializers.HyperlinkedModelSerializer):
|
||||
class AssignmentAllPollSerializer(serializers.HyperlinkedModelSerializer):
|
||||
"""
|
||||
Serializer for assignment.models.AssignmentPoll objects.
|
||||
|
||||
@ -103,25 +103,25 @@ class AssignmentShortPollSerializer(AssignmentAllPollSerializer):
|
||||
'votescast')
|
||||
|
||||
|
||||
class AssignmentFullSerializer(rest_api.serializers.HyperlinkedModelSerializer):
|
||||
class AssignmentFullSerializer(serializers.HyperlinkedModelSerializer):
|
||||
"""
|
||||
Serializer for assignment.models.Assignment objects. With all polls.
|
||||
"""
|
||||
assignmentcandidate_set = AssignmentCandidateSerializer(many=True, read_only=True)
|
||||
poll_set = AssignmentAllPollSerializer(many=True, read_only=True)
|
||||
tags = rest_api.serializers.HyperlinkedRelatedField(many=True, read_only=True, view_name='tag-detail')
|
||||
|
||||
class Meta:
|
||||
model = Assignment
|
||||
fields = (
|
||||
'url',
|
||||
'name',
|
||||
'description',
|
||||
'posts',
|
||||
'poll_description_default',
|
||||
'status',
|
||||
'assignmentcandidate_set',
|
||||
'poll_description_default',
|
||||
'poll_set',
|
||||
'tags')
|
||||
'tags',)
|
||||
|
||||
|
||||
class AssignmentShortSerializer(AssignmentFullSerializer):
|
||||
@ -133,11 +133,12 @@ class AssignmentShortSerializer(AssignmentFullSerializer):
|
||||
class Meta:
|
||||
model = Assignment
|
||||
fields = (
|
||||
'url',
|
||||
'name',
|
||||
'description',
|
||||
'posts',
|
||||
'poll_description_default',
|
||||
'status',
|
||||
'assignmentcandidate_set',
|
||||
'poll_description_default',
|
||||
'poll_set',
|
||||
'tags')
|
||||
'tags',)
|
||||
|
@ -14,8 +14,8 @@ from openslides.agenda.views import CreateRelatedAgendaItemView as _CreateRelate
|
||||
from openslides.config.api import config
|
||||
from openslides.users.models import Group, User # TODO: remove this
|
||||
from openslides.poll.views import PollFormView
|
||||
from openslides.utils import rest_api
|
||||
from openslides.utils.pdf import stylesheet
|
||||
from openslides.utils.rest_api import viewsets
|
||||
from openslides.utils.utils import html_strong
|
||||
from openslides.utils.views import (CreateView, DeleteView, DetailView,
|
||||
ListView, PDFView, PermissionMixin,
|
||||
@ -190,9 +190,9 @@ class AssignmentRunOtherDeleteView(SingleObjectMixin, QuestionView):
|
||||
self.is_blocked = self.get_object().is_blocked(self.person)
|
||||
|
||||
|
||||
class AssignmentViewSet(rest_api.viewsets.ModelViewSet):
|
||||
class AssignmentViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
API endpoint to retrieve, create, edit and delete assignments.
|
||||
API endpoint to list, retrieve, create, update and destroy assignments.
|
||||
"""
|
||||
model = Assignment
|
||||
queryset = Assignment.objects.all()
|
||||
@ -200,8 +200,8 @@ class AssignmentViewSet(rest_api.viewsets.ModelViewSet):
|
||||
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 and to see organizational items.
|
||||
permission to see assignments and in case of create, update or destroy
|
||||
requests the permission to manage assignments.
|
||||
"""
|
||||
if (not request.user.has_perm('assignment.can_see_assignment') or
|
||||
(self.action in ('create', 'update', 'destroy') and not
|
||||
|
@ -39,6 +39,14 @@ class ConfigHandler(object):
|
||||
config_variable.on_change()
|
||||
break
|
||||
|
||||
def get_data_as_dict(self):
|
||||
"""
|
||||
Returns all config variables as dictionary retrieved from the config cache.
|
||||
"""
|
||||
if not hasattr(self, '_cache'):
|
||||
self.setup_cache()
|
||||
return self._cache
|
||||
|
||||
def get_default(self, key):
|
||||
"""
|
||||
Returns the default value for 'key'.
|
||||
|
@ -9,3 +9,10 @@ class ConfigAppConfig(AppConfig):
|
||||
# Load main menu entry.
|
||||
# Do this by just importing all from this file.
|
||||
from . import main_menu # noqa
|
||||
|
||||
# Import all required stuff.
|
||||
from openslides.utils.rest_api import router
|
||||
from .views import ConfigViewSet
|
||||
|
||||
# Register viewsets.
|
||||
router.register('config/config', ConfigViewSet, 'config')
|
||||
|
@ -3,6 +3,7 @@ from django.contrib import messages
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from openslides.utils.rest_api import response, viewsets
|
||||
from openslides.utils.views import FormView
|
||||
|
||||
from .api import config
|
||||
@ -100,3 +101,22 @@ class ConfigView(FormView):
|
||||
config[key] = form.cleaned_data[key]
|
||||
messages.success(self.request, _('%s settings successfully saved.') % _(self.config_collection.title))
|
||||
return super(ConfigView, self).form_valid(form)
|
||||
|
||||
|
||||
class ConfigViewSet(viewsets.ViewSet):
|
||||
"""
|
||||
API endpoint to list and update the config.
|
||||
"""
|
||||
def list(self, request):
|
||||
"""
|
||||
Lists als config variables. Everybody can see this.
|
||||
"""
|
||||
# TODO: Check if we need permission check here.
|
||||
return response.Response(config.get_data_as_dict())
|
||||
|
||||
def update(self, request, pk=None):
|
||||
if not request.user.has_perm('config.can_manage'):
|
||||
self.permission_denied(request)
|
||||
else:
|
||||
# TODO: Implement update method
|
||||
self.permission_denied(request)
|
||||
|
@ -30,7 +30,11 @@ class CoreAppConfig(AppConfig):
|
||||
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')
|
||||
signals.post_delete.connect(inform_changed_data_receiver, dispatch_uid='inform_changed_data_receiver')
|
||||
# TODO: test if the m2m_changed signal is also needed
|
||||
# Update data when any model of any installed app is saved or deleted.
|
||||
# TODO: Test if the m2m_changed signal is also needed.
|
||||
signals.post_save.connect(
|
||||
inform_changed_data_receiver,
|
||||
dispatch_uid='inform_changed_data_receiver')
|
||||
signals.post_delete.connect(
|
||||
inform_changed_data_receiver,
|
||||
dispatch_uid='inform_changed_data_receiver')
|
||||
|
@ -43,7 +43,7 @@ class CustomSlide(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, models.Model):
|
||||
elif link == 'delete':
|
||||
url = reverse('customslide_delete', args=[str(self.pk)])
|
||||
else:
|
||||
url = super(CustomSlide, self).get_absolute_url(link)
|
||||
url = super().get_absolute_url(link)
|
||||
return url
|
||||
|
||||
|
||||
|
@ -1,19 +1,21 @@
|
||||
from openslides.utils import rest_api
|
||||
from openslides.utils.rest_api import serializers
|
||||
|
||||
from .models import CustomSlide, Tag
|
||||
|
||||
|
||||
class CustomSlideSerializer(rest_api.serializers.HyperlinkedModelSerializer):
|
||||
class CustomSlideSerializer(serializers.HyperlinkedModelSerializer):
|
||||
"""
|
||||
Serializer for core.models.CustomSlide objects.
|
||||
"""
|
||||
class Meta:
|
||||
model = CustomSlide
|
||||
fields = ('url', 'title', 'text', 'weight',)
|
||||
|
||||
|
||||
class TagSerializer(rest_api.serializers.HyperlinkedModelSerializer):
|
||||
class TagSerializer(serializers.HyperlinkedModelSerializer):
|
||||
"""
|
||||
Serializer for core.models.Tag objects.
|
||||
"""
|
||||
class Meta:
|
||||
model = Tag
|
||||
fields = ('url', 'name',)
|
||||
|
@ -12,9 +12,9 @@ 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.rest_api import viewsets
|
||||
from openslides.utils.signals import template_manipulation
|
||||
from openslides.utils.widgets import Widget
|
||||
|
||||
@ -218,9 +218,9 @@ class CustomSlideDeleteView(CustomSlideViewMixin, utils_views.DeleteView):
|
||||
pass
|
||||
|
||||
|
||||
class CustomSlideViewSet(rest_api.viewsets.ModelViewSet):
|
||||
class CustomSlideViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
API endpoint to retrieve, create, update and delete custom slides.
|
||||
API endpoint to list, retrieve, create, update and destroy custom slides.
|
||||
"""
|
||||
model = CustomSlide
|
||||
queryset = CustomSlide.objects.all()
|
||||
@ -229,7 +229,7 @@ class CustomSlideViewSet(rest_api.viewsets.ModelViewSet):
|
||||
def check_permissions(self, request):
|
||||
"""
|
||||
Calls self.permission_denied() if the requesting user has not the
|
||||
permission to manage.
|
||||
permission to manage projector.
|
||||
"""
|
||||
if not request.user.has_perm('core.can_manage_projector'):
|
||||
self.permission_denied(request)
|
||||
@ -313,9 +313,9 @@ class TagListView(utils_views.AjaxMixin, utils_views.ListView):
|
||||
**context)
|
||||
|
||||
|
||||
class TagViewSet(rest_api.viewsets.ModelViewSet):
|
||||
class TagViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
API endpoint to retrieve, create, edit and delete tags.
|
||||
API endpoint to list, retrieve, create, update and destroy tags.
|
||||
"""
|
||||
model = Tag
|
||||
queryset = Tag.objects.all()
|
||||
@ -324,7 +324,7 @@ class TagViewSet(rest_api.viewsets.ModelViewSet):
|
||||
def check_permissions(self, request):
|
||||
"""
|
||||
Calls self.permission_denied() if the requesting user has not the
|
||||
permission to manage and it's a create, update or detroy request.
|
||||
permission to manage tags and it is a create, update or detroy request.
|
||||
"""
|
||||
if (self.action in ('create', 'update', 'destroy')
|
||||
and not request.user.has_perm('core.can_manage_tags')):
|
||||
|
@ -12,9 +12,11 @@ class MediafileAppConfig(AppConfig):
|
||||
|
||||
# Import all required stuff.
|
||||
from openslides.projector.api import register_slide
|
||||
from openslides.utils.rest_api import router
|
||||
from openslides.utils.signals import template_manipulation
|
||||
from .slides import mediafile_presentation_as_slide
|
||||
from .template import add_mediafile_stylesheets
|
||||
from .views import MediafileViewSet
|
||||
|
||||
# Connect template signal.
|
||||
template_manipulation.connect(add_mediafile_stylesheets, dispatch_uid='add_mediafile_stylesheets')
|
||||
@ -22,3 +24,6 @@ class MediafileAppConfig(AppConfig):
|
||||
# Register slides.
|
||||
Mediafile = self.get_model('Mediafile')
|
||||
register_slide('mediafile', mediafile_presentation_as_slide, Mediafile)
|
||||
|
||||
# Register viewsets.
|
||||
router.register('mediafile/mediafile', MediafileViewSet)
|
||||
|
@ -7,10 +7,11 @@ 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
|
||||
from openslides.users.models import User
|
||||
|
||||
|
||||
class Mediafile(SlideMixin, AbsoluteUrlMixin, models.Model):
|
||||
class Mediafile(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, models.Model):
|
||||
"""
|
||||
Class for uploaded files which can be delivered under a certain url.
|
||||
"""
|
||||
|
16
openslides/mediafile/serializers.py
Normal file
16
openslides/mediafile/serializers.py
Normal file
@ -0,0 +1,16 @@
|
||||
from openslides.utils.rest_api import serializers
|
||||
|
||||
from .models import Mediafile
|
||||
|
||||
|
||||
class MediafileSerializer(serializers.HyperlinkedModelSerializer):
|
||||
"""
|
||||
Serializer for mediafile.models.Mediafile objects.
|
||||
"""
|
||||
filesize = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Mediafile
|
||||
|
||||
def get_filesize(self, mediafile):
|
||||
return mediafile.get_filesize()
|
@ -2,11 +2,13 @@ from django.http import HttpResponse
|
||||
|
||||
from openslides.config.api import config
|
||||
from openslides.projector.api import get_active_slide
|
||||
from openslides.utils.rest_api import viewsets
|
||||
from openslides.utils.views import (AjaxView, CreateView, DeleteView, RedirectView, ListView,
|
||||
UpdateView)
|
||||
|
||||
from .forms import MediafileManagerForm, MediafileNormalUserForm
|
||||
from .models import Mediafile
|
||||
from .serializers import MediafileSerializer
|
||||
|
||||
|
||||
class MediafileListView(ListView):
|
||||
@ -198,3 +200,26 @@ class PdfToggleFullscreenView(RedirectView):
|
||||
def get_ajax_context(self, *args, **kwargs):
|
||||
config['pdf_fullscreen'] = not config['pdf_fullscreen']
|
||||
return {'fullscreen': config['pdf_fullscreen']}
|
||||
|
||||
|
||||
class MediafileViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
API endpoint to list, retrieve, create, update and destroy mediafile
|
||||
objects.
|
||||
"""
|
||||
model = Mediafile
|
||||
queryset = Mediafile.objects.all()
|
||||
serializer_class = MediafileSerializer
|
||||
|
||||
def check_permissions(self, request):
|
||||
"""
|
||||
Calls self.permission_denied() if the requesting user has not the
|
||||
permission to see mediafile objects and in case of create, update or
|
||||
destroy requests the permission to manage mediafile objects.
|
||||
"""
|
||||
# TODO: Use mediafile.can_upload permission to create and update some
|
||||
# objects but restricted concerning the uploader.
|
||||
if (not request.user.has_perm('mediafile.can_see') or
|
||||
(self.action in ('create', 'update', 'destroy') and not
|
||||
request.user.has_perm('mediafile.can_manage'))):
|
||||
self.permission_denied(request)
|
||||
|
@ -13,8 +13,10 @@ class MotionAppConfig(AppConfig):
|
||||
|
||||
# Import all required stuff.
|
||||
from openslides.config.signals import config_signal
|
||||
from openslides.utils.rest_api import router
|
||||
from openslides.projector.api import register_slide_model
|
||||
from .signals import create_builtin_workflows, setup_motion_config
|
||||
from .views import CategoryViewSet, MotionViewSet, WorkflowViewSet
|
||||
|
||||
# Connect signals.
|
||||
config_signal.connect(setup_motion_config, dispatch_uid='setup_motion_config')
|
||||
@ -25,3 +27,8 @@ class MotionAppConfig(AppConfig):
|
||||
MotionPoll = self.get_model('MotionPoll')
|
||||
register_slide_model(Motion, 'motion/slide.html')
|
||||
register_slide_model(MotionPoll, 'motion/motionpoll_slide.html')
|
||||
|
||||
# Register viewsets.
|
||||
router.register('motion/category', CategoryViewSet)
|
||||
router.register('motion/motion', MotionViewSet)
|
||||
router.register('motion/workflow', WorkflowViewSet)
|
||||
|
@ -12,12 +12,13 @@ from openslides.poll.models import (BaseOption, BasePoll, BaseVote, CollectDefau
|
||||
from openslides.projector.models import SlideMixin
|
||||
from jsonfield import JSONField
|
||||
from openslides.utils.models import AbsoluteUrlMixin
|
||||
from openslides.utils.rest_api import RESTModelMixin
|
||||
from openslides.users.models import User
|
||||
|
||||
from .exceptions import WorkflowError
|
||||
|
||||
|
||||
class Motion(SlideMixin, AbsoluteUrlMixin, models.Model):
|
||||
class Motion(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, models.Model):
|
||||
"""
|
||||
The Motion Class.
|
||||
|
||||
@ -57,7 +58,7 @@ class Motion(SlideMixin, AbsoluteUrlMixin, models.Model):
|
||||
"""
|
||||
Counts the number of the motion in one category.
|
||||
|
||||
Needed to find the next free motion-identifier.
|
||||
Needed to find the next free motion identifier.
|
||||
"""
|
||||
|
||||
category = models.ForeignKey('Category', null=True, blank=True)
|
||||
@ -553,7 +554,7 @@ class Motion(SlideMixin, AbsoluteUrlMixin, models.Model):
|
||||
return config['motion_amendments_enabled'] and self.parent is not None
|
||||
|
||||
|
||||
class MotionVersion(AbsoluteUrlMixin, models.Model):
|
||||
class MotionVersion(RESTModelMixin, AbsoluteUrlMixin, models.Model):
|
||||
"""
|
||||
A MotionVersion object saves some date of the motion.
|
||||
"""
|
||||
@ -611,8 +612,14 @@ class MotionVersion(AbsoluteUrlMixin, models.Model):
|
||||
"""Return True, if the version is the active version of a motion. Else: False."""
|
||||
return self.active_version.exists()
|
||||
|
||||
def get_root_rest_element(self):
|
||||
"""
|
||||
Returns the motion to this instance which is the root REST element.
|
||||
"""
|
||||
return self.motion
|
||||
|
||||
class MotionSubmitter(models.Model):
|
||||
|
||||
class MotionSubmitter(RESTModelMixin, models.Model):
|
||||
"""Save the submitter of a Motion."""
|
||||
|
||||
motion = models.ForeignKey('Motion', related_name="submitter")
|
||||
@ -625,8 +632,14 @@ class MotionSubmitter(models.Model):
|
||||
"""Return the name of the submitter as string."""
|
||||
return str(self.person)
|
||||
|
||||
def get_root_rest_element(self):
|
||||
"""
|
||||
Returns the motion to this instance which is the root REST element.
|
||||
"""
|
||||
return self.motion
|
||||
|
||||
class MotionSupporter(models.Model):
|
||||
|
||||
class MotionSupporter(RESTModelMixin, models.Model):
|
||||
"""Save the submitter of a Motion."""
|
||||
|
||||
motion = models.ForeignKey('Motion', related_name="supporter")
|
||||
@ -639,8 +652,14 @@ class MotionSupporter(models.Model):
|
||||
"""Return the name of the supporter as string."""
|
||||
return str(self.person)
|
||||
|
||||
def get_root_rest_element(self):
|
||||
"""
|
||||
Returns the motion to this instance which is the root REST element.
|
||||
"""
|
||||
return self.motion
|
||||
|
||||
class Category(AbsoluteUrlMixin, models.Model):
|
||||
|
||||
class Category(RESTModelMixin, AbsoluteUrlMixin, models.Model):
|
||||
name = models.CharField(max_length=255, verbose_name=ugettext_lazy("Category name"))
|
||||
"""Name of the category."""
|
||||
|
||||
@ -666,7 +685,7 @@ class Category(AbsoluteUrlMixin, models.Model):
|
||||
ordering = ['prefix']
|
||||
|
||||
|
||||
class MotionLog(models.Model):
|
||||
class MotionLog(RESTModelMixin, models.Model):
|
||||
"""Save a logmessage for a motion."""
|
||||
|
||||
motion = models.ForeignKey(Motion, related_name='log_messages')
|
||||
@ -674,7 +693,7 @@ class MotionLog(models.Model):
|
||||
|
||||
message_list = JSONField()
|
||||
"""
|
||||
The log message. It should be a list of strings in english.
|
||||
The log message. It should be a list of strings in English.
|
||||
"""
|
||||
|
||||
person = models.ForeignKey(User, null=True)
|
||||
@ -697,8 +716,14 @@ class MotionLog(models.Model):
|
||||
'person': self.person}
|
||||
return time_and_messages
|
||||
|
||||
def get_root_rest_element(self):
|
||||
"""
|
||||
Returns the motion to this instance which is the root REST element.
|
||||
"""
|
||||
return self.motion
|
||||
|
||||
class MotionVote(BaseVote):
|
||||
|
||||
class MotionVote(RESTModelMixin, BaseVote):
|
||||
"""Saves the votes for a MotionPoll.
|
||||
|
||||
There should allways be three MotionVote objects for each poll,
|
||||
@ -707,8 +732,14 @@ class MotionVote(BaseVote):
|
||||
option = models.ForeignKey('MotionOption')
|
||||
"""The option object, to witch the vote belongs."""
|
||||
|
||||
def get_root_rest_element(self):
|
||||
"""
|
||||
Returns the motion to this instance which is the root REST element.
|
||||
"""
|
||||
return self.option.poll.motion
|
||||
|
||||
class MotionOption(BaseOption):
|
||||
|
||||
class MotionOption(RESTModelMixin, BaseOption):
|
||||
"""Links between the MotionPollClass and the MotionVoteClass.
|
||||
|
||||
There should be one MotionOption object for each poll."""
|
||||
@ -719,8 +750,14 @@ class MotionOption(BaseOption):
|
||||
vote_class = MotionVote
|
||||
"""The VoteClass, to witch this Class links."""
|
||||
|
||||
def get_root_rest_element(self):
|
||||
"""
|
||||
Returns the motion to this instance which is the root REST element.
|
||||
"""
|
||||
return self.poll.motion
|
||||
|
||||
class MotionPoll(SlideMixin, CollectDefaultVotesMixin,
|
||||
|
||||
class MotionPoll(RESTModelMixin, SlideMixin, CollectDefaultVotesMixin,
|
||||
AbsoluteUrlMixin, BasePoll):
|
||||
"""The Class to saves the vote result for a motion poll."""
|
||||
|
||||
@ -778,8 +815,14 @@ class MotionPoll(SlideMixin, CollectDefaultVotesMixin,
|
||||
def get_slide_context(self, **context):
|
||||
return super(MotionPoll, self).get_slide_context(poll=self)
|
||||
|
||||
def get_root_rest_element(self):
|
||||
"""
|
||||
Returns the motion to this instance which is the root REST element.
|
||||
"""
|
||||
return self.motion
|
||||
|
||||
class State(models.Model):
|
||||
|
||||
class State(RESTModelMixin, models.Model):
|
||||
"""
|
||||
Defines a state for a motion.
|
||||
|
||||
@ -867,8 +910,14 @@ class State(models.Model):
|
||||
if not state.workflow == self.workflow:
|
||||
raise WorkflowError('%s can not be next state of %s because it does not belong to the same workflow.' % (state, self))
|
||||
|
||||
def get_root_rest_element(self):
|
||||
"""
|
||||
Returns the workflow to this instance which is the root REST element.
|
||||
"""
|
||||
return self.workflow
|
||||
|
||||
class Workflow(models.Model):
|
||||
|
||||
class Workflow(RESTModelMixin, models.Model):
|
||||
"""Defines a workflow for a motion."""
|
||||
|
||||
name = models.CharField(max_length=255)
|
||||
|
180
openslides/motion/serializers.py
Normal file
180
openslides/motion/serializers.py
Normal file
@ -0,0 +1,180 @@
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from openslides.utils.rest_api import serializers
|
||||
|
||||
from .models import (
|
||||
Category,
|
||||
Motion,
|
||||
MotionLog,
|
||||
MotionOption,
|
||||
MotionPoll,
|
||||
MotionSubmitter,
|
||||
MotionSupporter,
|
||||
MotionVersion,
|
||||
MotionVote,
|
||||
State,
|
||||
Workflow,)
|
||||
|
||||
|
||||
class CategorySerializer(serializers.HyperlinkedModelSerializer):
|
||||
"""
|
||||
Serializer for motion.models.Category objects.
|
||||
"""
|
||||
class Meta:
|
||||
model = Category
|
||||
fields = ('url', 'name', 'prefix',)
|
||||
|
||||
|
||||
class StateSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
Serializer for motion.models.State objects.
|
||||
"""
|
||||
class Meta:
|
||||
model = State
|
||||
fields = (
|
||||
'id',
|
||||
'name',
|
||||
'action_word',
|
||||
'icon',
|
||||
'required_permission_to_see',
|
||||
'allow_support',
|
||||
'allow_create_poll',
|
||||
'allow_submitter_edit',
|
||||
'versioning',
|
||||
'leave_old_version_active',
|
||||
'dont_set_identifier',
|
||||
'next_states',)
|
||||
|
||||
|
||||
class WorkflowSerializer(serializers.HyperlinkedModelSerializer):
|
||||
"""
|
||||
Serializer for motion.models.Workflow objects.
|
||||
"""
|
||||
state_set = StateSerializer(many=True, read_only=True)
|
||||
first_state = serializers.PrimaryKeyRelatedField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Workflow
|
||||
fields = ('url', 'name', 'state_set', 'first_state',)
|
||||
|
||||
|
||||
class MotionSubmitterSerializer(serializers.HyperlinkedModelSerializer):
|
||||
"""
|
||||
Serializer for motion.models.MotionSubmitter objects.
|
||||
"""
|
||||
class Meta:
|
||||
model = MotionSubmitter
|
||||
fields = ('person',) # TODO: Rename this to 'user', see #1348
|
||||
|
||||
|
||||
class MotionSupporterSerializer(serializers.HyperlinkedModelSerializer):
|
||||
"""
|
||||
Serializer for motion.models.MotionSupporter objects.
|
||||
"""
|
||||
class Meta:
|
||||
model = MotionSupporter
|
||||
fields = ('person',) # TODO: Rename this to 'user', see #1348
|
||||
|
||||
|
||||
class MotionLogSerializer(serializers.HyperlinkedModelSerializer):
|
||||
"""
|
||||
Serializer for motion.models.MotionLog objects.
|
||||
"""
|
||||
class Meta:
|
||||
model = MotionLog
|
||||
fields = ('message_list', 'person', 'time',)
|
||||
|
||||
|
||||
class MotionVoteSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
Serializer for motion.models.MotionVote objects.
|
||||
"""
|
||||
class Meta:
|
||||
model = MotionVote
|
||||
fields = ('value', 'weight',)
|
||||
|
||||
|
||||
class MotionOptionSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
Serializer for motion.models.MotionOption objects.
|
||||
"""
|
||||
motionvote_set = MotionVoteSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = MotionOption
|
||||
fields = ('motionvote_set',)
|
||||
|
||||
|
||||
class MotionPollSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
Serializer for motion.models.MotionPoll objects.
|
||||
"""
|
||||
motionoption_set = MotionOptionSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = MotionPoll
|
||||
fields = (
|
||||
'poll_number',
|
||||
'motionoption_set',
|
||||
'votesvalid',
|
||||
'votesinvalid',
|
||||
'votescast',)
|
||||
|
||||
|
||||
class MotionVersionSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
Serializer for motion.models.MotionVersion objects.
|
||||
"""
|
||||
class Meta:
|
||||
model = MotionVersion
|
||||
fields = (
|
||||
'id',
|
||||
'version_number',
|
||||
'creation_time',
|
||||
'title',
|
||||
'text',
|
||||
'reason',)
|
||||
|
||||
|
||||
class MotionSerializer(serializers.HyperlinkedModelSerializer):
|
||||
"""
|
||||
Serializer for motion.models.Motion objects.
|
||||
"""
|
||||
versions = MotionVersionSerializer(many=True, read_only=True)
|
||||
active_version = serializers.PrimaryKeyRelatedField(read_only=True)
|
||||
submitter = MotionSubmitterSerializer(many=True, read_only=True)
|
||||
supporter = MotionSupporterSerializer(many=True, read_only=True)
|
||||
state = StateSerializer(read_only=True)
|
||||
workflow = serializers.SerializerMethodField()
|
||||
polls = MotionPollSerializer(many=True, read_only=True)
|
||||
log_messages = MotionLogSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Motion
|
||||
fields = (
|
||||
'url',
|
||||
'identifier',
|
||||
'identifier_number',
|
||||
'parent',
|
||||
'category',
|
||||
'tags',
|
||||
'versions',
|
||||
'active_version',
|
||||
'submitter',
|
||||
'supporter',
|
||||
'state',
|
||||
'workflow',
|
||||
'attachments',
|
||||
'polls',
|
||||
'log_messages',)
|
||||
|
||||
def get_workflow(self, motion):
|
||||
"""
|
||||
Returns the hyperlink to the workflow of the motion.
|
||||
"""
|
||||
request = self.context.get('request', None)
|
||||
assert request is not None, (
|
||||
"`%s` requires the request in the serializer"
|
||||
" context. Add `context={'request': request}` when instantiating "
|
||||
"the serializer." % self.__class__.__name__)
|
||||
return reverse('workflow-detail', kwargs={'pk': motion.state.workflow.pk}, request=request)
|
@ -10,6 +10,7 @@ from django.shortcuts import get_object_or_404
|
||||
from openslides.agenda.views import CreateRelatedAgendaItemView as _CreateRelatedAgendaItemView
|
||||
from openslides.config.api import config
|
||||
from openslides.poll.views import PollFormView
|
||||
from openslides.utils.rest_api import viewsets
|
||||
from openslides.utils.utils import html_strong, htmldiff
|
||||
from openslides.utils.views import (CreateView, CSVImportView, DeleteView, DetailView,
|
||||
ListView, PDFView, QuestionView,
|
||||
@ -21,8 +22,9 @@ from .forms import (BaseMotionForm, MotionCategoryMixin,
|
||||
MotionCSVImportForm, MotionSubmitterMixin,
|
||||
MotionSupporterMixin, MotionWorkflowMixin)
|
||||
from .models import (Category, Motion, MotionPoll, MotionSubmitter,
|
||||
MotionSupporter, MotionVersion, State)
|
||||
MotionSupporter, MotionVersion, State, Workflow)
|
||||
from .pdf import motion_poll_to_pdf, motion_to_pdf, motions_to_pdf
|
||||
from .serializers import CategorySerializer, MotionSerializer, WorkflowSerializer
|
||||
|
||||
|
||||
class MotionListView(ListView):
|
||||
@ -537,6 +539,29 @@ class SupportView(SingleObjectMixin, QuestionView):
|
||||
return _("You have unsupported this motion successfully.")
|
||||
|
||||
|
||||
class MotionViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
API endpoint to list, retrieve, create, update and destroy motions.
|
||||
"""
|
||||
model = Motion
|
||||
queryset = Motion.objects.all()
|
||||
serializer_class = MotionSerializer
|
||||
|
||||
def check_permissions(self, request):
|
||||
"""
|
||||
Calls self.permission_denied() if the requesting user has not the
|
||||
permission to see motions and in case of create, update or
|
||||
destroy requests the permission to manage motions.
|
||||
"""
|
||||
# TODO: Use motion.can_create_motion permission and
|
||||
# motion.can_support_motion permission to create and update some
|
||||
# objects but restricted concerning the requesting user.
|
||||
if (not request.user.has_perm('motion.can_see_motion') or
|
||||
(self.action in ('create', 'update', 'destroy') and not
|
||||
request.user.has_perm('motion.can_manage_motion'))):
|
||||
self.permission_denied(request)
|
||||
|
||||
|
||||
class PollCreateView(SingleObjectMixin, RedirectView):
|
||||
"""
|
||||
View to create a poll for a motion.
|
||||
@ -815,6 +840,26 @@ class CategoryDeleteView(DeleteView):
|
||||
success_url_name = 'motion_category_list'
|
||||
|
||||
|
||||
class CategoryViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
API endpoint to list, retrieve, create, update and destroy categories.
|
||||
"""
|
||||
model = Category
|
||||
queryset = Category.objects.all()
|
||||
serializer_class = CategorySerializer
|
||||
|
||||
def check_permissions(self, request):
|
||||
"""
|
||||
Calls self.permission_denied() if the requesting user has not the
|
||||
permission to see motions and in case of create, update or destroy
|
||||
requests the permission to manage motions.
|
||||
"""
|
||||
if (not request.user.has_perm('motion.can_see_motion') or
|
||||
(self.action in ('create', 'update', 'destroy') and not
|
||||
request.user.has_perm('motion.can_manage_motion'))):
|
||||
self.permission_denied(request)
|
||||
|
||||
|
||||
class MotionCSVImportView(CSVImportView):
|
||||
"""
|
||||
Imports motions from an uploaded csv file.
|
||||
@ -839,3 +884,23 @@ class MotionCSVImportView(CSVImportView):
|
||||
messages.error(self.request, error)
|
||||
# Overleap method of CSVImportView
|
||||
return super(CSVImportView, self).form_valid(form)
|
||||
|
||||
|
||||
class WorkflowViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
API endpoint to list, retrieve, create, update and destroy workflows.
|
||||
"""
|
||||
model = Workflow
|
||||
queryset = Workflow.objects.all()
|
||||
serializer_class = WorkflowSerializer
|
||||
|
||||
def check_permissions(self, request):
|
||||
"""
|
||||
Calls self.permission_denied() if the requesting user has not the
|
||||
permission to see motions and in case of create, update or destroy
|
||||
requests the permission to manage motions.
|
||||
"""
|
||||
if (not request.user.has_perm('motion.can_see_motion') or
|
||||
(self.action in ('create', 'update', 'destroy') and not
|
||||
request.user.has_perm('motion.can_manage_motion'))):
|
||||
self.permission_denied(request)
|
||||
|
@ -1,9 +1,9 @@
|
||||
from openslides.utils import rest_api
|
||||
from openslides.utils.rest_api import serializers
|
||||
|
||||
from .models import User
|
||||
|
||||
|
||||
class UserShortSerializer(rest_api.serializers.ModelSerializer):
|
||||
class UserShortSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
Serializer for users.models.User objects.
|
||||
|
||||
@ -12,6 +12,7 @@ class UserShortSerializer(rest_api.serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = (
|
||||
'url',
|
||||
'username',
|
||||
'title',
|
||||
'first_name',
|
||||
@ -19,7 +20,7 @@ class UserShortSerializer(rest_api.serializers.ModelSerializer):
|
||||
'structure_level')
|
||||
|
||||
|
||||
class UserFullSerializer(rest_api.serializers.ModelSerializer):
|
||||
class UserFullSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
Serializer for users.models.User objects.
|
||||
|
||||
@ -28,6 +29,7 @@ class UserFullSerializer(rest_api.serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = (
|
||||
'url',
|
||||
'is_present',
|
||||
'username',
|
||||
'title',
|
||||
|
@ -6,7 +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.rest_api import viewsets
|
||||
from openslides.utils.utils import delete_default_permissions, html_strong
|
||||
from openslides.utils.views import (
|
||||
CreateView, CSVImportView, DeleteView, DetailView, FormView, ListView,
|
||||
@ -261,17 +261,18 @@ class ResetPasswordView(SingleObjectMixin, QuestionView):
|
||||
return _('The Password for %s was successfully reset.') % html_strong(self.get_object())
|
||||
|
||||
|
||||
class UserViewSet(rest_api.viewsets.ModelViewSet):
|
||||
class UserViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
API endpoint to create, view, edit and delete users.
|
||||
API endpoint to list, retrive, create, update and delete users.
|
||||
"""
|
||||
model = User
|
||||
queryset = User.objects.all()
|
||||
|
||||
def check_permissions(self, request):
|
||||
"""
|
||||
Calls self.permission_denied() if the requesting user has not all
|
||||
permissions to see users.
|
||||
Calls self.permission_denied() if the requesting user has not the
|
||||
permission to see users and in case of create, update or destroy
|
||||
requests the permission to see extra user data and to manage users.
|
||||
"""
|
||||
if (not request.user.has_perm('users.can_see_name') or
|
||||
(self.action in ('create', 'update', 'destroy') and not
|
||||
|
@ -1,5 +1,5 @@
|
||||
from django.core.urlresolvers import reverse
|
||||
from rest_framework import permissions, routers, serializers, viewsets # noqa
|
||||
from rest_framework import response, routers, serializers, viewsets # noqa
|
||||
|
||||
router = routers.DefaultRouter()
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user