Select Git revision
permissions.py
Forked from
hub / hub
Source project has a limited visibility.
serializers.py 14.68 KiB
import contextlib
from rest_framework import serializers
from rest_framework.relations import HyperlinkedIdentityField
from rest_framework.reverse import reverse
from django.conf import settings
from django.core.exceptions import SuspiciousOperation, ValidationError
from core.models.assemblies import Assembly
from core.models.badges import Badge, BadgeToken, BadgeTokenTimeConstraint
from core.models.conference import Conference, ConferenceMember, ConferenceTrack
from core.models.events import Event
from core.models.links import Link
from core.models.messages import DirectMessage
from core.models.metanavi import MetaNavItem
from core.models.rooms import Room
from core.models.users import PlatformUser, UserTimelineEntry
class ParameterisedHyperlinkedIdentityField(HyperlinkedIdentityField):
"""
Represents the instance, or a property on the instance, using hyperlinking.
lookup_fields is a tuple of tuples of the form:
('model_field', 'url_parameter')
taken from https://github.com/encode/django-rest-framework/issues/1024
"""
lookup_fields = (('pk', 'pk'),)
def __init__(self, *args, **kwargs):
self.lookup_fields = kwargs.pop('lookup_fields', self.lookup_fields)
super().__init__(*args, **kwargs)
def get_url(self, obj, view_name, request, format): # pylint: disable=redefined-builtin
"""
Given an object, return the URL that hyperlinks to the object.
May raise a `NoReverseMatch` if the `view_name` and `lookup_field`
attributes are not configured to correctly match the URL conf.
"""
kwargs = {}
for model_field, url_param in self.lookup_fields:
attr = obj
for field in model_field.split('.'):
attr = getattr(attr, field)
kwargs[url_param] = attr
return reverse(view_name, kwargs=kwargs, request=request, format=format)
class HubHyperlinkedIdentityField(HyperlinkedIdentityField):
"""
Represents the instance, or a property on the instance, using hyperlinking.
lookup_fields is a tuple of tuples of the form:
('model_field', 'url_parameter')
taken from https://github.com/encode/django-rest-framework/issues/1024
"""
lookup_fields = (('pk', 'pk'),)
def __init__(self, *args, **kwargs):
self.lookup_fields = kwargs.pop('lookup_fields', self.lookup_fields)
super().__init__(*args, **kwargs)
def get_url(self, obj, view_name, request, format): # pylint: disable=redefined-builtin
"""
Given an object, return the URL that hyperlinks to the object.
May raise a `NoReverseMatch` if the `view_name` and `lookup_field`
attributes are not configured to correctly match the URL conf.
"""
kwargs = {}
for model_field, url_param in self.lookup_fields:
attr = obj
for field in model_field.split('.'):
attr = getattr(attr, field)
kwargs[url_param] = attr
from core.templatetags.hub_absolute import hub_absolute # pylint: disable=import-outside-toplevel
# TODO: add request=request, format=format,
return hub_absolute(view_name, **kwargs, i18n=False)
class LinkRelatedField(serializers.RelatedField):
"""
A read only field that represents its targets using their
plain string representation.
"""
def __init__(self, **kwargs):
kwargs['read_only'] = True
super().__init__(**kwargs)
def to_representation(self, value: Link):
return value.to_dict()
class ValidatingModelSerializer(serializers.ModelSerializer):
def validate(self, data):
instance = self.Meta.model(**{field: value for field, value in data.items() if field in self.Meta.model._meta.fields})
for f, v in {field: value for field, value in data.items() if field in self.Meta.model._meta.fields}.items():
getattr(instance, f).set(v)
try:
instance.clean()
except ValidationError as err:
raise serializers.ValidationError(err.args[0])
return data
class HubModelSerializer(ValidatingModelSerializer):
def __init__(self, *args, **kwargs):
# Don't pass the 'fields' arg up to the superclass
kwargs.pop('fields', None)
self.conference = Conference.objects.get(pk=settings.SELECTED_CONFERENCE_ID)
# instantiate superclass(es)
super().__init__(*args, **kwargs)
# fetch 'staff_only_fields' configuration value
if hasattr(self.Meta, 'staff_only_fields'):
staff_only_fields = self.Meta.staff_only_fields
else:
staff_only_fields = None
# now check the user's conference membership based on the request
if 'request' not in self.context:
from django.contrib.auth.models import AnonymousUser
self.request_user = request_user = AnonymousUser()
self.conference_member = None
else:
self.request_user = request_user = self.context['request'].user
self.conference_member = None
if request_user.is_authenticated:
with contextlib.suppress(ConferenceMember.DoesNotExist):
self.conference_member = ConferenceMember.objects.select_related('conference').get(
conference=self.conference,
user=request_user,
)
# store if the request's user has staff permissions in the conference (either direct or globally)
self.is_staff = request_user.is_superuser or request_user.is_staff or (self.conference_member is not None and self.conference_member.is_staff)
# filter out staff-only fields if the user isn't staff
if staff_only_fields is not None and not self.is_staff:
# remove the extended fields
for field_name in staff_only_fields:
self.fields.pop(field_name) # a KeyError here indicates a staff-only field not mentioned in the regular fields!
class ConferenceSerializer(HubModelSerializer):
tracks = serializers.SlugRelatedField(many=True, read_only=True, slug_field='slug')
class Meta:
model = Conference
fields = [
'slug',
'name',
'is_public',
'start',
'end',
'publication_date',
'registration_start',
'registration_deadline',
'tracks',
]
staff_only_fields = [
'is_public',
'publication_date',
'registration_start',
]
class ConferenceTrackSerializer(HubModelSerializer):
class Meta:
model = ConferenceTrack
read_only_fields = ['id']
fields = ['conference', 'slug', 'name', 'color', 'is_public', 'id']
staff_only_fields = ['is_public']
class AssemblySerializer(HubModelSerializer):
parent = serializers.SlugRelatedField(slug_field='slug', queryset=Assembly.objects.none)
events_url = ParameterisedHyperlinkedIdentityField(view_name='api:assembly-events', lookup_fields=(('slug', 'assembly'),))
rooms_url = ParameterisedHyperlinkedIdentityField(view_name='api:assembly-rooms', lookup_fields=(('slug', 'assembly'),))
badges_url = ParameterisedHyperlinkedIdentityField(view_name='api:assembly-badges-list-create', lookup_fields=(('slug', 'assembly'),))
class Meta:
model = Assembly
fields = [
'slug',
'id',
'name',
'state',
'hierarchy',
'parent',
'assembly_location',
'assembly_link',
'is_official',
'events_url',
'rooms_url',
'badges_url',
]
staff_only_fields = ['state', 'hierarchy']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
cluster_qs = Assembly.objects.associated_with_user(conference=self.conference, user=self.request_user).exclude(hierarchy=Assembly.Hierarchy.REGULAR)
self.fields['parent'].queryset = cluster_qs
class BadgeSerializer(HubModelSerializer):
class Meta:
model = Badge
exclude = ['issuing_assembly', 'conference', 'issuing_token']
badge_token_url = ParameterisedHyperlinkedIdentityField(
view_name='api:badge-token-list-create',
lookup_fields=(('issuing_assembly.slug', 'assembly'), ('pk', 'pk')),
)
def create(self, validated_data):
issuing_assembly = Assembly.objects.filter(slug__iexact=self.context['assembly'])
conference = Conference.objects.filter(slug=self.context['conference'])
validated_data.update({'conference': conference, 'issuing_assembly': issuing_assembly})
return super().create(validated_data)
class BadgeTokenTimeConstraintSerializer(serializers.ModelSerializer):
class Meta:
model = BadgeTokenTimeConstraint
fields = ['date_time_range']
class BadgeTokenSerializer(HubModelSerializer):
time_constraints = BadgeTokenTimeConstraintSerializer(many=True, allow_null=True, required=False)
redeemable_count = serializers.IntegerField(initial=0, required=False)
valid = serializers.ReadOnlyField()
redeemed_count = serializers.ReadOnlyField()
class Meta:
model = BadgeToken
exclude = ['badge']
read_only_fields = ['redeemed_count']
def create(self, validated_data):
time_constraints = validated_data.pop('time_constraints', [])
badge = Badge.objects.filter(pk=self.context['badge']).first()
badge_token = BadgeToken.objects.create(**validated_data, badge=badge)
for time_constraint in time_constraints:
BadgeTokenTimeConstraint.objects.create(badge_token=badge_token, **time_constraint)
return badge_token
class BadgeTokenUpdateSerializer(BadgeTokenSerializer):
class Meta:
read_only_fields = ['redeemed_count', 'badge']
class RoomSerializer(HubModelSerializer):
assembly = serializers.SlugRelatedField(read_only=True, slug_field='slug')
links = LinkRelatedField(
many=True,
read_only=True,
)
public_url = HubHyperlinkedIdentityField(view_name='plainui:room', lookup_fields=(('slug', 'slug'),))
class Meta:
model = Room
read_only_fields = ['id']
fields = [
'id',
'name',
'slug',
'blocked',
'room_type',
'capacity',
'links',
'assembly',
'links',
'backend_link',
'public_url',
]
staff_only_fields = ['blocked', 'backend_link']
class EventSerializer(HubModelSerializer):
url = ParameterisedHyperlinkedIdentityField(view_name='api:event-detail', lookup_fields=(('pk', 'pk'),))
track = serializers.SlugRelatedField(slug_field='slug', read_only=True)
assembly = serializers.SlugRelatedField(slug_field='slug', read_only=True)
room = serializers.SlugRelatedField(slug_field='id', read_only=True)
class Meta:
model = Event
extra_kwargs = {'id': {'read_only': False}}
fields = [
'id',
'kind',
'name',
'slug',
'url',
'track',
'assembly',
'room',
'location',
'language',
'description',
'is_public',
'schedule_start',
'schedule_duration',
'schedule_end',
]
staff_only_fields = ['is_public']
def update(self, instance, validated_data):
# is somebody trying to change the event ID?
if (given_id := validated_data.get('id')) is not None and given_id != instance.id:
raise SuspiciousOperation("You cannot update an event's id.")
# prevent some fields from being updated, no matter what
for protected_field in ['id', 'conference']:
validated_data.pop(protected_field, None)
# do the normal stuff
return super().update(instance, validated_data)
class UserTimelineEntrySerializer(ValidatingModelSerializer):
class Meta:
model = UserTimelineEntry
read_only_fields = ['id']
fields = [
'id',
'timestamp',
'target',
'is_bookmark',
'comment',
]
class MetaNavItemSerializer(HubModelSerializer):
conference = serializers.SlugRelatedField(read_only=True, slug_field='slug')
class Meta:
model = MetaNavItem
read_only_fields = ['id']
fields = [
'id',
'conference',
'slug',
'title_de',
'title_en',
'enabled',
'url',
'graphic_light',
'graphic_dark',
]
class PlatformUserVisibleSerializer(serializers.ModelSerializer):
class Meta:
model = PlatformUser
fields = ['username', 'display_name']
read_only_fields = fields
class PlatformUserByUsernameFieldSerializer(serializers.SlugRelatedField):
def __init__(self, *args, **kwargs):
super().__init__(*args, slug_field='username', **kwargs)
def get_queryset(self):
return PlatformUser.objects.all()
class DirectMessageSerializerSentShort(HubModelSerializer):
class Meta:
model = DirectMessage
fields = ['id', 'sender', 'recipient', 'timestamp', 'in_reply_to', 'subject']
read_only_fields = fields
sender = PlatformUserVisibleSerializer(read_only=True)
recipient = PlatformUserVisibleSerializer(read_only=True)
class DirectMessageSerializerReceivedShort(HubModelSerializer):
class Meta:
model = DirectMessage
fields = [*DirectMessageSerializerSentShort.Meta.fields, 'has_responded']
read_only_fields = fields
sender = PlatformUserVisibleSerializer(read_only=True)
recipient = PlatformUserVisibleSerializer(read_only=True)
class DirectMessageSerializerSent(HubModelSerializer):
class Meta:
model = DirectMessage
fields = [*DirectMessageSerializerSentShort.Meta.fields, 'body']
read_only_fields = fields
sender = PlatformUserVisibleSerializer(read_only=True)
recipient = PlatformUserVisibleSerializer(read_only=True)
class DirectMessageSerializerReceived(HubModelSerializer):
class Meta:
model = DirectMessage
fields = [*DirectMessageSerializerReceivedShort.Meta.fields, 'body']
read_only_fields = [f for f in fields if f != 'was_read']
sender = PlatformUserVisibleSerializer(read_only=True)
recipient = PlatformUserVisibleSerializer(read_only=True)
class DirectMessageSendSerializer(HubModelSerializer):
class Meta:
model = DirectMessage
fields = ['id', 'recipient', 'subject', 'body']
read_only_fields = ['id']
write_only_fields = [f for f in fields if f != 'id']
recipient = PlatformUserByUsernameFieldSerializer()