aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/admin.py5
-rw-r--r--api/migrations/0018_messagedeletioncontext.py24
-rw-r--r--api/migrations/0019_deletedmessage.py34
-rw-r--r--api/migrations/0021_merge_20181125_1015.py14
-rw-r--r--api/migrations/0027_merge_20190120_0852.py14
-rw-r--r--api/models.py70
-rw-r--r--api/serializers.py23
-rw-r--r--api/tests/test_models.py42
-rw-r--r--api/urls.py19
-rw-r--r--api/viewsets.py52
10 files changed, 275 insertions, 22 deletions
diff --git a/api/admin.py b/api/admin.py
index 2c8c130b..ab7e814e 100644
--- a/api/admin.py
+++ b/api/admin.py
@@ -1,7 +1,8 @@
from django.contrib import admin
from .models import (
- DocumentationLink, Infraction,
+ DeletedMessage, DocumentationLink,
+ Infraction, MessageDeletionContext,
OffTopicChannelName, Role,
SnakeFact, SnakeIdiom,
SnakeName, SpecialSnake,
@@ -9,8 +10,10 @@ from .models import (
)
+admin.site.register(DeletedMessage)
admin.site.register(DocumentationLink)
admin.site.register(Infraction)
+admin.site.register(MessageDeletionContext)
admin.site.register(OffTopicChannelName)
admin.site.register(Role)
admin.site.register(SnakeFact)
diff --git a/api/migrations/0018_messagedeletioncontext.py b/api/migrations/0018_messagedeletioncontext.py
new file mode 100644
index 00000000..88cbab28
--- /dev/null
+++ b/api/migrations/0018_messagedeletioncontext.py
@@ -0,0 +1,24 @@
+# Generated by Django 2.1.1 on 2018-11-18 20:12
+
+import api.models
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0017_auto_20181029_1921'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='MessageDeletionContext',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('creation', models.DateTimeField(help_text='When this deletion took place.')),
+ ('actor', models.ForeignKey(help_text='The original actor causing this deletion. Could be the author of a manual clean command invocation, the bot when executing automatic actions, or nothing to indicate that the bulk deletion was not issued by us.', null=True, on_delete=django.db.models.deletion.CASCADE, to='api.User')),
+ ],
+ bases=(api.models.ModelReprMixin, models.Model),
+ ),
+ ]
diff --git a/api/migrations/0019_deletedmessage.py b/api/migrations/0019_deletedmessage.py
new file mode 100644
index 00000000..fbd94949
--- /dev/null
+++ b/api/migrations/0019_deletedmessage.py
@@ -0,0 +1,34 @@
+# Generated by Django 2.1.1 on 2018-11-18 20:26
+
+import api.models
+import api.validators
+import django.contrib.postgres.fields
+import django.contrib.postgres.fields.jsonb
+import django.core.validators
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0018_messagedeletioncontext'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='DeletedMessage',
+ fields=[
+ ('id', models.BigIntegerField(help_text='The message ID as taken from Discord.', primary_key=True, serialize=False, validators=[django.core.validators.MinValueValidator(limit_value=0, message='Message IDs cannot be negative.')])),
+ ('channel_id', models.BigIntegerField(help_text='The channel ID that this message was sent in, taken from Discord.', validators=[django.core.validators.MinValueValidator(limit_value=0, message='Channel IDs cannot be negative.')])),
+ ('content', models.CharField(help_text='The content of this message, taken from Discord.', max_length=2000)),
+ ('embeds', django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.jsonb.JSONField(validators=[api.validators.validate_tag_embed]), help_text='Embeds attached to this message.', size=None)),
+ ('author', models.ForeignKey(help_text='The author of this message.', on_delete=django.db.models.deletion.CASCADE, to='api.User')),
+ ('deletion_context', models.ForeignKey(help_text='The deletion context this message is part of.', on_delete=django.db.models.deletion.CASCADE, to='api.MessageDeletionContext')),
+ ],
+ options={
+ 'abstract': False,
+ },
+ bases=(api.models.ModelReprMixin, models.Model),
+ ),
+ ]
diff --git a/api/migrations/0021_merge_20181125_1015.py b/api/migrations/0021_merge_20181125_1015.py
new file mode 100644
index 00000000..d8eaa510
--- /dev/null
+++ b/api/migrations/0021_merge_20181125_1015.py
@@ -0,0 +1,14 @@
+# Generated by Django 2.1.1 on 2018-11-25 10:15
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0020_add_snake_field_validators'),
+ ('api', '0019_deletedmessage'),
+ ]
+
+ operations = [
+ ]
diff --git a/api/migrations/0027_merge_20190120_0852.py b/api/migrations/0027_merge_20190120_0852.py
new file mode 100644
index 00000000..6fab4fd0
--- /dev/null
+++ b/api/migrations/0027_merge_20190120_0852.py
@@ -0,0 +1,14 @@
+# Generated by Django 2.1.5 on 2019-01-20 08:52
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0026_use_proper_default_for_infraction_insertion_date'),
+ ('api', '0021_merge_20181125_1015'),
+ ]
+
+ operations = [
+ ]
diff --git a/api/models.py b/api/models.py
index 092c2e8e..b063b78c 100644
--- a/api/models.py
+++ b/api/models.py
@@ -238,6 +238,76 @@ class User(ModelReprMixin, models.Model):
return f"{self.name}#{self.discriminator}"
+class Message(ModelReprMixin, models.Model):
+ id = models.BigIntegerField(
+ primary_key=True,
+ help_text="The message ID as taken from Discord.",
+ validators=(
+ MinValueValidator(
+ limit_value=0,
+ message="Message IDs cannot be negative."
+ ),
+ )
+ )
+ author = models.ForeignKey(
+ User,
+ on_delete=models.CASCADE,
+ help_text="The author of this message."
+ )
+ channel_id = models.BigIntegerField(
+ help_text=(
+ "The channel ID that this message was "
+ "sent in, taken from Discord."
+ ),
+ validators=(
+ MinValueValidator(
+ limit_value=0,
+ message="Channel IDs cannot be negative."
+ ),
+ )
+ )
+ content = models.CharField(
+ max_length=2_000,
+ help_text="The content of this message, taken from Discord."
+ )
+ embeds = pgfields.ArrayField(
+ pgfields.JSONField(
+ validators=(validate_tag_embed,)
+ ),
+ help_text="Embeds attached to this message."
+ )
+
+ class Meta:
+ abstract = True
+
+
+class MessageDeletionContext(ModelReprMixin, models.Model):
+ actor = models.ForeignKey(
+ User,
+ on_delete=models.CASCADE,
+ help_text=(
+ "The original actor causing this deletion. Could be the author "
+ "of a manual clean command invocation, the bot when executing "
+ "automatic actions, or nothing to indicate that the bulk "
+ "deletion was not issued by us."
+ ),
+ null=True
+ )
+ creation = models.DateTimeField(
+ # Consider whether we want to add a validator here that ensures
+ # the deletion context does not take place in the future.
+ help_text="When this deletion took place."
+ )
+
+
+class DeletedMessage(Message):
+ deletion_context = models.ForeignKey(
+ MessageDeletionContext,
+ help_text="The deletion context this message is part of.",
+ on_delete=models.CASCADE
+ )
+
+
class Infraction(ModelReprMixin, models.Model):
"""An infraction for a Discord user."""
diff --git a/api/serializers.py b/api/serializers.py
index 612ce5b4..764f85e5 100644
--- a/api/serializers.py
+++ b/api/serializers.py
@@ -2,14 +2,27 @@ from rest_framework.serializers import ModelSerializer, PrimaryKeyRelatedField,
from rest_framework_bulk import BulkSerializerMixin
from .models import (
- DocumentationLink, Infraction,
- OffTopicChannelName,
- Role, SnakeFact,
- SnakeIdiom, SnakeName,
- SpecialSnake, Tag, User
+ DeletedMessage, DocumentationLink,
+ Infraction, MessageDeletionContext,
+ OffTopicChannelName, Role,
+ SnakeFact, SnakeIdiom,
+ SnakeName, SpecialSnake,
+ Tag, User
)
+class MessageDeletionContextSerializer(BulkSerializerMixin, ModelSerializer):
+ deleted_messages = PrimaryKeyRelatedField(
+ many=True,
+ queryset=DeletedMessage.objects.all()
+ )
+
+ class Meta:
+ model = MessageDeletionContext
+ fields = ('actor', 'creation', 'messages')
+ depth = 1
+
+
class DocumentationLinkSerializer(ModelSerializer):
class Meta:
model = DocumentationLink
diff --git a/api/tests/test_models.py b/api/tests/test_models.py
index 1419a7d7..1fe60c0d 100644
--- a/api/tests/test_models.py
+++ b/api/tests/test_models.py
@@ -3,9 +3,11 @@ from datetime import datetime as dt, timezone
from django.test import SimpleTestCase
from ..models import (
- DocumentationLink, Infraction,
- ModelReprMixin, OffTopicChannelName,
- Role, SnakeFact, SnakeIdiom,
+ DeletedMessage, DocumentationLink,
+ Infraction, Message,
+ MessageDeletionContext, ModelReprMixin,
+ OffTopicChannelName, Role,
+ SnakeFact, SnakeIdiom,
SnakeName, SpecialSnake,
Tag, User
)
@@ -28,6 +30,23 @@ class ReprMixinTests(SimpleTestCase):
class StringDunderMethodTests(SimpleTestCase):
def setUp(self):
self.objects = (
+ DeletedMessage(
+ id=45,
+ author=User(
+ id=444, name='bill',
+ discriminator=5, avatar_hash=None
+ ),
+ channel_id=666,
+ content="wooey",
+ deletion_context=MessageDeletionContext(
+ actor=User(
+ id=5555, name='shawn',
+ discriminator=555, avatar_hash=None
+ ),
+ creation=dt.utcnow()
+ ),
+ embeds=[]
+ ),
DocumentationLink(
'test', 'http://example.com', 'http://example.com'
),
@@ -43,6 +62,23 @@ class StringDunderMethodTests(SimpleTestCase):
id=5, name='test role',
colour=0x5, permissions=0
),
+ Message(
+ id=45,
+ author=User(
+ id=444, name='bill',
+ discriminator=5, avatar_hash=None
+ ),
+ channel_id=666,
+ content="wooey",
+ embeds=[]
+ ),
+ MessageDeletionContext(
+ actor=User(
+ id=5555, name='shawn',
+ discriminator=555, avatar_hash=None
+ ),
+ creation=dt.utcnow()
+ ),
Tag(
title='bob',
embed={'content': "the builder"}
diff --git a/api/urls.py b/api/urls.py
index 66d3fb9e..75980d75 100644
--- a/api/urls.py
+++ b/api/urls.py
@@ -3,17 +3,22 @@ from rest_framework.routers import DefaultRouter
from .views import HealthcheckView, RulesView
from .viewsets import (
- DocumentationLinkViewSet, InfractionViewSet,
- OffTopicChannelNameViewSet, RoleViewSet,
- SnakeFactViewSet, SnakeIdiomViewSet,
- SnakeNameViewSet, SpecialSnakeViewSet,
- TagViewSet, UserViewSet
+ DeletedMessageViewSet, DocumentationLinkViewSet,
+ InfractionViewSet, OffTopicChannelNameViewSet,
+ RoleViewSet, SnakeFactViewSet,
+ SnakeIdiomViewSet, SnakeNameViewSet,
+ SpecialSnakeViewSet, TagViewSet,
+ UserViewSet
)
# http://www.django-rest-framework.org/api-guide/routers/#defaultrouter
bot_router = DefaultRouter(trailing_slash=False)
bot_router.register(
+ 'deleted-messages',
+ DeletedMessageViewSet
+)
+bot_router.register(
'documentation-links',
DocumentationLinkViewSet
)
@@ -31,6 +36,10 @@ bot_router.register(
RoleViewSet
)
bot_router.register(
+ 'roles',
+ RoleViewSet
+)
+bot_router.register(
'snake-facts',
SnakeFactViewSet
)
diff --git a/api/viewsets.py b/api/viewsets.py
index ab62f8a7..260d4b8a 100644
--- a/api/viewsets.py
+++ b/api/viewsets.py
@@ -14,21 +14,57 @@ from rest_framework_bulk import BulkCreateModelMixin
from .models import (
DocumentationLink, Infraction,
- OffTopicChannelName, Role,
- SnakeFact, SnakeIdiom,
- SnakeName, SpecialSnake,
- Tag, User
+ MessageDeletionContext, OffTopicChannelName,
+ Role, SnakeFact,
+ SnakeIdiom, SnakeName,
+ SpecialSnake, Tag,
+ User
)
from .serializers import (
DocumentationLinkSerializer, ExpandedInfractionSerializer,
InfractionSerializer, OffTopicChannelNameSerializer,
- RoleSerializer, SnakeFactSerializer,
- SnakeIdiomSerializer, SnakeNameSerializer,
- SpecialSnakeSerializer, TagSerializer,
- UserSerializer
+ MessageDeletionContextSerializer, RoleSerializer,
+ SnakeFactSerializer, SnakeIdiomSerializer,
+ SnakeNameSerializer, SpecialSnakeSerializer,
+ TagSerializer, UserSerializer
)
+class DeletedMessageViewSet(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 = MessageDeletionContextSerializer
+
+
class DocumentationLinkViewSet(
CreateModelMixin, DestroyModelMixin, ListModelMixin, RetrieveModelMixin, GenericViewSet
):