diff options
-rw-r--r-- | api/migrations/0030_reminder.py | 28 | ||||
-rw-r--r-- | api/models.py | 39 | ||||
-rw-r--r-- | api/serializers.py | 17 | ||||
-rw-r--r-- | api/tests/test_models.py | 18 | ||||
-rw-r--r-- | api/urls.py | 12 | ||||
-rw-r--r-- | api/viewsets.py | 72 |
6 files changed, 164 insertions, 22 deletions
diff --git a/api/migrations/0030_reminder.py b/api/migrations/0030_reminder.py new file mode 100644 index 00000000..f0c14ccb --- /dev/null +++ b/api/migrations/0030_reminder.py @@ -0,0 +1,28 @@ +# Generated by Django 2.1.5 on 2019-01-22 22:17 + +import api.models +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0029_add_infraction_type_watch'), + ] + + operations = [ + migrations.CreateModel( + name='Reminder', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('active', models.BooleanField(default=True, help_text='Whether this reminder is still active. If not, it has been sent out to the user.')), + ('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 that the user wants to be reminded of.', max_length=1500)), + ('expiration', models.DateTimeField(help_text='When this reminder should be sent.')), + ('author', models.ForeignKey(help_text='The creator of this reminder.', on_delete=django.db.models.deletion.CASCADE, to='api.User')), + ], + bases=(api.models.ModelReprMixin, models.Model), + ), + ] diff --git a/api/models.py b/api/models.py index 6dce0eb4..5ca274ce 100644 --- a/api/models.py +++ b/api/models.py @@ -369,3 +369,42 @@ class Infraction(ModelReprMixin, models.Model): if self.hidden: s += " (hidden)" return s + + +class Reminder(ModelReprMixin, models.Model): + """A reminder created by a user.""" + + active = models.BooleanField( + default=True, + help_text=( + "Whether this reminder is still active. " + "If not, it has been sent out to the user." + ) + ) + author = models.ForeignKey( + User, + on_delete=models.CASCADE, + help_text="The creator of this reminder." + ) + 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=1500, + help_text="The content that the user wants to be reminded of." + ) + expiration = models.DateTimeField( + help_text="When this reminder should be sent." + ) + + def __str__(self): + return f"{self.content} on {self.expiration} by {self.author}" diff --git a/api/serializers.py b/api/serializers.py index a3ab13e6..fca8008f 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -4,10 +4,11 @@ from rest_framework_bulk import BulkSerializerMixin from .models import ( DeletedMessage, DocumentationLink, Infraction, MessageDeletionContext, - OffTopicChannelName, Role, - SnakeFact, SnakeIdiom, - SnakeName, SpecialSnake, - Tag, User + OffTopicChannelName, Reminder, + Role, SnakeFact, + SnakeIdiom, SnakeName, + SpecialSnake, Tag, + User ) @@ -126,6 +127,14 @@ class SpecialSnakeSerializer(ModelSerializer): fields = ('name', 'images', 'info') +class ReminderSerializer(ModelSerializer): + author = PrimaryKeyRelatedField(queryset=User.objects.all()) + + class Meta: + model = Reminder + fields = ('active', 'author', 'channel_id', 'content', 'expiration', 'id') + + class RoleSerializer(ModelSerializer): class Meta: model = Role diff --git a/api/tests/test_models.py b/api/tests/test_models.py index 1fe60c0d..a958419d 100644 --- a/api/tests/test_models.py +++ b/api/tests/test_models.py @@ -6,10 +6,11 @@ from ..models import ( DeletedMessage, DocumentationLink, Infraction, Message, MessageDeletionContext, ModelReprMixin, - OffTopicChannelName, Role, - SnakeFact, SnakeIdiom, - SnakeName, SpecialSnake, - Tag, User + OffTopicChannelName, Reminder, + Role, SnakeFact, + SnakeIdiom, SnakeName, + SpecialSnake, Tag, + User ) @@ -95,6 +96,15 @@ class StringDunderMethodTests(SimpleTestCase): user_id=5, actor_id=5, hidden=True, type='kick', reason='He terk my jerb!', expires_at=dt(5018, 11, 20, 15, 52, tzinfo=timezone.utc) + ), + Reminder( + author=User( + id=452, name='billy', + discriminator=5, avatar_hash=None + ), + channel_id=555, + content="oh no", + expiration=dt(5018, 11, 20, 15, 52, tzinfo=timezone.utc) ) ) diff --git a/api/urls.py b/api/urls.py index 75980d75..c8548df1 100644 --- a/api/urls.py +++ b/api/urls.py @@ -5,10 +5,10 @@ from .views import HealthcheckView, RulesView from .viewsets import ( DeletedMessageViewSet, DocumentationLinkViewSet, InfractionViewSet, OffTopicChannelNameViewSet, - RoleViewSet, SnakeFactViewSet, - SnakeIdiomViewSet, SnakeNameViewSet, - SpecialSnakeViewSet, TagViewSet, - UserViewSet + ReminderViewSet, RoleViewSet, + SnakeFactViewSet, SnakeIdiomViewSet, + SnakeNameViewSet, SpecialSnakeViewSet, + TagViewSet, UserViewSet ) @@ -32,8 +32,8 @@ bot_router.register( base_name='offtopicchannelname' ) bot_router.register( - 'roles', - RoleViewSet + 'reminders', + ReminderViewSet ) bot_router.register( 'roles', diff --git a/api/viewsets.py b/api/viewsets.py index f8dd13e8..83945fe8 100644 --- a/api/viewsets.py +++ b/api/viewsets.py @@ -15,18 +15,19 @@ from rest_framework_bulk import BulkCreateModelMixin from .models import ( DocumentationLink, Infraction, MessageDeletionContext, OffTopicChannelName, - Role, SnakeFact, - SnakeIdiom, SnakeName, - SpecialSnake, Tag, - User + Reminder, Role, + SnakeFact, SnakeIdiom, + SnakeName, SpecialSnake, + Tag, User ) from .serializers import ( DocumentationLinkSerializer, ExpandedInfractionSerializer, InfractionSerializer, MessageDeletionContextSerializer, - OffTopicChannelNameSerializer, RoleSerializer, - SnakeFactSerializer, SnakeIdiomSerializer, - SnakeNameSerializer, SpecialSnakeSerializer, - TagSerializer, UserSerializer + OffTopicChannelNameSerializer, ReminderSerializer, + RoleSerializer, SnakeFactSerializer, + SnakeIdiomSerializer, SnakeNameSerializer, + SpecialSnakeSerializer, TagSerializer, + UserSerializer ) @@ -356,6 +357,61 @@ class OffTopicChannelNameViewSet(DestroyModelMixin, ViewSet): 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 |