Merge pull request #1409 from normanjaeckel/DjangoRESTFramework-MoreWork

Added api for assignments.
This commit is contained in:
Oskar Hahn 2015-01-18 00:22:12 +01:00
commit 98cb6503a5
12 changed files with 242 additions and 16 deletions

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View 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')

View File

@ -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'

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -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