aboutsummaryrefslogtreecommitdiffstats
path: root/api/viewsets.py
diff options
context:
space:
mode:
authorGravatar Gareth Coles <[email protected]>2019-04-05 12:11:31 +0100
committerGravatar Gareth Coles <[email protected]>2019-04-05 12:11:31 +0100
commitebda74029355ce3236ca9266acde40fd40329df7 (patch)
treeaba491f902c874328c88b8e7ccb9772b5414c17f /api/viewsets.py
parentSet `harakiri = 30`. (diff)
[#176] Redo project layout
Diffstat (limited to 'api/viewsets.py')
-rw-r--r--api/viewsets.py890
1 files changed, 0 insertions, 890 deletions
diff --git a/api/viewsets.py b/api/viewsets.py
deleted file mode 100644
index 17024fe8..00000000
--- a/api/viewsets.py
+++ /dev/null
@@ -1,890 +0,0 @@
-from django.shortcuts import get_object_or_404
-from django_filters.rest_framework import DjangoFilterBackend
-from rest_framework.decorators import action
-from rest_framework.exceptions import ParseError, ValidationError
-from rest_framework.filters import SearchFilter
-from rest_framework.mixins import (
- CreateModelMixin, DestroyModelMixin,
- ListModelMixin, RetrieveModelMixin,
- UpdateModelMixin
-)
-from rest_framework.response import Response
-from rest_framework.status import HTTP_201_CREATED
-from rest_framework.viewsets import GenericViewSet, ModelViewSet, ViewSet
-from rest_framework_bulk import BulkCreateModelMixin
-
-from .models import (
- BotSetting, DocumentationLink,
- Infraction, MessageDeletionContext,
- Nomination, OffTopicChannelName,
- Reminder, Role,
- SnakeFact, SnakeIdiom,
- SnakeName, SpecialSnake,
- Tag, User
-)
-from .serializers import (
- BotSettingSerializer, DocumentationLinkSerializer,
- ExpandedInfractionSerializer, InfractionSerializer,
- MessageDeletionContextSerializer, NominationSerializer,
- OffTopicChannelNameSerializer, ReminderSerializer,
- RoleSerializer, SnakeFactSerializer,
- SnakeIdiomSerializer, SnakeNameSerializer,
- SpecialSnakeSerializer, TagSerializer,
- UserSerializer
-)
-
-
-class BotSettingViewSet(RetrieveModelMixin, UpdateModelMixin, GenericViewSet):
- """
- View providing update operations on bot setting routes.
- """
-
- serializer_class = BotSettingSerializer
- queryset = BotSetting.objects.all()
-
-
-class DeletedMessageViewSet(CreateModelMixin, GenericViewSet):
- """
- View providing support for posting bulk deletion logs generated by the bot.
-
- ## Routes
- ### POST /bot/deleted-messages
- Post messages from bulk deletion logs.
-
- #### Body schema
- >>> {
- ... # The member ID of the original actor, if applicable.
- ... # If a member ID is given, it must be present on the site.
- ... 'actor': Optional[int]
- ... 'creation': datetime,
- ... 'messages': [
- ... {
- ... 'id': int,
- ... 'author': int,
- ... 'channel_id': int,
- ... 'content': str,
- ... 'embeds': [
- ... # Discord embed objects
- ... ]
- ... }
- ... ]
- ... }
-
- #### Status codes
- - 204: returned on success
- """
-
- queryset = MessageDeletionContext.objects.all()
- serializer_class = MessageDeletionContextSerializer
-
-
-class DocumentationLinkViewSet(
- CreateModelMixin, DestroyModelMixin, ListModelMixin, RetrieveModelMixin, GenericViewSet
-):
- """
- View providing management of documentation links used in the bot's `Doc` cog.
-
- ## Routes
- ### GET /bot/documentation-links
- Retrieve all currently stored entries from the database.
-
- #### Response format
- >>> [
- ... {
- ... 'package': 'flask',
- ... 'base_url': 'https://flask.pocoo.org/docs/dev',
- ... 'inventory_url': 'https://flask.pocoo.org/docs/objects.inv'
- ... },
- ... # ...
- ... ]
-
- #### Status codes
- - 200: returned on success
-
- ### GET /bot/documentation-links/<package:str>
- Look up the documentation object for the given `package`.
-
- #### Response format
- >>> {
- ... 'package': 'flask',
- ... 'base_url': 'https://flask.pocoo.org/docs/dev',
- ... 'inventory_url': 'https://flask.pocoo.org/docs/objects.inv'
- ... }
-
- #### Status codes
- - 200: returned on success
- - 404: if no entry for the given `package` exists
-
- ### POST /bot/documentation-links
- Create a new documentation link object.
-
- #### Body schema
- >>> {
- ... 'package': str,
- ... 'base_url': URL,
- ... 'inventory_url': URL
- ... }
-
- #### Status codes
- - 201: returned on success
- - 400: if the request body has invalid fields, see the response for details
-
- ### DELETE /bot/documentation-links/<package:str>
- Delete the entry for the given `package`.
-
- #### Status codes
- - 204: returned on success
- - 404: if the given `package` could not be found
- """
-
- queryset = DocumentationLink.objects.all()
- serializer_class = DocumentationLinkSerializer
- lookup_field = 'package'
-
-
-class InfractionViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, GenericViewSet):
- """
- View providing CRUD operations on infractions for Discord users.
-
- ## Routes
- ### GET /bot/infractions
- Retrieve all infractions.
- May be filtered by the query parameters.
-
- #### Query parameters
- - **active** `bool`: whether the infraction is still active
- - **actor** `int`: snowflake of the user which applied the infraction
- - **hidden** `bool`: whether the infraction is a shadow infraction
- - **search** `str`: regular expression applied to the infraction's reason
- - **type** `str`: the type of the infraction
- - **user** `int`: snowflake of the user to which the infraction was applied
-
- Invalid query parameters are ignored.
-
- #### Response format
- >>> [
- ... {
- ... 'id': 5,
- ... 'inserted_at': '2018-11-22T07:24:06.132307Z',
- ... 'expires_at': '5018-11-20T15:52:00Z',
- ... 'active': False,
- ... 'user': 172395097705414656,
- ... 'actor': 125435062127820800,
- ... 'type': 'ban',
- ... 'reason': 'He terk my jerb!',
- ... 'hidden': True
- ... }
- ... ]
-
- #### Status codes
- - 200: returned on success
-
- ### GET /bot/infractions/<id:int>
- Retrieve a single infraction by ID.
-
- #### Response format
- See `GET /bot/infractions`.
-
- #### Status codes
- - 200: returned on success
- - 404: if an infraction with the given `id` could not be found
-
- ### POST /bot/infractions
- Create a new infraction and return the created infraction.
- Only `actor`, `type`, and `user` are required.
- The `actor` and `user` must be users known by the site.
-
- #### Request body
- >>> {
- ... 'active': False,
- ... 'actor': 125435062127820800,
- ... 'expires_at': '5018-11-20T15:52:00+00:00',
- ... 'hidden': True,
- ... 'type': 'ban',
- ... 'reason': 'He terk my jerb!',
- ... 'user': 172395097705414656
- ... }
-
- #### Response format
- See `GET /bot/infractions`.
-
- #### Status codes
- - 201: returned on success
- - 400: if a given user is unknown or a field in the request body is invalid
-
- ### PATCH /bot/infractions/<id:int>
- Update the infraction with the given `id` and return the updated infraction.
- Only `active`, `reason`, and `expires_at` may be updated.
-
- #### Request body
- >>> {
- ... 'active': True,
- ... 'expires_at': '4143-02-15T21:04:31+00:00',
- ... 'reason': 'durka derr'
- ... }
-
- #### Response format
- See `GET /bot/infractions`.
-
- #### Status codes
- - 200: returned on success
- - 400: if a field in the request body is invalid or disallowed
- - 404: if an infraction with the given `id` could not be found
-
- ### Expanded routes
- All routes support expansion of `user` and `actor` in responses. To use an expanded route,
- append `/expanded` to the end of the route e.g. `GET /bot/infractions/expanded`.
-
- #### Response format
- See `GET /bot/users/<snowflake:int>` for the expanded formats of `user` and `actor`. Responses
- are otherwise identical to their non-expanded counterparts.
- """
-
- serializer_class = InfractionSerializer
- queryset = Infraction.objects.all()
- filter_backends = (DjangoFilterBackend, SearchFilter)
- filter_fields = ('user__id', 'actor__id', 'active', 'hidden', 'type')
- search_fields = ('$reason',)
- frozen_fields = ('id', 'inserted_at', 'type', 'user', 'actor', 'hidden')
-
- def partial_update(self, request, *args, **kwargs):
- for field in request.data:
- if field in self.frozen_fields:
- raise ValidationError({field: ['This field cannot be updated.']})
-
- instance = self.get_object()
- serializer = self.get_serializer(instance, data=request.data, partial=True)
- serializer.is_valid(raise_exception=True)
- serializer.save()
-
- return Response(serializer.data)
-
- @action(url_path='expanded', detail=False)
- def list_expanded(self, *args, **kwargs):
- self.serializer_class = ExpandedInfractionSerializer
- return self.list(*args, **kwargs)
-
- @list_expanded.mapping.post
- def create_expanded(self, *args, **kwargs):
- self.serializer_class = ExpandedInfractionSerializer
- return self.create(*args, **kwargs)
-
- @action(url_path='expanded', url_name='detail-expanded', detail=True)
- def retrieve_expanded(self, *args, **kwargs):
- self.serializer_class = ExpandedInfractionSerializer
- return self.retrieve(*args, **kwargs)
-
- @retrieve_expanded.mapping.patch
- def partial_update_expanded(self, *args, **kwargs):
- self.serializer_class = ExpandedInfractionSerializer
- return self.partial_update(*args, **kwargs)
-
-
-class OffTopicChannelNameViewSet(DestroyModelMixin, ViewSet):
- """
- View of off-topic channel names used by the bot
- to rotate our off-topic names on a daily basis.
-
- ## Routes
- ### GET /bot/off-topic-channel-names
- Return all known off-topic channel names from the database.
- If the `random_items` query parameter is given, for example using...
- $ curl api.pythondiscord.local:8000/bot/off-topic-channel-names?random_items=5
- ... then the API will return `5` random items from the database.
-
- #### Response format
- Return a list of off-topic-channel names:
- >>> [
- ... "lemons-lemonade-stand",
- ... "bbq-with-bisk"
- ... ]
-
- #### Status codes
- - 200: returned on success
- - 400: returned when `random_items` is not a positive integer
-
- ### POST /bot/off-topic-channel-names
- Create a new off-topic-channel name in the database.
- The name must be given as a query parameter, for example:
- $ curl api.pythondiscord.local:8000/bot/off-topic-channel-names?name=lemons-lemonade-shop
-
- #### Status codes
- - 201: returned on success
- - 400: if the request body has invalid fields, see the response for details
-
- ### DELETE /bot/off-topic-channel-names/<name:str>
- Delete the off-topic-channel name with the given `name`.
-
- #### Status codes
- - 204: returned on success
- - 404: returned when the given `name` was not found
-
- ## Authentication
- Requires a API token.
- """
-
- lookup_field = 'name'
- serializer_class = OffTopicChannelNameSerializer
-
- def get_object(self):
- queryset = self.get_queryset()
- name = self.kwargs[self.lookup_field]
- return get_object_or_404(queryset, name=name)
-
- def get_queryset(self):
- return OffTopicChannelName.objects.all()
-
- def create(self, request):
- if 'name' in request.query_params:
- create_data = {'name': request.query_params['name']}
- serializer = OffTopicChannelNameSerializer(data=create_data)
- serializer.is_valid(raise_exception=True)
- serializer.save()
- return Response(create_data, status=HTTP_201_CREATED)
-
- else:
- raise ParseError(detail={
- 'name': ["This query parameter is required."]
- })
-
- def list(self, request): # noqa
- if 'random_items' in request.query_params:
- param = request.query_params['random_items']
- try:
- random_count = int(param)
- except ValueError:
- raise ParseError(detail={'random_items': ["Must be a valid integer."]})
-
- if random_count <= 0:
- raise ParseError(detail={
- 'random_items': ["Must be a positive integer."]
- })
-
- queryset = self.get_queryset().order_by('?')[:random_count]
- serialized = self.serializer_class(queryset, many=True)
- return Response(serialized.data)
-
- queryset = self.get_queryset()
- serialized = self.serializer_class(queryset, many=True)
- return Response(serialized.data)
-
-
-class ReminderViewSet(CreateModelMixin, ListModelMixin, DestroyModelMixin, GenericViewSet):
- """
- View providing CRUD access to reminders.
-
- ## Routes
- ### GET /bot/reminders
- Returns all reminders in the database.
-
- #### Response format
- >>> [
- ... {
- ... 'active': True,
- ... 'author': 1020103901030,
- ... 'content': "Make dinner",
- ... 'expiration': '5018-11-20T15:52:00Z'
- ... 'id': 11
- ... },
- ... ...
- ... ]
-
- #### Status codes
- - 200: returned on success
-
- ### POST /bot/reminders
- Create a new reminder.
-
- #### Request body
- >>> {
- ... 'author': int,
- ... 'content': str,
- ... 'expiration': str # ISO-formatted datetime
- ... }
-
- #### Status codes
- - 201: returned on success
- - 400: if the body format is invalid
- - 404: if no user with the given ID could be found
-
- ### DELETE /bot/reminders/<id:int>
- Delete the reminder with the given `id`.
-
- #### Status codes
- - 204: returned on success
- - 404: if a reminder with the given `id` does not exist
-
- ## Authentication
- Requires an API token.
- """
-
- serializer_class = ReminderSerializer
- queryset = Reminder.objects.prefetch_related('author')
- filter_backends = (DjangoFilterBackend, SearchFilter)
- filter_fields = ('active', 'author__id')
-
-
-class RoleViewSet(ModelViewSet):
- """
- View providing CRUD access to the roles on our server, used
- by the bot to keep a mirror of our server's roles on the site.
-
- ## Routes
- ### GET /bot/roles
- Returns all roles in the database.
-
- #### Response format
- >>> [
- ... {
- ... 'id': 267628507062992896,
- ... 'name': "Admins",
- ... 'colour': 1337,
- ... 'permissions': 8
- ... }
- ... ]
-
- #### Status codes
- - 200: returned on success
-
- ### GET /bot/roles/<snowflake:int>
- Gets a single role by ID.
-
- #### Response format
- >>> {
- ... 'id': 267628507062992896,
- ... 'name': "Admins",
- ... 'colour': 1337,
- ... 'permissions': 8
- ... }
-
- #### Status codes
- - 200: returned on success
- - 404: if a role with the given `snowflake` could not be found
-
- ### POST /bot/roles
- Adds a single new role.
-
- #### Request body
- >>> {
- ... 'id': int,
- ... 'name': str,
- ... 'colour': int,
- ... 'permissions': int,
- ... }
-
- #### Status codes
- - 201: returned on success
- - 400: if the body format is invalid
-
- ### PUT /bot/roles/<snowflake:int>
- Update the role with the given `snowflake`.
- All fields in the request body are required.
-
- #### Request body
- >>> {
- ... 'id': int,
- ... 'name': str,
- ... 'colour': int,
- ... 'permissions': int
- ... }
-
- #### Status codes
- - 200: returned on success
- - 400: if the request body was invalid
-
- ### PATCH /bot/roles/<snowflake:int>
- Update the role with the given `snowflake`.
- All fields in the request body are required.
-
- >>> {
- ... 'id': int,
- ... 'name': str,
- ... 'colour': int,
- ... 'permissions': int
- ... }
-
- ### DELETE /bot/roles/<snowflake:int>
- Deletes the role with the given `snowflake`.
-
- #### Status codes
- - 204: returned on success
- - 404: if a role with the given `snowflake` does not exist
- """
-
- queryset = Role.objects.all()
- serializer_class = RoleSerializer
-
-
-class SnakeFactViewSet(ListModelMixin, GenericViewSet):
- """
- View providing snake facts created by the Pydis community in the first code jam.
-
- ## Routes
- ### GET /bot/snake-facts
- Returns snake facts from the database.
-
- #### Response format
- >>> [
- ... {'fact': 'Snakes are dangerous'},
- ... {'fact': 'Except for Python, we all love it'}
- ... ]
-
- #### Status codes
- - 200: returned on success
-
- ## Authentication
- Requires an API token.
- """
-
- serializer_class = SnakeFactSerializer
- queryset = SnakeFact.objects.all()
-
-
-class SnakeIdiomViewSet(ListModelMixin, GenericViewSet):
- """
- View providing snake idioms for the snake cog.
-
- ## Routes
- ### GET /bot/snake-idioms
- Returns snake idioms from the database.
-
- #### Response format
- >>> [
- ... {'idiom': 'Sneky snek'},
- ... {'idiom': 'Snooky Snake'}
- ... ]
-
- #### Status codes
- - 200: returned on success
-
- ## Authentication
- Requires an API token
- """
-
- serializer_class = SnakeIdiomSerializer
- queryset = SnakeIdiom.objects.all()
-
-
-class SnakeNameViewSet(ViewSet):
- """
- View providing snake names for the bot's snake cog from our first code jam's winners.
-
- ## Routes
- ### GET /bot/snake-names
- By default, return a single random snake name along with its name and scientific name.
- If the `get_all` query parameter is given, for example using...
- $ curl api.pythondiscord.local:8000/bot/snake-names?get_all=yes
- ... then the API will return all snake names and scientific names in the database.
-
- #### Response format
- Without `get_all` query parameter:
- >>> {
- ... 'name': "Python",
- ... 'scientific': "Langus greatus"
- ... }
-
- If the database is empty for whatever reason, this will return an empty dictionary.
-
- With `get_all` query parameter:
- >>> [
- ... {'name': "Python 3", 'scientific': "Langus greatus"},
- ... {'name': "Python 2", 'scientific': "Langus decentus"}
- ... ]
-
- #### Status codes
- - 200: returned on success
-
- ## Authentication
- Requires a API token.
- """
-
- serializer_class = SnakeNameSerializer
-
- def get_queryset(self):
- return SnakeName.objects.all()
-
- def list(self, request): # noqa
- if request.query_params.get('get_all'):
- queryset = self.get_queryset()
- serialized = self.serializer_class(queryset, many=True)
- return Response(serialized.data)
-
- single_snake = SnakeName.objects.order_by('?').first()
- if single_snake is not None:
- body = {
- 'name': single_snake.name,
- 'scientific': single_snake.scientific
- }
-
- return Response(body)
-
- return Response({})
-
-
-class SpecialSnakeViewSet(ListModelMixin, GenericViewSet):
- """
- View providing special snake names for our bot's snake cog.
-
- ## Routes
- ### GET /bot/special-snakes
- Returns a list of special snake names.
-
- #### Response Format
- >>> [
- ... {
- ... 'name': 'Snakky sneakatus',
- ... 'info': 'Scary snek',
- ... 'image': 'https://discordapp.com/assets/53ef346458017da2062aca5c7955946b.svg'
- ... }
- ... ]
-
- #### Status codes
- - 200: returned on success
-
- ## Authentication
- Requires an API token.
- """
-
- serializer_class = SpecialSnakeSerializer
- queryset = SpecialSnake.objects.all()
-
-
-class TagViewSet(ModelViewSet):
- """
- View providing CRUD operations on tags shown by our bot.
-
- ## Routes
- ### GET /bot/tags
- Returns all tags in the database.
-
- #### Response format
- >>> [
- ... {
- ... 'title': "resources",
- ... 'embed': {
- ... 'content': "Did you really think I'd put something useful here?"
- ... }
- ... }
- ... ]
-
- #### Status codes
- - 200: returned on success
-
- ### GET /bot/tags/<title:str>
- Gets a single tag by its title.
-
- #### Response format
- >>> {
- ... 'title': "My awesome tag",
- ... 'embed': {
- ... 'content': "totally not filler words"
- ... }
- ... }
-
- #### Status codes
- - 200: returned on success
- - 404: if a tag with the given `title` could not be found
-
- ### POST /bot/tags
- Adds a single tag to the database.
-
- #### Request body
- >>> {
- ... 'title': str,
- ... 'embed': dict
- ... }
-
- The embed structure is the same as the embed structure that the Discord API
- expects. You can view the documentation for it here:
- https://discordapp.com/developers/docs/resources/channel#embed-object
-
- #### Status codes
- - 201: returned on success
- - 400: if one of the given fields is invalid
-
- ### PUT /bot/tags/<title:str>
- Update the tag with the given `title`.
-
- #### Request body
- >>> {
- ... 'title': str,
- ... 'embed': dict
- ... }
-
- The embed structure is the same as the embed structure that the Discord API
- expects. You can view the documentation for it here:
- https://discordapp.com/developers/docs/resources/channel#embed-object
-
- #### Status codes
- - 200: returned on success
- - 400: if the request body was invalid, see response body for details
- - 404: if the tag with the given `title` could not be found
-
- ### PATCH /bot/tags/<title:str>
- Update the tag with the given `title`.
-
- #### Request body
- >>> {
- ... 'title': str,
- ... 'embed': dict
- ... }
-
- The embed structure is the same as the embed structure that the Discord API
- expects. You can view the documentation for it here:
- https://discordapp.com/developers/docs/resources/channel#embed-object
-
- #### Status codes
- - 200: returned on success
- - 400: if the request body was invalid, see response body for details
- - 404: if the tag with the given `title` could not be found
-
- ### DELETE /bot/tags/<title:str>
- Deletes the tag with the given `title`.
-
- #### Status codes
- - 204: returned on success
- - 404: if a tag with the given `title` does not exist
- """
-
- serializer_class = TagSerializer
- queryset = Tag.objects.all()
-
-
-class UserViewSet(BulkCreateModelMixin, ModelViewSet):
- """
- View providing CRUD operations on Discord users through the bot.
-
- ## Routes
- ### GET /bot/users
- Returns all users currently known.
-
- #### Response format
- >>> [
- ... {
- ... 'id': 409107086526644234,
- ... 'avatar': "3ba3c1acce584c20b1e96fc04bbe80eb",
- ... 'name': "Python",
- ... 'discriminator': 4329,
- ... 'roles': [
- ... 352427296948486144,
- ... 270988689419665409,
- ... 277546923144249364,
- ... 458226699344019457
- ... ],
- ... 'in_guild': True
- ... }
- ... ]
-
- #### Status codes
- - 200: returned on success
-
- ### GET /bot/users/<snowflake:int>
- Gets a single user by ID.
-
- #### Response format
- >>> {
- ... 'id': 409107086526644234,
- ... 'avatar': "3ba3c1acce584c20b1e96fc04bbe80eb",
- ... 'name': "Python",
- ... 'discriminator': 4329,
- ... 'roles': [
- ... 352427296948486144,
- ... 270988689419665409,
- ... 277546923144249364,
- ... 458226699344019457
- ... ],
- ... 'in_guild': True
- ... }
-
- #### Status codes
- - 200: returned on success
- - 404: if a user with the given `snowflake` could not be found
-
- ### POST /bot/users
- Adds a single or multiple new users.
- The roles attached to the user(s) must be roles known by the site.
-
- #### Request body
- >>> {
- ... 'id': int,
- ... 'avatar': str,
- ... 'name': str,
- ... 'discriminator': int,
- ... 'roles': List[int],
- ... 'in_guild': bool
- ... }
-
- Alternatively, request users can be POSTed as a list of above objects,
- in which case multiple users will be created at once.
-
- #### Status codes
- - 201: returned on success
- - 400: if one of the given roles does not exist, or one of the given fields is invalid
-
- ### PUT /bot/users/<snowflake:int>
- Update the user with the given `snowflake`.
- All fields in the request body are required.
-
- #### Request body
- >>> {
- ... 'id': int,
- ... 'avatar': str,
- ... 'name': str,
- ... 'discriminator': int,
- ... 'roles': List[int],
- ... 'in_guild': bool
- ... }
-
- #### Status codes
- - 200: returned on success
- - 400: if the request body was invalid, see response body for details
- - 404: if the user with the given `snowflake` could not be found
-
- ### PATCH /bot/users/<snowflake:int>
- Update the user with the given `snowflake`.
- All fields in the request body are optional.
-
- #### Request body
- >>> {
- ... 'id': int,
- ... 'avatar': str,
- ... 'name': str,
- ... 'discriminator': int,
- ... 'roles': List[int],
- ... 'in_guild': bool
- ... }
-
- #### Status codes
- - 200: returned on success
- - 400: if the request body was invalid, see response body for details
- - 404: if the user with the given `snowflake` could not be found
-
- ### DELETE /bot/users/<snowflake:int>
- Deletes the user with the given `snowflake`.
-
- #### Status codes
- - 204: returned on success
- - 404: if a user with the given `snowflake` does not exist
- """
-
- serializer_class = UserSerializer
- queryset = User.objects.prefetch_related('roles')
-
-
-class NominationViewSet(ModelViewSet):
- # TODO: doc me
- serializer_class = NominationSerializer
- queryset = Nomination.objects.prefetch_related('author', 'user')
- frozen_fields = ('author', 'inserted_at', 'user')
-
- def update(self, request, *args, **kwargs):
- for field in request.data:
- if field in self.frozen_fields:
- raise ValidationError({field: ['This field cannot be updated.']})
-
- instance = self.get_object()
- serializer = self.get_serializer(instance, data=request.data, partial=True)
- serializer.is_valid(raise_exception=True)
- serializer.save()
-
- return Response(serializer.data)