Merge pull request #1409 from normanjaeckel/DjangoRESTFramework-MoreWork
Added api for assignments.
This commit is contained in:
commit
98cb6503a5
@ -29,5 +29,5 @@ class AgendaAppConfig(AppConfig):
|
|||||||
Item = self.get_model('Item')
|
Item = self.get_model('Item')
|
||||||
register_slide('agenda', agenda_slide, Item)
|
register_slide('agenda', agenda_slide, Item)
|
||||||
|
|
||||||
# Register viewset.
|
# Register viewsets.
|
||||||
router.register('agenda/item', ItemViewSet)
|
router.register('agenda/item', ItemViewSet)
|
||||||
|
@ -437,6 +437,6 @@ class Speaker(RESTModelMixin, AbsoluteUrlMixin, models.Model):
|
|||||||
|
|
||||||
def get_root_rest_element(self):
|
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
|
return self.item
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from openslides.utils import rest_api
|
from openslides.utils import rest_api
|
||||||
|
from rest_framework.reverse import reverse
|
||||||
|
|
||||||
from .models import Item, Speaker
|
from .models import Item, Speaker
|
||||||
|
|
||||||
@ -17,16 +18,33 @@ class SpeakerSerializer(rest_api.serializers.HyperlinkedModelSerializer):
|
|||||||
'weight')
|
'weight')
|
||||||
|
|
||||||
|
|
||||||
|
class RelatedItemRelatedField(rest_api.serializers.RelatedField):
|
||||||
|
"""
|
||||||
|
A custom field to use for the `content_object` generic relationship.
|
||||||
|
"""
|
||||||
|
def to_representation(self, value):
|
||||||
|
"""
|
||||||
|
Returns the url to the related object.
|
||||||
|
"""
|
||||||
|
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__)
|
||||||
|
view_name = '%s-detail' % type(value)._meta.object_name.lower()
|
||||||
|
return reverse(view_name, kwargs={'pk': value.pk}, request=request)
|
||||||
|
|
||||||
|
|
||||||
class ItemSerializer(rest_api.serializers.HyperlinkedModelSerializer):
|
class ItemSerializer(rest_api.serializers.HyperlinkedModelSerializer):
|
||||||
"""
|
"""
|
||||||
Serializer for a agenda.models.Item objects.
|
Serializer for agenda.models.Item objects.
|
||||||
"""
|
"""
|
||||||
get_title = rest_api.serializers.CharField(read_only=True)
|
get_title = rest_api.serializers.CharField(read_only=True)
|
||||||
get_title_supplement = 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)
|
item_no = rest_api.serializers.CharField(read_only=True)
|
||||||
speaker_set = SpeakerSerializer(many=True, 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')
|
tags = rest_api.serializers.HyperlinkedRelatedField(many=True, read_only=True, view_name='tag-detail')
|
||||||
# content_object = serializers.PrimaryKeyRelatedField(read_only=True)
|
content_object = RelatedItemRelatedField(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Item
|
model = Item
|
||||||
|
@ -774,7 +774,7 @@ class ItemCSVImportView(CSVImportView):
|
|||||||
|
|
||||||
class ItemViewSet(rest_api.viewsets.ModelViewSet):
|
class ItemViewSet(rest_api.viewsets.ModelViewSet):
|
||||||
"""
|
"""
|
||||||
API endpoint to view, edit and delete agenda items.
|
API endpoint to retrieve, create, edit and delete agenda items.
|
||||||
"""
|
"""
|
||||||
model = Item
|
model = Item
|
||||||
serializer_class = ItemSerializer
|
serializer_class = ItemSerializer
|
||||||
|
@ -13,9 +13,11 @@ class AssignmentAppConfig(AppConfig):
|
|||||||
# Import all required stuff.
|
# Import all required stuff.
|
||||||
from openslides.config.signals import config_signal
|
from openslides.config.signals import config_signal
|
||||||
from openslides.projector.api import register_slide_model
|
from openslides.projector.api import register_slide_model
|
||||||
|
from openslides.utils.rest_api import router
|
||||||
from openslides.utils.signals import template_manipulation
|
from openslides.utils.signals import template_manipulation
|
||||||
from .signals import setup_assignment_config
|
from .signals import setup_assignment_config
|
||||||
from .template import add_assignment_stylesheets
|
from .template import add_assignment_stylesheets
|
||||||
|
from .views import AssignmentViewSet
|
||||||
|
|
||||||
# Connect signals.
|
# Connect signals.
|
||||||
config_signal.connect(setup_assignment_config, dispatch_uid='setup_assignment_config')
|
config_signal.connect(setup_assignment_config, dispatch_uid='setup_assignment_config')
|
||||||
@ -28,3 +30,6 @@ class AssignmentAppConfig(AppConfig):
|
|||||||
AssignmentPoll = self.get_model('AssignmentPoll')
|
AssignmentPoll = self.get_model('AssignmentPoll')
|
||||||
register_slide_model(Assignment, 'assignment/slide.html')
|
register_slide_model(Assignment, 'assignment/slide.html')
|
||||||
register_slide_model(AssignmentPoll, 'assignment/assignmentpoll_slide.html')
|
register_slide_model(AssignmentPoll, 'assignment/assignmentpoll_slide.html')
|
||||||
|
|
||||||
|
# Register viewsets.
|
||||||
|
router.register('assignment/assignment', AssignmentViewSet)
|
||||||
|
@ -14,11 +14,12 @@ from openslides.poll.models import (BaseOption, BasePoll, BaseVote,
|
|||||||
from openslides.projector.models import SlideMixin
|
from openslides.projector.models import SlideMixin
|
||||||
from openslides.utils.exceptions import OpenSlidesError
|
from openslides.utils.exceptions import OpenSlidesError
|
||||||
from openslides.utils.models import AbsoluteUrlMixin
|
from openslides.utils.models import AbsoluteUrlMixin
|
||||||
|
from openslides.utils.rest_api import RESTModelMixin
|
||||||
from openslides.utils.utils import html_strong
|
from openslides.utils.utils import html_strong
|
||||||
from openslides.users.models import User
|
from openslides.users.models import User
|
||||||
|
|
||||||
|
|
||||||
class AssignmentCandidate(models.Model):
|
class AssignmentCandidate(RESTModelMixin, models.Model):
|
||||||
"""
|
"""
|
||||||
Many2Many table between an assignment and the candidates.
|
Many2Many table between an assignment and the candidates.
|
||||||
"""
|
"""
|
||||||
@ -33,8 +34,14 @@ class AssignmentCandidate(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.person)
|
return str(self.person)
|
||||||
|
|
||||||
|
def get_root_rest_element(self):
|
||||||
|
"""
|
||||||
|
Returns the assignment to this instance which is the root rest element.
|
||||||
|
"""
|
||||||
|
return self.assignment
|
||||||
|
|
||||||
class Assignment(SlideMixin, AbsoluteUrlMixin, models.Model):
|
|
||||||
|
class Assignment(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, models.Model):
|
||||||
slide_callback_name = 'assignment'
|
slide_callback_name = 'assignment'
|
||||||
|
|
||||||
STATUS = (
|
STATUS = (
|
||||||
@ -262,11 +269,17 @@ class Assignment(SlideMixin, AbsoluteUrlMixin, models.Model):
|
|||||||
return '(%s)' % _('Assignment')
|
return '(%s)' % _('Assignment')
|
||||||
|
|
||||||
|
|
||||||
class AssignmentVote(BaseVote):
|
class AssignmentVote(RESTModelMixin, BaseVote):
|
||||||
option = models.ForeignKey('AssignmentOption')
|
option = models.ForeignKey('AssignmentOption')
|
||||||
|
|
||||||
|
def get_root_rest_element(self):
|
||||||
|
"""
|
||||||
|
Returns the assignment to this instance which is the root rest element.
|
||||||
|
"""
|
||||||
|
return self.option.poll.assignment
|
||||||
|
|
||||||
class AssignmentOption(BaseOption):
|
|
||||||
|
class AssignmentOption(RESTModelMixin, BaseOption):
|
||||||
poll = models.ForeignKey('AssignmentPoll')
|
poll = models.ForeignKey('AssignmentPoll')
|
||||||
candidate = models.ForeignKey(User)
|
candidate = models.ForeignKey(User)
|
||||||
vote_class = AssignmentVote
|
vote_class = AssignmentVote
|
||||||
@ -274,8 +287,14 @@ class AssignmentOption(BaseOption):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.candidate)
|
return str(self.candidate)
|
||||||
|
|
||||||
|
def get_root_rest_element(self):
|
||||||
|
"""
|
||||||
|
Returns the assignment to this instance which is the root rest element.
|
||||||
|
"""
|
||||||
|
return self.poll.assignment
|
||||||
|
|
||||||
class AssignmentPoll(SlideMixin, CollectDefaultVotesMixin,
|
|
||||||
|
class AssignmentPoll(RESTModelMixin, SlideMixin, CollectDefaultVotesMixin,
|
||||||
PublishPollMixin, AbsoluteUrlMixin, BasePoll):
|
PublishPollMixin, AbsoluteUrlMixin, BasePoll):
|
||||||
|
|
||||||
slide_callback_name = 'assignmentpoll'
|
slide_callback_name = 'assignmentpoll'
|
||||||
@ -326,3 +345,9 @@ class AssignmentPoll(SlideMixin, CollectDefaultVotesMixin,
|
|||||||
|
|
||||||
def get_slide_context(self, **context):
|
def get_slide_context(self, **context):
|
||||||
return super(AssignmentPoll, self).get_slide_context(poll=self)
|
return super(AssignmentPoll, self).get_slide_context(poll=self)
|
||||||
|
|
||||||
|
def get_root_rest_element(self):
|
||||||
|
"""
|
||||||
|
Returns the assignment to this instance which is the root rest element.
|
||||||
|
"""
|
||||||
|
return self.assignment
|
||||||
|
143
openslides/assignment/serializers.py
Normal file
143
openslides/assignment/serializers.py
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
from openslides.utils import rest_api
|
||||||
|
|
||||||
|
from .models import (
|
||||||
|
models,
|
||||||
|
Assignment,
|
||||||
|
AssignmentCandidate,
|
||||||
|
AssignmentOption,
|
||||||
|
AssignmentPoll,
|
||||||
|
AssignmentVote)
|
||||||
|
|
||||||
|
|
||||||
|
class AssignmentCandidateSerializer(rest_api.serializers.HyperlinkedModelSerializer):
|
||||||
|
"""
|
||||||
|
Serializer for assignment.models.AssignmentCandidate objects.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = AssignmentCandidate
|
||||||
|
fields = (
|
||||||
|
'id',
|
||||||
|
'person',
|
||||||
|
'elected',
|
||||||
|
'blocked')
|
||||||
|
|
||||||
|
|
||||||
|
class AssignmentVoteSerializer(rest_api.serializers.HyperlinkedModelSerializer):
|
||||||
|
"""
|
||||||
|
Serializer for assignment.models.AssignmentVote objects.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = AssignmentVote
|
||||||
|
fields = (
|
||||||
|
'weight',
|
||||||
|
'value')
|
||||||
|
|
||||||
|
|
||||||
|
class AssignmentOptionSerializer(rest_api.serializers.HyperlinkedModelSerializer):
|
||||||
|
"""
|
||||||
|
Serializer for assignment.models.AssignmentOption objects.
|
||||||
|
"""
|
||||||
|
assignmentvote_set = AssignmentVoteSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = AssignmentOption
|
||||||
|
fields = (
|
||||||
|
'candidate',
|
||||||
|
'assignmentvote_set')
|
||||||
|
|
||||||
|
|
||||||
|
class FilterPollListSerializer(rest_api.serializers.ListSerializer):
|
||||||
|
"""
|
||||||
|
Customized serilizer to filter polls and exclude unpublished ones.
|
||||||
|
"""
|
||||||
|
def to_representation(self, data):
|
||||||
|
"""
|
||||||
|
List of object instances -> List of dicts of primitive datatypes.
|
||||||
|
|
||||||
|
This method is adapted to filter the data and exclude unpublished polls.
|
||||||
|
"""
|
||||||
|
# Dealing with nested relationships, data can be a Manager,
|
||||||
|
# so, first get a queryset from the Manager if needed
|
||||||
|
iterable = data.filter(published=True) if isinstance(data, models.Manager) else data
|
||||||
|
return [self.child.to_representation(item) for item in iterable]
|
||||||
|
|
||||||
|
|
||||||
|
class AssignmentAllPollSerializer(rest_api.serializers.HyperlinkedModelSerializer):
|
||||||
|
"""
|
||||||
|
Serializer for assignment.models.AssignmentPoll objects.
|
||||||
|
|
||||||
|
Serializes all polls.
|
||||||
|
"""
|
||||||
|
assignmentoption_set = AssignmentOptionSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = AssignmentPoll
|
||||||
|
fields = (
|
||||||
|
'id',
|
||||||
|
'yesnoabstain',
|
||||||
|
'description',
|
||||||
|
'published',
|
||||||
|
'assignmentoption_set',
|
||||||
|
'votesvalid',
|
||||||
|
'votesinvalid',
|
||||||
|
'votescast')
|
||||||
|
|
||||||
|
|
||||||
|
class AssignmentShortPollSerializer(AssignmentAllPollSerializer):
|
||||||
|
"""
|
||||||
|
Serializer for assignment.models.AssignmentPoll objects.
|
||||||
|
|
||||||
|
Serializes only short polls.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
list_serializer_class = FilterPollListSerializer
|
||||||
|
model = AssignmentPoll
|
||||||
|
fields = (
|
||||||
|
'id',
|
||||||
|
'yesnoabstain',
|
||||||
|
'description',
|
||||||
|
'published',
|
||||||
|
'assignmentoption_set',
|
||||||
|
'votesvalid',
|
||||||
|
'votesinvalid',
|
||||||
|
'votescast')
|
||||||
|
|
||||||
|
|
||||||
|
class AssignmentFullSerializer(rest_api.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 = (
|
||||||
|
'name',
|
||||||
|
'description',
|
||||||
|
'posts',
|
||||||
|
'poll_description_default',
|
||||||
|
'status',
|
||||||
|
'assignmentcandidate_set',
|
||||||
|
'poll_set',
|
||||||
|
'tags')
|
||||||
|
|
||||||
|
|
||||||
|
class AssignmentShortSerializer(AssignmentFullSerializer):
|
||||||
|
"""
|
||||||
|
Serializer for assignment.models.Assignment objects. Without unpublished poll.
|
||||||
|
"""
|
||||||
|
poll_set = AssignmentShortPollSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Assignment
|
||||||
|
fields = (
|
||||||
|
'name',
|
||||||
|
'description',
|
||||||
|
'posts',
|
||||||
|
'poll_description_default',
|
||||||
|
'status',
|
||||||
|
'assignmentcandidate_set',
|
||||||
|
'poll_set',
|
||||||
|
'tags')
|
@ -12,6 +12,7 @@ from openslides.agenda.views import CreateRelatedAgendaItemView as _CreateRelate
|
|||||||
from openslides.config.api import config
|
from openslides.config.api import config
|
||||||
from openslides.users.models import Group, User # TODO: remove this
|
from openslides.users.models import Group, User # TODO: remove this
|
||||||
from openslides.poll.views import PollFormView
|
from openslides.poll.views import PollFormView
|
||||||
|
from openslides.utils import rest_api
|
||||||
from openslides.utils.pdf import stylesheet
|
from openslides.utils.pdf import stylesheet
|
||||||
from openslides.utils.utils import html_strong
|
from openslides.utils.utils import html_strong
|
||||||
from openslides.utils.views import (CreateView, DeleteView, DetailView,
|
from openslides.utils.views import (CreateView, DeleteView, DetailView,
|
||||||
@ -21,6 +22,7 @@ from openslides.utils.views import (CreateView, DeleteView, DetailView,
|
|||||||
|
|
||||||
from .forms import AssignmentForm, AssignmentRunForm
|
from .forms import AssignmentForm, AssignmentRunForm
|
||||||
from .models import Assignment, AssignmentPoll
|
from .models import Assignment, AssignmentPoll
|
||||||
|
from .serializers import AssignmentFullSerializer, AssignmentShortSerializer
|
||||||
|
|
||||||
|
|
||||||
class AssignmentListView(ListView):
|
class AssignmentListView(ListView):
|
||||||
@ -186,6 +188,35 @@ class AssignmentRunOtherDeleteView(SingleObjectMixin, QuestionView):
|
|||||||
self.is_blocked = self.get_object().is_blocked(self.person)
|
self.is_blocked = self.get_object().is_blocked(self.person)
|
||||||
|
|
||||||
|
|
||||||
|
class AssignmentViewSet(rest_api.viewsets.ModelViewSet):
|
||||||
|
"""
|
||||||
|
API endpoint to retrieve, create, edit and delete assignments.
|
||||||
|
"""
|
||||||
|
model = Assignment
|
||||||
|
queryset = Assignment.objects.all()
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
if (not request.user.has_perm('assignment.can_see_assignment') or
|
||||||
|
(self.action in ('create', 'update', 'destroy') and not
|
||||||
|
request.user.has_perm('assignment.can_manage_assignment'))):
|
||||||
|
self.permission_denied(request)
|
||||||
|
|
||||||
|
def get_serializer_class(self):
|
||||||
|
"""
|
||||||
|
Returns different serializer classes with respect to users permissions.
|
||||||
|
"""
|
||||||
|
if self.request.user.has_perm('assignment.can_manage_assignment'):
|
||||||
|
serializer_class = AssignmentFullSerializer
|
||||||
|
else:
|
||||||
|
serializer_class = AssignmentShortSerializer
|
||||||
|
return serializer_class
|
||||||
|
|
||||||
|
|
||||||
class PollCreateView(SingleObjectMixin, RedirectView):
|
class PollCreateView(SingleObjectMixin, RedirectView):
|
||||||
model = Assignment
|
model = Assignment
|
||||||
required_permission = 'assignment.can_manage_assignment'
|
required_permission = 'assignment.can_manage_assignment'
|
||||||
|
@ -26,7 +26,7 @@ class CoreAppConfig(AppConfig):
|
|||||||
CustomSlide = self.get_model('CustomSlide')
|
CustomSlide = self.get_model('CustomSlide')
|
||||||
register_slide_model(CustomSlide, 'core/customslide_slide.html')
|
register_slide_model(CustomSlide, 'core/customslide_slide.html')
|
||||||
|
|
||||||
# Register viewset.
|
# Register viewsets.
|
||||||
router.register('core/customslide', CustomSlideViewSet)
|
router.register('core/customslide', CustomSlideViewSet)
|
||||||
router.register('core/tag', TagViewSet)
|
router.register('core/tag', TagViewSet)
|
||||||
|
|
||||||
|
@ -219,7 +219,7 @@ class CustomSlideDeleteView(CustomSlideViewMixin, utils_views.DeleteView):
|
|||||||
|
|
||||||
class CustomSlideViewSet(rest_api.viewsets.ModelViewSet):
|
class CustomSlideViewSet(rest_api.viewsets.ModelViewSet):
|
||||||
"""
|
"""
|
||||||
API endpoint to view, edit and delete custom slides.
|
API endpoint to retrieve, create, update and delete custom slides.
|
||||||
"""
|
"""
|
||||||
model = CustomSlide
|
model = CustomSlide
|
||||||
queryset = CustomSlide.objects.all()
|
queryset = CustomSlide.objects.all()
|
||||||
@ -314,7 +314,7 @@ class TagListView(utils_views.AjaxMixin, utils_views.ListView):
|
|||||||
|
|
||||||
class TagViewSet(rest_api.viewsets.ModelViewSet):
|
class TagViewSet(rest_api.viewsets.ModelViewSet):
|
||||||
"""
|
"""
|
||||||
API endpoint to view, edit and delete tags.
|
API endpoint to retrieve, create, edit and delete tags.
|
||||||
"""
|
"""
|
||||||
model = Tag
|
model = Tag
|
||||||
queryset = Tag.objects.all()
|
queryset = Tag.objects.all()
|
||||||
|
@ -30,5 +30,5 @@ class UsersAppConfig(AppConfig):
|
|||||||
# Register slides.
|
# Register slides.
|
||||||
register_slide_model(User, 'participant/user_slide.html')
|
register_slide_model(User, 'participant/user_slide.html')
|
||||||
|
|
||||||
# Register viewset.
|
# Register viewsets.
|
||||||
router.register('users/user', UserViewSet)
|
router.register('users/user', UserViewSet)
|
||||||
|
@ -5,7 +5,9 @@ from .models import User
|
|||||||
|
|
||||||
class UserShortSerializer(rest_api.serializers.ModelSerializer):
|
class UserShortSerializer(rest_api.serializers.ModelSerializer):
|
||||||
"""
|
"""
|
||||||
Serializer for a users.models.User objects.
|
Serializer for users.models.User objects.
|
||||||
|
|
||||||
|
Serializes only name fields.
|
||||||
"""
|
"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
@ -19,7 +21,9 @@ class UserShortSerializer(rest_api.serializers.ModelSerializer):
|
|||||||
|
|
||||||
class UserFullSerializer(rest_api.serializers.ModelSerializer):
|
class UserFullSerializer(rest_api.serializers.ModelSerializer):
|
||||||
"""
|
"""
|
||||||
Serializer for a users.models.User objects.
|
Serializer for users.models.User objects.
|
||||||
|
|
||||||
|
Serializes all relevant fields.
|
||||||
"""
|
"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
|
Loading…
Reference in New Issue
Block a user