aboutsummaryrefslogtreecommitdiffstats
path: root/pydis_site
diff options
context:
space:
mode:
authorGravatar Johannes Christ <[email protected]>2019-04-24 20:14:15 +0200
committerGravatar GitHub <[email protected]>2019-04-24 20:14:15 +0200
commitdd9723705d0b41363c0941b02c4ee5d3b2bafe32 (patch)
treee0bfe8cd82087ed1b9d2dff6249282d13c790a74 /pydis_site
parentMerge pull request #202 from gdude2002/django+200/wiki (diff)
parentAddress review (diff)
Merge pull request #215 from gdude2002/django+192/flake8-docstrings
[#192] Flake8-docstrings
Diffstat (limited to 'pydis_site')
-rw-r--r--pydis_site/apps/api/apps.py2
-rw-r--r--pydis_site/apps/api/models/bot/bot_setting.py2
-rw-r--r--pydis_site/apps/api/models/bot/deleted_message.py2
-rw-r--r--pydis_site/apps/api/models/bot/documentation_link.py2
-rw-r--r--pydis_site/apps/api/models/bot/infraction.py2
-rw-r--r--pydis_site/apps/api/models/bot/message.py4
-rw-r--r--pydis_site/apps/api/models/bot/message_deletion_context.py7
-rw-r--r--pydis_site/apps/api/models/bot/off_topic_channel_name.py4
-rw-r--r--pydis_site/apps/api/models/bot/reminder.py2
-rw-r--r--pydis_site/apps/api/models/bot/role.py4
-rw-r--r--pydis_site/apps/api/models/bot/snake_fact.py2
-rw-r--r--pydis_site/apps/api/models/bot/snake_idiom.py2
-rw-r--r--pydis_site/apps/api/models/bot/snake_name.py2
-rw-r--r--pydis_site/apps/api/models/bot/special_snake.py2
-rw-r--r--pydis_site/apps/api/models/bot/tag.py15
-rw-r--r--pydis_site/apps/api/models/bot/user.py2
-rw-r--r--pydis_site/apps/api/models/utils.py8
-rw-r--r--pydis_site/apps/api/serializers.py101
-rw-r--r--pydis_site/apps/api/tests/base.py6
-rw-r--r--pydis_site/apps/api/views.py6
-rw-r--r--pydis_site/apps/api/viewsets/bot/bot_setting.py4
-rw-r--r--pydis_site/apps/api/viewsets/bot/infraction.py28
-rw-r--r--pydis_site/apps/api/viewsets/bot/nomination.py9
-rw-r--r--pydis_site/apps/api/viewsets/bot/off_topic_channel_name.py25
-rw-r--r--pydis_site/apps/api/viewsets/bot/role.py5
-rw-r--r--pydis_site/apps/api/viewsets/bot/snake_name.py10
-rw-r--r--pydis_site/apps/api/viewsets/log_entry.py3
-rw-r--r--pydis_site/apps/home/apps.py2
-rw-r--r--pydis_site/apps/home/models/repository_metadata.py2
-rw-r--r--pydis_site/apps/home/templatetags/extra_filters.py12
-rw-r--r--pydis_site/apps/home/templatetags/wiki_extra.py67
31 files changed, 316 insertions, 28 deletions
diff --git a/pydis_site/apps/api/apps.py b/pydis_site/apps/api/apps.py
index d87006dd..76810b2e 100644
--- a/pydis_site/apps/api/apps.py
+++ b/pydis_site/apps/api/apps.py
@@ -2,4 +2,6 @@ from django.apps import AppConfig
class ApiConfig(AppConfig):
+ """Django AppConfig for the API app."""
+
name = 'api'
diff --git a/pydis_site/apps/api/models/bot/bot_setting.py b/pydis_site/apps/api/models/bot/bot_setting.py
index a6eeaa1f..ee9838b7 100644
--- a/pydis_site/apps/api/models/bot/bot_setting.py
+++ b/pydis_site/apps/api/models/bot/bot_setting.py
@@ -6,6 +6,8 @@ from pydis_site.apps.api.models.utils import ModelReprMixin
def validate_bot_setting_name(name):
+ """Raises a ValidationError if the given name is not a known setting."""
+
known_settings = (
'defcon',
)
diff --git a/pydis_site/apps/api/models/bot/deleted_message.py b/pydis_site/apps/api/models/bot/deleted_message.py
index 0f46cd12..eb7f4c89 100644
--- a/pydis_site/apps/api/models/bot/deleted_message.py
+++ b/pydis_site/apps/api/models/bot/deleted_message.py
@@ -5,6 +5,8 @@ from pydis_site.apps.api.models.bot.message_deletion_context import MessageDelet
class DeletedMessage(Message):
+ """A deleted message, previously sent somewhere on the Discord server."""
+
deletion_context = models.ForeignKey(
MessageDeletionContext,
help_text="The deletion context this message is part of.",
diff --git a/pydis_site/apps/api/models/bot/documentation_link.py b/pydis_site/apps/api/models/bot/documentation_link.py
index d7df22ad..30379396 100644
--- a/pydis_site/apps/api/models/bot/documentation_link.py
+++ b/pydis_site/apps/api/models/bot/documentation_link.py
@@ -22,4 +22,6 @@ class DocumentationLink(ModelReprMixin, models.Model):
)
def __str__(self):
+ """Returns the package and URL for the current documentation link, for display purposes."""
+
return f"{self.package} - {self.base_url}"
diff --git a/pydis_site/apps/api/models/bot/infraction.py b/pydis_site/apps/api/models/bot/infraction.py
index 911ca589..7669352f 100644
--- a/pydis_site/apps/api/models/bot/infraction.py
+++ b/pydis_site/apps/api/models/bot/infraction.py
@@ -59,6 +59,8 @@ class Infraction(ModelReprMixin, models.Model):
)
def __str__(self):
+ """Returns some info on the current infraction, for display purposes."""
+
s = f"#{self.id}: {self.type} on {self.user_id}"
if self.expires_at:
s += f" until {self.expires_at}"
diff --git a/pydis_site/apps/api/models/bot/message.py b/pydis_site/apps/api/models/bot/message.py
index 2578d5c6..6b566620 100644
--- a/pydis_site/apps/api/models/bot/message.py
+++ b/pydis_site/apps/api/models/bot/message.py
@@ -8,6 +8,8 @@ from pydis_site.apps.api.models.utils import ModelReprMixin
class Message(ModelReprMixin, models.Model):
+ """A message, sent somewhere on the Discord server."""
+
id = models.BigIntegerField(
primary_key=True,
help_text="The message ID as taken from Discord.",
@@ -48,4 +50,6 @@ class Message(ModelReprMixin, models.Model):
)
class Meta:
+ """Metadata provided for Django's ORM."""
+
abstract = True
diff --git a/pydis_site/apps/api/models/bot/message_deletion_context.py b/pydis_site/apps/api/models/bot/message_deletion_context.py
index 9904ef71..44a0c8ae 100644
--- a/pydis_site/apps/api/models/bot/message_deletion_context.py
+++ b/pydis_site/apps/api/models/bot/message_deletion_context.py
@@ -5,6 +5,13 @@ from pydis_site.apps.api.models.utils import ModelReprMixin
class MessageDeletionContext(ModelReprMixin, models.Model):
+ """
+ Represents the context for a deleted message.
+
+ The context includes its creation date, as well as the actor associated with the deletion.
+ This helps to keep track of message deletions on the server.
+ """
+
actor = models.ForeignKey(
User,
on_delete=models.CASCADE,
diff --git a/pydis_site/apps/api/models/bot/off_topic_channel_name.py b/pydis_site/apps/api/models/bot/off_topic_channel_name.py
index dff7eaf8..2f55a131 100644
--- a/pydis_site/apps/api/models/bot/off_topic_channel_name.py
+++ b/pydis_site/apps/api/models/bot/off_topic_channel_name.py
@@ -5,6 +5,8 @@ from pydis_site.apps.api.models.utils import ModelReprMixin
class OffTopicChannelName(ModelReprMixin, models.Model):
+ """An off-topic channel name, used during the daily channel name shuffle."""
+
name = models.CharField(
primary_key=True,
max_length=96,
@@ -13,4 +15,6 @@ class OffTopicChannelName(ModelReprMixin, models.Model):
)
def __str__(self):
+ """Returns the current off-topic name, for display purposes."""
+
return self.name
diff --git a/pydis_site/apps/api/models/bot/reminder.py b/pydis_site/apps/api/models/bot/reminder.py
index abccdf82..ae45b5de 100644
--- a/pydis_site/apps/api/models/bot/reminder.py
+++ b/pydis_site/apps/api/models/bot/reminder.py
@@ -41,4 +41,6 @@ class Reminder(ModelReprMixin, models.Model):
)
def __str__(self):
+ """Returns some info on the current reminder, for display purposes."""
+
return f"{self.content} on {self.expiration} by {self.author}"
diff --git a/pydis_site/apps/api/models/bot/role.py b/pydis_site/apps/api/models/bot/role.py
index 8106394f..ad043bd6 100644
--- a/pydis_site/apps/api/models/bot/role.py
+++ b/pydis_site/apps/api/models/bot/role.py
@@ -7,7 +7,7 @@ from pydis_site.apps.api.models.utils import ModelReprMixin
class Role(ModelReprMixin, models.Model):
"""A role on our Discord server."""
- id = models.BigIntegerField( # noqa
+ id = models.BigIntegerField(
primary_key=True,
validators=(
MinValueValidator(
@@ -45,4 +45,6 @@ class Role(ModelReprMixin, models.Model):
)
def __str__(self):
+ """Returns the name of the current role, for display purposes."""
+
return self.name
diff --git a/pydis_site/apps/api/models/bot/snake_fact.py b/pydis_site/apps/api/models/bot/snake_fact.py
index 4398620a..c960cbc4 100644
--- a/pydis_site/apps/api/models/bot/snake_fact.py
+++ b/pydis_site/apps/api/models/bot/snake_fact.py
@@ -13,4 +13,6 @@ class SnakeFact(ModelReprMixin, models.Model):
)
def __str__(self):
+ """Returns the current snake fact, for display purposes."""
+
return self.fact
diff --git a/pydis_site/apps/api/models/bot/snake_idiom.py b/pydis_site/apps/api/models/bot/snake_idiom.py
index e4db00e0..0e8f5e94 100644
--- a/pydis_site/apps/api/models/bot/snake_idiom.py
+++ b/pydis_site/apps/api/models/bot/snake_idiom.py
@@ -13,4 +13,6 @@ class SnakeIdiom(ModelReprMixin, models.Model):
)
def __str__(self):
+ """Returns the current idiom, for display purposes."""
+
return self.idiom
diff --git a/pydis_site/apps/api/models/bot/snake_name.py b/pydis_site/apps/api/models/bot/snake_name.py
index 045a7faa..b6ea6202 100644
--- a/pydis_site/apps/api/models/bot/snake_name.py
+++ b/pydis_site/apps/api/models/bot/snake_name.py
@@ -20,4 +20,6 @@ class SnakeName(ModelReprMixin, models.Model):
)
def __str__(self):
+ """Returns the regular and scientific name of the current snake, for display purposes."""
+
return f"{self.name} ({self.scientific})"
diff --git a/pydis_site/apps/api/models/bot/special_snake.py b/pydis_site/apps/api/models/bot/special_snake.py
index 1406b9e0..662ff8e3 100644
--- a/pydis_site/apps/api/models/bot/special_snake.py
+++ b/pydis_site/apps/api/models/bot/special_snake.py
@@ -23,4 +23,6 @@ class SpecialSnake(ModelReprMixin, models.Model):
)
def __str__(self):
+ """Returns the name of the current snake, for display purposes."""
+
return self.name
diff --git a/pydis_site/apps/api/models/bot/tag.py b/pydis_site/apps/api/models/bot/tag.py
index 62881ca2..99819e42 100644
--- a/pydis_site/apps/api/models/bot/tag.py
+++ b/pydis_site/apps/api/models/bot/tag.py
@@ -9,6 +9,8 @@ from pydis_site.apps.api.models.utils import ModelReprMixin
def validate_tag_embed_fields(fields):
+ """Raises a ValidationError if any of the given embed fields is invalid."""
+
field_validators = {
'name': (MaxLengthValidator(limit_value=256),),
'value': (MaxLengthValidator(limit_value=1024),)
@@ -27,6 +29,8 @@ def validate_tag_embed_fields(fields):
def validate_tag_embed_footer(footer):
+ """Raises a ValidationError if the given footer is invalid."""
+
field_validators = {
'text': (
MinLengthValidator(
@@ -51,6 +55,8 @@ def validate_tag_embed_footer(footer):
def validate_tag_embed_author(author):
+ """Raises a ValidationError if the given author is invalid."""
+
field_validators = {
'name': (
MinLengthValidator(
@@ -77,8 +83,9 @@ def validate_tag_embed_author(author):
def validate_tag_embed(embed):
"""
- Validate a JSON document containing an embed as possible to send
- on Discord. This attempts to rebuild the validation used by Discord
+ Validate a JSON document containing an embed as possible to send on Discord.
+
+ This attempts to rebuild the validation used by Discord
as well as possible by checking for various embed limits so we can
ensure that any embed we store here will also be accepted as a
valid embed by the Discord API.
@@ -90,7 +97,7 @@ def validate_tag_embed(embed):
>>> from django.contrib.postgres import fields as pgfields
>>> from django.db import models
- >>> from pydis_site.apps.api.validators import validate_tag_embed
+ >>> from pydis_site.apps.api.models.bot.tag import validate_tag_embed
>>> class MyMessage(models.Model):
... embed = pgfields.JSONField(
... validators=(
@@ -176,4 +183,6 @@ class Tag(ModelReprMixin, models.Model):
)
def __str__(self):
+ """Returns the title of this tag, for display purposes."""
+
return self.title
diff --git a/pydis_site/apps/api/models/bot/user.py b/pydis_site/apps/api/models/bot/user.py
index f5365ed1..8b995b59 100644
--- a/pydis_site/apps/api/models/bot/user.py
+++ b/pydis_site/apps/api/models/bot/user.py
@@ -49,4 +49,6 @@ class User(ModelReprMixin, models.Model):
)
def __str__(self):
+ """Returns the name and discriminator for the current user, for display purposes."""
+
return f"{self.name}#{self.discriminator}"
diff --git a/pydis_site/apps/api/models/utils.py b/pydis_site/apps/api/models/utils.py
index 731486e7..8f590392 100644
--- a/pydis_site/apps/api/models/utils.py
+++ b/pydis_site/apps/api/models/utils.py
@@ -2,13 +2,11 @@ from operator import itemgetter
class ModelReprMixin:
- """
- Adds a `__repr__` method to the model subclassing this
- mixin which will display the model's class name along
- with all parameters used to construct the object.
- """
+ """Mixin providing a `__repr__()` to display model class name and initialisation parameters."""
def __repr__(self):
+ """Returns the current model class name and initialisation parameters."""
+
attributes = ' '.join(
f'{attribute}={value!r}'
for attribute, value in sorted(
diff --git a/pydis_site/apps/api/serializers.py b/pydis_site/apps/api/serializers.py
index 8f045044..905a7f82 100644
--- a/pydis_site/apps/api/serializers.py
+++ b/pydis_site/apps/api/serializers.py
@@ -1,3 +1,5 @@
+"""Converters from Django models to data interchange formats and back."""
+
from rest_framework.serializers import ModelSerializer, PrimaryKeyRelatedField, ValidationError
from rest_framework_bulk import BulkSerializerMixin
@@ -14,23 +16,37 @@ from .models import (
class BotSettingSerializer(ModelSerializer):
+ """A class providing (de-)serialization of `BotSetting` instances."""
+
class Meta:
+ """Metadata defined for the Django REST Framework."""
+
model = BotSetting
fields = ('name', 'data')
class DeletedMessageSerializer(ModelSerializer):
+ """
+ A class providing (de-)serialization of `DeletedMessage` instances.
+
+ The serializer generally requires a valid `deletion_context` to be
+ given, which should be created beforehand. See the `DeletedMessage`
+ model for more information.
+ """
+
author = PrimaryKeyRelatedField(
queryset=User.objects.all()
)
deletion_context = PrimaryKeyRelatedField(
queryset=MessageDeletionContext.objects.all(),
- # This will be overriden in the `create` function
+ # This will be overridden in the `create` function
# of the deletion context serializer.
required=False
)
class Meta:
+ """Metadata defined for the Django REST Framework."""
+
model = DeletedMessage
fields = (
'id', 'author',
@@ -40,14 +56,26 @@ class DeletedMessageSerializer(ModelSerializer):
class MessageDeletionContextSerializer(ModelSerializer):
+ """A class providing (de-)serialization of `MessageDeletionContext` instances."""
+
deletedmessage_set = DeletedMessageSerializer(many=True)
class Meta:
+ """Metadata defined for the Django REST Framework."""
+
model = MessageDeletionContext
fields = ('actor', 'creation', 'id', 'deletedmessage_set')
depth = 1
def create(self, validated_data):
+ """
+ Return a `MessageDeletionContext` based on the given data.
+
+ In addition to the normal attributes expected by the `MessageDeletionContext` model
+ itself, this serializer also allows for passing the `deletedmessage_set` element
+ which contains messages that were deleted as part of this context.
+ """
+
messages = validated_data.pop('deletedmessage_set')
deletion_context = MessageDeletionContext.objects.create(**validated_data)
for message in messages:
@@ -60,19 +88,29 @@ class MessageDeletionContextSerializer(ModelSerializer):
class DocumentationLinkSerializer(ModelSerializer):
+ """A class providing (de-)serialization of `DocumentationLink` instances."""
+
class Meta:
+ """Metadata defined for the Django REST Framework."""
+
model = DocumentationLink
fields = ('package', 'base_url', 'inventory_url')
class InfractionSerializer(ModelSerializer):
+ """A class providing (de-)serialization of `Infraction` instances."""
+
class Meta:
+ """Metadata defined for the Django REST Framework."""
+
model = Infraction
fields = (
'id', 'inserted_at', 'expires_at', 'active', 'user', 'actor', 'type', 'reason', 'hidden'
)
def validate(self, attrs):
+ """Validate data constraints for the given data and abort if it is invalid."""
+
infr_type = attrs.get('type')
expires_at = attrs.get('expires_at')
@@ -87,7 +125,15 @@ class InfractionSerializer(ModelSerializer):
class ExpandedInfractionSerializer(InfractionSerializer):
+ """A class providing expanded (de-)serialization of `Infraction` instances.
+
+ In addition to the fields of `Infraction` objects themselves, this
+ serializer also attaches the `user` and `actor` fields when serializing.
+ """
+
def to_representation(self, instance):
+ """Return the dictionary representation of this infraction."""
+
ret = super().to_representation(instance)
user = User.objects.get(id=ret['user'])
@@ -102,7 +148,11 @@ class ExpandedInfractionSerializer(InfractionSerializer):
class LogEntrySerializer(ModelSerializer):
+ """A class providing (de-)serialization of `LogEntry` instances."""
+
class Meta:
+ """Metadata defined for the Django REST Framework."""
+
model = LogEntry
fields = (
'application', 'logger_name', 'timestamp',
@@ -111,72 +161,121 @@ class LogEntrySerializer(ModelSerializer):
class OffTopicChannelNameSerializer(ModelSerializer):
+ """A class providing (de-)serialization of `OffTopicChannelName` instances."""
+
class Meta:
+ """Metadata defined for the Django REST Framework."""
+
model = OffTopicChannelName
fields = ('name',)
def to_representation(self, obj):
+ """
+ Return the representation of this `OffTopicChannelName`.
+
+ This only returns the name of the off topic channel name. As the model
+ only has a single attribute, it is unnecessary to create a nested dictionary.
+ Additionally, this allows off topic channel name routes to simply return an
+ array of names instead of objects, saving on bandwidth.
+ """
+
return obj.name
class SnakeFactSerializer(ModelSerializer):
+ """A class providing (de-)serialization of `SnakeFact` instances."""
+
class Meta:
+ """Metadata defined for the Django REST Framework."""
+
model = SnakeFact
fields = ('fact',)
class SnakeIdiomSerializer(ModelSerializer):
+ """A class providing (de-)serialization of `SnakeIdiom` instances."""
+
class Meta:
+ """Metadata defined for the Django REST Framework."""
+
model = SnakeIdiom
fields = ('idiom',)
class SnakeNameSerializer(ModelSerializer):
+ """A class providing (de-)serialization of `SnakeName` instances."""
+
class Meta:
+ """Metadata defined for the Django REST Framework."""
+
model = SnakeName
fields = ('name', 'scientific')
class SpecialSnakeSerializer(ModelSerializer):
+ """A class providing (de-)serialization of `SpecialSnake` instances."""
+
class Meta:
+ """Metadata defined for the Django REST Framework."""
+
model = SpecialSnake
fields = ('name', 'images', 'info')
class ReminderSerializer(ModelSerializer):
+ """A class providing (de-)serialization of `Reminder` instances."""
+
author = PrimaryKeyRelatedField(queryset=User.objects.all())
class Meta:
+ """Metadata defined for the Django REST Framework."""
+
model = Reminder
fields = ('active', 'author', 'channel_id', 'content', 'expiration', 'id')
class RoleSerializer(ModelSerializer):
+ """A class providing (de-)serialization of `Role` instances."""
+
class Meta:
+ """Metadata defined for the Django REST Framework."""
+
model = Role
fields = ('id', 'name', 'colour', 'permissions')
class TagSerializer(ModelSerializer):
+ """A class providing (de-)serialization of `Tag` instances."""
+
class Meta:
+ """Metadata defined for the Django REST Framework."""
+
model = Tag
fields = ('title', 'embed')
class UserSerializer(BulkSerializerMixin, ModelSerializer):
+ """A class providing (de-)serialization of `User` instances."""
+
roles = PrimaryKeyRelatedField(many=True, queryset=Role.objects.all(), required=False)
class Meta:
+ """Metadata defined for the Django REST Framework."""
+
model = User
fields = ('id', 'avatar_hash', 'name', 'discriminator', 'roles', 'in_guild')
depth = 1
class NominationSerializer(ModelSerializer):
+ """A class providing (de-)serialization of `Nomination` instances."""
+
author = PrimaryKeyRelatedField(queryset=User.objects.all())
user = PrimaryKeyRelatedField(queryset=User.objects.all())
class Meta:
+ """Metadata defined for the Django REST Framework."""
+
model = Nomination
fields = ('active', 'author', 'reason', 'user', 'inserted_at')
depth = 1
diff --git a/pydis_site/apps/api/tests/base.py b/pydis_site/apps/api/tests/base.py
index 8f8ace56..37459f70 100644
--- a/pydis_site/apps/api/tests/base.py
+++ b/pydis_site/apps/api/tests/base.py
@@ -13,8 +13,10 @@ test_user, _created = User.objects.get_or_create(
class APISubdomainTestCase(APITestCase):
"""
- Configures the test client to use the proper subdomain
- for requests and forces authentication for the test user.
+ Configures the test client.
+
+ This ensures that it uses the proper subdomain for requests, and forces authentication
+ for the test user.
The test user is considered staff and superuser.
If you want to test for a custom user (for example, to test model permissions),
diff --git a/pydis_site/apps/api/views.py b/pydis_site/apps/api/views.py
index c529da0f..c7dbf5c8 100644
--- a/pydis_site/apps/api/views.py
+++ b/pydis_site/apps/api/views.py
@@ -58,8 +58,10 @@ class RulesView(APIView):
@staticmethod
def _format_link(description, link, target):
"""
- Build the markup necessary to render `link` with `description`
- as its description in the given `target` language.
+ Build the markup for rendering the link.
+
+ This will render `link` with `description` as its description in the given
+ `target` language.
Arguments:
description (str):
diff --git a/pydis_site/apps/api/viewsets/bot/bot_setting.py b/pydis_site/apps/api/viewsets/bot/bot_setting.py
index 5464018a..07f5b170 100644
--- a/pydis_site/apps/api/viewsets/bot/bot_setting.py
+++ b/pydis_site/apps/api/viewsets/bot/bot_setting.py
@@ -6,9 +6,7 @@ from pydis_site.apps.api.serializers import BotSettingSerializer
class BotSettingViewSet(RetrieveModelMixin, UpdateModelMixin, GenericViewSet):
- """
- View providing update operations on bot setting routes.
- """
+ """View providing update operations on bot setting routes."""
serializer_class = BotSettingSerializer
queryset = BotSetting.objects.all()
diff --git a/pydis_site/apps/api/viewsets/bot/infraction.py b/pydis_site/apps/api/viewsets/bot/infraction.py
index 8eacf5c1..4be153e1 100644
--- a/pydis_site/apps/api/viewsets/bot/infraction.py
+++ b/pydis_site/apps/api/viewsets/bot/infraction.py
@@ -122,7 +122,9 @@ class InfractionViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge
search_fields = ('$reason',)
frozen_fields = ('id', 'inserted_at', 'type', 'user', 'actor', 'hidden')
- def partial_update(self, request, *args, **kwargs):
+ def partial_update(self, request, *_args, **_kwargs):
+ """Method that handles the nuts and bolts of updating an Infraction."""
+
for field in request.data:
if field in self.frozen_fields:
raise ValidationError({field: ['This field cannot be updated.']})
@@ -136,20 +138,44 @@ class InfractionViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge
@action(url_path='expanded', detail=False)
def list_expanded(self, *args, **kwargs):
+ """
+ DRF method for listing Infraction entries.
+
+ Called by the Django Rest Framework in response to the corresponding HTTP request.
+ """
+
self.serializer_class = ExpandedInfractionSerializer
return self.list(*args, **kwargs)
@list_expanded.mapping.post
def create_expanded(self, *args, **kwargs):
+ """
+ DRF method for creating an Infraction.
+
+ Called by the Django Rest Framework in response to the corresponding HTTP request.
+ """
+
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):
+ """
+ DRF method for retrieving a specific Infraction.
+
+ Called by the Django Rest Framework in response to the corresponding HTTP request.
+ """
+
self.serializer_class = ExpandedInfractionSerializer
return self.retrieve(*args, **kwargs)
@retrieve_expanded.mapping.patch
def partial_update_expanded(self, *args, **kwargs):
+ """
+ DRF method for updating an Infraction.
+
+ Called by the Django Rest Framework in response to the corresponding HTTP request.
+ """
+
self.serializer_class = ExpandedInfractionSerializer
return self.partial_update(*args, **kwargs)
diff --git a/pydis_site/apps/api/viewsets/bot/nomination.py b/pydis_site/apps/api/viewsets/bot/nomination.py
index 725ae176..62f5dd48 100644
--- a/pydis_site/apps/api/viewsets/bot/nomination.py
+++ b/pydis_site/apps/api/viewsets/bot/nomination.py
@@ -7,12 +7,19 @@ from pydis_site.apps.api.serializers import NominationSerializer
class NominationViewSet(ModelViewSet):
- # TODO: doc me
+ """View providing CRUD operations on helper nominations done through the bot."""
+
serializer_class = NominationSerializer
queryset = Nomination.objects.prefetch_related('author', 'user')
frozen_fields = ('author', 'inserted_at', 'user')
def update(self, request, *args, **kwargs):
+ """
+ DRF method for updating a Nomination.
+
+ Called by the Django Rest Framework in response to the corresponding HTTP request.
+ """
+
for field in request.data:
if field in self.frozen_fields:
raise ValidationError({field: ['This field cannot be updated.']})
diff --git a/pydis_site/apps/api/viewsets/bot/off_topic_channel_name.py b/pydis_site/apps/api/viewsets/bot/off_topic_channel_name.py
index df51917d..4976c291 100644
--- a/pydis_site/apps/api/viewsets/bot/off_topic_channel_name.py
+++ b/pydis_site/apps/api/viewsets/bot/off_topic_channel_name.py
@@ -11,8 +11,7 @@ from pydis_site.apps.api.serializers import OffTopicChannelNameSerializer
class OffTopicChannelNameViewSet(DestroyModelMixin, ViewSet):
"""
- View of off-topic channel names used by the bot
- to rotate our off-topic names on a daily basis.
+ 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
@@ -56,14 +55,28 @@ class OffTopicChannelNameViewSet(DestroyModelMixin, ViewSet):
serializer_class = OffTopicChannelNameSerializer
def get_object(self):
+ """
+ Returns the OffTopicChannelName entry for this request, if it exists.
+
+ If it doesn't, a HTTP 404 is returned by way of throwing an exception.
+ """
+
queryset = self.get_queryset()
name = self.kwargs[self.lookup_field]
return get_object_or_404(queryset, name=name)
def get_queryset(self):
+ """Returns a queryset that covers the entire OffTopicChannelName table."""
+
return OffTopicChannelName.objects.all()
def create(self, request):
+ """
+ DRF method for creating a new OffTopicChannelName.
+
+ Called by the Django Rest Framework in response to the corresponding HTTP request.
+ """
+
if 'name' in request.query_params:
create_data = {'name': request.query_params['name']}
serializer = OffTopicChannelNameSerializer(data=create_data)
@@ -76,7 +89,13 @@ class OffTopicChannelNameViewSet(DestroyModelMixin, ViewSet):
'name': ["This query parameter is required."]
})
- def list(self, request): # noqa
+ def list(self, request):
+ """
+ DRF method for listing OffTopicChannelName entries.
+
+ Called by the Django Rest Framework in response to the corresponding HTTP request.
+ """
+
if 'random_items' in request.query_params:
param = request.query_params['random_items']
try:
diff --git a/pydis_site/apps/api/viewsets/bot/role.py b/pydis_site/apps/api/viewsets/bot/role.py
index 0131b374..213f0a19 100644
--- a/pydis_site/apps/api/viewsets/bot/role.py
+++ b/pydis_site/apps/api/viewsets/bot/role.py
@@ -6,8 +6,9 @@ from pydis_site.apps.api.serializers import RoleSerializer
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.
+ View providing CRUD access to the roles on our server.
+
+ This is used by the bot to keep a mirror of our server's roles on the site.
## Routes
### GET /bot/roles
diff --git a/pydis_site/apps/api/viewsets/bot/snake_name.py b/pydis_site/apps/api/viewsets/bot/snake_name.py
index 991706f5..8e63a542 100644
--- a/pydis_site/apps/api/viewsets/bot/snake_name.py
+++ b/pydis_site/apps/api/viewsets/bot/snake_name.py
@@ -41,9 +41,17 @@ class SnakeNameViewSet(ViewSet):
serializer_class = SnakeNameSerializer
def get_queryset(self):
+ """Returns a queryset that covers the entire SnakeName table."""
+
return SnakeName.objects.all()
- def list(self, request): # noqa
+ def list(self, request):
+ """
+ DRF method for listing SnakeName entries.
+
+ Called by the Django Rest Framework in response to the corresponding HTTP request.
+ """
+
if request.query_params.get('get_all'):
queryset = self.get_queryset()
serialized = self.serializer_class(queryset, many=True)
diff --git a/pydis_site/apps/api/viewsets/log_entry.py b/pydis_site/apps/api/viewsets/log_entry.py
index 4aa7dffa..9108a4fa 100644
--- a/pydis_site/apps/api/viewsets/log_entry.py
+++ b/pydis_site/apps/api/viewsets/log_entry.py
@@ -7,8 +7,7 @@ from pydis_site.apps.api.serializers import LogEntrySerializer
class LogEntryViewSet(CreateModelMixin, GenericViewSet):
"""
- View providing support for creating log entries in the site database
- for viewing via the log browser.
+ View supporting the creation of log entries in the database for viewing via the log browser.
## Routes
### POST /logs
diff --git a/pydis_site/apps/home/apps.py b/pydis_site/apps/home/apps.py
index 90dc7137..9a3d213c 100644
--- a/pydis_site/apps/home/apps.py
+++ b/pydis_site/apps/home/apps.py
@@ -2,4 +2,6 @@ from django.apps import AppConfig
class HomeConfig(AppConfig):
+ """Django AppConfig for the home app."""
+
name = 'home'
diff --git a/pydis_site/apps/home/models/repository_metadata.py b/pydis_site/apps/home/models/repository_metadata.py
index c975c904..f68d3985 100644
--- a/pydis_site/apps/home/models/repository_metadata.py
+++ b/pydis_site/apps/home/models/repository_metadata.py
@@ -30,4 +30,6 @@ class RepositoryMetadata(models.Model):
)
def __str__(self):
+ """Returns the repo name, for display purposes."""
+
return self.repo_name
diff --git a/pydis_site/apps/home/templatetags/extra_filters.py b/pydis_site/apps/home/templatetags/extra_filters.py
index edffe9ac..8af11591 100644
--- a/pydis_site/apps/home/templatetags/extra_filters.py
+++ b/pydis_site/apps/home/templatetags/extra_filters.py
@@ -5,4 +5,16 @@ register = template.Library()
@register.filter
def starts_with(value: str, arg: str):
+ """
+ Simple filter for checking if a string value starts with another string.
+
+ Usage:
+
+ ```django
+ {% if request.url | starts_with:"/wiki" %}
+ ...
+ {% endif %}
+ ```
+ """
+
return value.startswith(arg)
diff --git a/pydis_site/apps/home/templatetags/wiki_extra.py b/pydis_site/apps/home/templatetags/wiki_extra.py
index 289b0279..ab14f7be 100644
--- a/pydis_site/apps/home/templatetags/wiki_extra.py
+++ b/pydis_site/apps/home/templatetags/wiki_extra.py
@@ -27,6 +27,13 @@ register = template.Library()
def get_unbound_field(field: Union[BoundField, Field]) -> Field:
+ """
+ Unwraps a bound Django Forms field, returning the unbound field.
+
+ Bound fields often don't give you the same level of access to the field's underlying attributes,
+ so sometimes it helps to have access to the underlying field object.
+ """
+
while isinstance(field, BoundField):
field = field.field
@@ -34,11 +41,33 @@ def get_unbound_field(field: Union[BoundField, Field]) -> Field:
def render(template_path: str, context: Dict[str, Any]):
- return mark_safe(get_template(template_path).render(context)) # noqa: S703 S308
+ """
+ Renders a template at a specified path, with the provided context dictionary.
+
+ This was extracted mostly for the sake of mocking it out in the tests - but do note that
+ the resulting rendered template is wrapped with `mark_safe`, so it will not be escaped.
+ """
+
+ return mark_safe(get_template(template_path).render(context)) # noqa: S703, S308
@register.simple_tag
def render_field(field: Field, render_labels: bool = True):
+ """
+ Renders a form field using a custom template designed specifically for the wiki forms.
+
+ As the wiki uses custom form rendering logic, we were unable to make use of Crispy Forms for
+ it. This means that, in order to customize the form fields, we needed to be able to render
+ the fields manually. This function handles that logic.
+
+ Sometimes we don't want to render the label that goes with a field - the `render_labels`
+ argument defaults to True, but can be set to False if the label shouldn't be rendered.
+
+ The label rendering logic is left up to the template.
+
+ Usage: `{% render_field field_obj [render_labels=True/False] %}`
+ """
+
unbound_field = get_unbound_field(field)
if not isinstance(render_labels, bool):
@@ -53,6 +82,29 @@ def render_field(field: Field, render_labels: bool = True):
@register.simple_tag(takes_context=True)
def get_field_options(context: Context, field: BoundField):
+ """
+ Retrieves the field options for a multiple choice field, and stores it in the context.
+
+ This tag exists because we can't call functions within Django templates directly, and is
+ only made use of in the template for ModelChoice (and derived) fields - but would work fine
+ with anything that makes use of your standard `<select>` element widgets.
+
+ This stores the parsed options under `options` in the context, which will subsequently
+ be available in the template.
+
+ Usage:
+
+ ```django
+ {% get_field_options field_object %}
+
+ {% if options %}
+ {% for group_name, group_choices, group_index in options %}
+ ...
+ {% endfor %}
+ {% endif %}
+ ```
+ """
+
widget = field.field.widget
if field.value() is None:
@@ -66,6 +118,19 @@ def get_field_options(context: Context, field: BoundField):
@register.filter
def render_urlpath(value: Union[URLPath, str]):
+ """
+ Simple filter to render a URLPath (or string) into a template.
+
+ This is used where the wiki intends to render a path - mostly because if you just
+ `str(url_path)`, you'll actually get a path that starts with `(root)` instead of `/`.
+
+ We support strings here as well because the wiki is very inconsistent about when it
+ provides a string versus when it provides a URLPath, and it was too much work to figure out
+ and account for it in the templates.
+
+ Usage: `{{ url_path | render_urlpath }}`
+ """
+
if isinstance(value, str):
return value or "/"