aboutsummaryrefslogtreecommitdiffstats
path: root/api
diff options
context:
space:
mode:
authorGravatar Johannes Christ <[email protected]>2018-11-25 11:16:57 +0100
committerGravatar Johannes Christ <[email protected]>2018-11-25 11:16:57 +0100
commit87a48cad5197234a6ccff616fec17a027b2adcb8 (patch)
tree0ea7bebddf9ce216e94602ddeca300184729fb00 /api
parentUse proper attribute name. (diff)
parentSet up image pushing and building on Azure. (#152) (diff)
Merge branch 'django' into django+add-logs-api.
Diffstat (limited to 'api')
-rw-r--r--api/admin.py6
-rw-r--r--api/migrations/0018_messagedeletioncontext.py2
-rw-r--r--api/migrations/0018_user_rename.py17
-rw-r--r--api/migrations/0019_deletedmessage.py2
-rw-r--r--api/migrations/0019_user_in_guild.py18
-rw-r--r--api/migrations/0020_add_snake_field_validators.py24
-rw-r--r--api/migrations/0021_merge_20181125_1015.py14
-rw-r--r--api/models.py18
-rw-r--r--api/serializers.py18
-rw-r--r--api/tests/test_models.py22
-rw-r--r--api/tests/test_users.py (renamed from api/tests/test_members.py)30
-rw-r--r--api/urls.py12
-rw-r--r--api/viewsets.py81
13 files changed, 176 insertions, 88 deletions
diff --git a/api/admin.py b/api/admin.py
index af2cfbeb..bcd41a7e 100644
--- a/api/admin.py
+++ b/api/admin.py
@@ -2,17 +2,16 @@ from django.contrib import admin
from .models import (
DeletedMessage, DocumentationLink,
- Member, MessageDeletionContext,
+ MessageDeletionContext,
OffTopicChannelName, Role,
SnakeFact, SnakeIdiom,
SnakeName, SpecialSnake,
- Tag
+ Tag, User
)
admin.site.register(DeletedMessage)
admin.site.register(DocumentationLink)
-admin.site.register(Member)
admin.site.register(MessageDeletionContext)
admin.site.register(OffTopicChannelName)
admin.site.register(Role)
@@ -21,3 +20,4 @@ admin.site.register(SnakeIdiom)
admin.site.register(SnakeName)
admin.site.register(SpecialSnake)
admin.site.register(Tag)
+admin.site.register(User)
diff --git a/api/migrations/0018_messagedeletioncontext.py b/api/migrations/0018_messagedeletioncontext.py
index 39a4fb87..88cbab28 100644
--- a/api/migrations/0018_messagedeletioncontext.py
+++ b/api/migrations/0018_messagedeletioncontext.py
@@ -17,7 +17,7 @@ class Migration(migrations.Migration):
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.Member')),
+ ('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/0018_user_rename.py b/api/migrations/0018_user_rename.py
new file mode 100644
index 00000000..f88eb5bc
--- /dev/null
+++ b/api/migrations/0018_user_rename.py
@@ -0,0 +1,17 @@
+# Generated by Django 2.1.3 on 2018-11-19 20:09
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0017_auto_20181029_1921'),
+ ]
+
+ operations = [
+ migrations.RenameModel(
+ old_name='Member',
+ new_name='User',
+ ),
+ ]
diff --git a/api/migrations/0019_deletedmessage.py b/api/migrations/0019_deletedmessage.py
index b119c3ef..fbd94949 100644
--- a/api/migrations/0019_deletedmessage.py
+++ b/api/migrations/0019_deletedmessage.py
@@ -23,7 +23,7 @@ class Migration(migrations.Migration):
('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.Member')),
+ ('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={
diff --git a/api/migrations/0019_user_in_guild.py b/api/migrations/0019_user_in_guild.py
new file mode 100644
index 00000000..fda008c4
--- /dev/null
+++ b/api/migrations/0019_user_in_guild.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.1.3 on 2018-11-19 20:30
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0018_user_rename'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='user',
+ name='in_guild',
+ field=models.BooleanField(default=True, help_text='Whether this user is in our server.'),
+ ),
+ ]
diff --git a/api/migrations/0020_add_snake_field_validators.py b/api/migrations/0020_add_snake_field_validators.py
new file mode 100644
index 00000000..3b625f9b
--- /dev/null
+++ b/api/migrations/0020_add_snake_field_validators.py
@@ -0,0 +1,24 @@
+# Generated by Django 2.1.2 on 2018-11-24 17:11
+
+import django.core.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0019_user_in_guild'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='snakename',
+ name='name',
+ field=models.CharField(help_text="The regular name for this snake, e.g. 'Python'.", max_length=100, primary_key=True, serialize=False, validators=[django.core.validators.RegexValidator(regex='^([^0-9])+$')]),
+ ),
+ migrations.AlterField(
+ model_name='snakename',
+ name='scientific',
+ field=models.CharField(help_text="The scientific name for this snake, e.g. 'Python bivittatus'.", max_length=150, validators=[django.core.validators.RegexValidator(regex='^([^0-9])+$')]),
+ ),
+ ]
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/models.py b/api/models.py
index ded9ebeb..68833328 100644
--- a/api/models.py
+++ b/api/models.py
@@ -92,11 +92,13 @@ class SnakeName(ModelReprMixin, models.Model):
name = models.CharField(
primary_key=True,
max_length=100,
- help_text="The regular name for this snake, e.g. 'Python'."
+ help_text="The regular name for this snake, e.g. 'Python'.",
+ validators=[RegexValidator(regex=r'^([^0-9])+$')]
)
scientific = models.CharField(
max_length=150,
- help_text="The scientific name for this snake, e.g. 'Python bivittatus'."
+ help_text="The scientific name for this snake, e.g. 'Python bivittatus'.",
+ validators=[RegexValidator(regex=r'^([^0-9])+$')]
)
def __str__(self):
@@ -167,8 +169,8 @@ class Role(ModelReprMixin, models.Model):
return self.name
-class Member(ModelReprMixin, models.Model):
- """A member of our Discord server."""
+class User(ModelReprMixin, models.Model):
+ """A Discord user."""
id = models.BigIntegerField( # noqa
primary_key=True,
@@ -205,6 +207,10 @@ class Member(ModelReprMixin, models.Model):
Role,
help_text="Any roles this user has on our server."
)
+ in_guild = models.BooleanField(
+ default=True,
+ help_text="Whether this user is in our server."
+ )
def __str__(self):
return f"{self.name}#{self.discriminator}"
@@ -222,7 +228,7 @@ class Message(ModelReprMixin, models.Model):
)
)
author = models.ForeignKey(
- Member,
+ User,
on_delete=models.CASCADE,
help_text="The author of this message."
)
@@ -255,7 +261,7 @@ class Message(ModelReprMixin, models.Model):
class MessageDeletionContext(ModelReprMixin, models.Model):
actor = models.ForeignKey(
- Member,
+ User,
on_delete=models.CASCADE,
help_text=(
"The original actor causing this deletion. Could be the author "
diff --git a/api/serializers.py b/api/serializers.py
index e39cd4a3..8091ac63 100644
--- a/api/serializers.py
+++ b/api/serializers.py
@@ -3,11 +3,11 @@ from rest_framework_bulk import BulkSerializerMixin
from .models import (
DeletedMessage, DocumentationLink,
- Member, MessageDeletionContext,
- OffTopicChannelName, Role,
- SnakeFact, SnakeIdiom,
- SnakeName, SpecialSnake,
- Tag
+ MessageDeletionContext, OffTopicChannelName,
+ Role, SnakeFact,
+ SnakeIdiom, SnakeName,
+ SpecialSnake, Tag,
+ User
)
@@ -74,10 +74,10 @@ class TagSerializer(ModelSerializer):
fields = ('title', 'embed')
-class MemberSerializer(BulkSerializerMixin, ModelSerializer):
- roles = PrimaryKeyRelatedField(many=True, queryset=Role.objects.all())
+class UserSerializer(BulkSerializerMixin, ModelSerializer):
+ roles = PrimaryKeyRelatedField(many=True, queryset=Role.objects.all(), required=False)
class Meta:
- model = Member
- fields = ('id', 'avatar_hash', 'name', 'discriminator', 'roles')
+ model = User
+ fields = ('id', 'avatar_hash', 'name', 'discriminator', 'roles', 'in_guild')
depth = 1
diff --git a/api/tests/test_models.py b/api/tests/test_models.py
index 8d41c23e..968f003e 100644
--- a/api/tests/test_models.py
+++ b/api/tests/test_models.py
@@ -4,12 +4,12 @@ from django.test import SimpleTestCase
from ..models import (
DeletedMessage, DocumentationLink,
- Member, Message,
- MessageDeletionContext, ModelReprMixin,
- OffTopicChannelName, Role,
- SnakeFact, SnakeIdiom,
- SnakeName, SpecialSnake,
- Tag
+ Message, MessageDeletionContext,
+ ModelReprMixin, OffTopicChannelName,
+ Role, SnakeFact,
+ SnakeIdiom, SnakeName,
+ SpecialSnake, Tag,
+ User
)
@@ -32,14 +32,14 @@ class StringDunderMethodTests(SimpleTestCase):
self.objects = (
DeletedMessage(
id=45,
- author=Member(
+ author=User(
id=444, name='bill',
discriminator=5, avatar_hash=None
),
channel_id=666,
content="wooey",
deletion_context=MessageDeletionContext(
- actor=Member(
+ actor=User(
id=5555, name='shawn',
discriminator=555, avatar_hash=None
),
@@ -64,7 +64,7 @@ class StringDunderMethodTests(SimpleTestCase):
),
Message(
id=45,
- author=Member(
+ author=User(
id=444, name='bill',
discriminator=5, avatar_hash=None
),
@@ -73,13 +73,13 @@ class StringDunderMethodTests(SimpleTestCase):
embeds=[]
),
MessageDeletionContext(
- actor=Member(
+ actor=User(
id=5555, name='shawn',
discriminator=555, avatar_hash=None
),
creation=datetime.utcnow()
),
- Member(
+ User(
id=5, name='bob',
discriminator=1, avatar_hash=None
),
diff --git a/api/tests/test_members.py b/api/tests/test_users.py
index 47466b62..8dadcbdb 100644
--- a/api/tests/test_members.py
+++ b/api/tests/test_users.py
@@ -1,7 +1,7 @@
from django_hosts.resolvers import reverse
from .base import APISubdomainTestCase
-from ..models import Member, Role
+from ..models import Role, User
class UnauthedDocumentationLinkAPITests(APISubdomainTestCase):
@@ -10,25 +10,25 @@ class UnauthedDocumentationLinkAPITests(APISubdomainTestCase):
self.client.force_authenticate(user=None)
def test_detail_lookup_returns_401(self):
- url = reverse('bot:member-detail', args=('whatever',), host='api')
+ url = reverse('bot:user-detail', args=('whatever',), host='api')
response = self.client.get(url)
self.assertEqual(response.status_code, 401)
def test_list_returns_401(self):
- url = reverse('bot:member-list', host='api')
+ url = reverse('bot:user-list', host='api')
response = self.client.get(url)
self.assertEqual(response.status_code, 401)
def test_create_returns_401(self):
- url = reverse('bot:member-list', host='api')
+ url = reverse('bot:user-list', host='api')
response = self.client.post(url, data={'hi': 'there'})
self.assertEqual(response.status_code, 401)
def test_delete_returns_401(self):
- url = reverse('bot:member-detail', args=('whatever',), host='api')
+ url = reverse('bot:user-detail', args=('whatever',), host='api')
response = self.client.delete(url)
self.assertEqual(response.status_code, 401)
@@ -45,7 +45,7 @@ class CreationTests(APISubdomainTestCase):
)
def test_accepts_valid_data(self):
- url = reverse('bot:member-list', host='api')
+ url = reverse('bot:user-list', host='api')
data = {
'id': 42,
'avatar_hash': "validavatarhashiswear",
@@ -53,20 +53,22 @@ class CreationTests(APISubdomainTestCase):
'discriminator': 42,
'roles': [
self.role.id
- ]
+ ],
+ 'in_guild': True
}
response = self.client.post(url, data=data)
self.assertEqual(response.status_code, 201)
self.assertEqual(response.json(), data)
- user = Member.objects.get(id=42)
+ user = User.objects.get(id=42)
self.assertEqual(user.avatar_hash, data['avatar_hash'])
self.assertEqual(user.name, data['name'])
self.assertEqual(user.discriminator, data['discriminator'])
+ self.assertEqual(user.in_guild, data['in_guild'])
def test_supports_multi_creation(self):
- url = reverse('bot:member-list', host='api')
+ url = reverse('bot:user-list', host='api')
data = [
{
'id': 5,
@@ -75,14 +77,16 @@ class CreationTests(APISubdomainTestCase):
'discriminator': 42,
'roles': [
self.role.id
- ]
+ ],
+ 'in_guild': True
},
{
'id': 8,
'avatar_hash': "maybenot",
'name': "another test man",
'discriminator': 555,
- 'roles': []
+ 'roles': [],
+ 'in_guild': False
}
]
@@ -91,7 +95,7 @@ class CreationTests(APISubdomainTestCase):
self.assertEqual(response.json(), data)
def test_returns_400_for_unknown_role_id(self):
- url = reverse('bot:member-list', host='api')
+ url = reverse('bot:user-list', host='api')
data = {
'id': 5,
'avatar_hash': "hahayes",
@@ -106,7 +110,7 @@ class CreationTests(APISubdomainTestCase):
self.assertEqual(response.status_code, 400)
def test_returns_400_for_bad_data(self):
- url = reverse('bot:member-list', host='api')
+ url = reverse('bot:user-list', host='api')
data = {
'id': True,
'avatar_hash': 1902831,
diff --git a/api/urls.py b/api/urls.py
index 2bca5689..dca208d8 100644
--- a/api/urls.py
+++ b/api/urls.py
@@ -4,10 +4,10 @@ from rest_framework.routers import DefaultRouter
from .views import HealthcheckView
from .viewsets import (
DeletedMessageViewSet, DocumentationLinkViewSet,
- MemberViewSet, OffTopicChannelNameViewSet,
- RoleViewSet, SnakeFactViewSet,
- SnakeIdiomViewSet, SnakeNameViewSet,
- SpecialSnakeViewSet, TagViewSet
+ OffTopicChannelNameViewSet, RoleViewSet,
+ SnakeFactViewSet, SnakeIdiomViewSet,
+ SnakeNameViewSet, SpecialSnakeViewSet,
+ TagViewSet, UserViewSet
)
@@ -27,8 +27,8 @@ bot_router.register(
base_name='offtopicchannelname'
)
bot_router.register(
- 'members',
- MemberViewSet
+ 'users',
+ UserViewSet
)
bot_router.register(
'roles',
diff --git a/api/viewsets.py b/api/viewsets.py
index 8dca5b2a..e406e8c3 100644
--- a/api/viewsets.py
+++ b/api/viewsets.py
@@ -10,18 +10,18 @@ from rest_framework.viewsets import GenericViewSet, ModelViewSet, ViewSet
from rest_framework_bulk import BulkCreateModelMixin
from .models import (
- DocumentationLink, Member,
- MessageDeletionContext, OffTopicChannelName,
- Role, SnakeFact,
- SnakeIdiom, SnakeName,
- SpecialSnake, Tag
+ DocumentationLink, MessageDeletionContext,
+ OffTopicChannelName, Role,
+ SnakeFact, SnakeIdiom,
+ SnakeName, SpecialSnake,
+ Tag, User
)
from .serializers import (
- DocumentationLinkSerializer, MemberSerializer,
- MessageDeletionContextSerializer, OffTopicChannelNameSerializer,
- RoleSerializer, SnakeFactSerializer,
- SnakeIdiomSerializer, SnakeNameSerializer,
- SpecialSnakeSerializer, TagSerializer
+ DocumentationLinkSerializer, MessageDeletionContextSerializer,
+ OffTopicChannelNameSerializer, RoleSerializer,
+ SnakeFactSerializer, SnakeIdiomSerializer,
+ SnakeNameSerializer, SpecialSnakeSerializer,
+ TagSerializer, UserSerializer
)
@@ -539,13 +539,13 @@ class TagViewSet(ModelViewSet):
queryset = Tag.objects.all()
-class MemberViewSet(BulkCreateModelMixin, ModelViewSet):
+class UserViewSet(BulkCreateModelMixin, ModelViewSet):
"""
- View providing CRUD operations on our Discord server's members through the bot.
+ View providing CRUD operations on Discord users through the bot.
## Routes
- ### GET /bot/members
- Returns all members currently known.
+ ### GET /bot/users
+ Returns all users currently known.
#### Response format
>>> [
@@ -559,15 +559,16 @@ class MemberViewSet(BulkCreateModelMixin, ModelViewSet):
... 270988689419665409,
... 277546923144249364,
... 458226699344019457
- ... ]
+ ... ],
+ ... 'in_guild': True
... }
... ]
#### Status codes
- 200: returned on success
- ### GET /bot/members/<snowflake:int>
- Gets a single member by ID.
+ ### GET /bot/users/<snowflake:int>
+ Gets a single user by ID.
#### Response format
>>> {
@@ -580,16 +581,17 @@ class MemberViewSet(BulkCreateModelMixin, ModelViewSet):
... 270988689419665409,
... 277546923144249364,
... 458226699344019457
- ... ]
+ ... ],
+ ... 'in_guild': True
... }
#### Status codes
- 200: returned on success
- - 404: if a member with the given `snowflake` could not be found
+ - 404: if a user with the given `snowflake` could not be found
- ### POST /bot/members
- Adds a single or multiple new members.
- The roles attached to the member(s) must be roles known by the site.
+ ### 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
>>> {
@@ -597,18 +599,19 @@ class MemberViewSet(BulkCreateModelMixin, ModelViewSet):
... 'avatar': str,
... 'name': str,
... 'discriminator': int,
- ... 'roles': List[int]
+ ... 'roles': List[int],
+ ... 'in_guild': bool
... }
- Alternatively, request members can be POSTed as a list of above objects,
- in which case multiple members will be created at once.
+ 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/members/<snowflake:int>
- Update the member with the given `snowflake`.
+ ### PUT /bot/users/<snowflake:int>
+ Update the user with the given `snowflake`.
All fields in the request body are required.
#### Request body
@@ -617,16 +620,17 @@ class MemberViewSet(BulkCreateModelMixin, ModelViewSet):
... 'avatar': str,
... 'name': str,
... 'discriminator': int,
- ... 'roles': List[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 member with the given `snowflake` could not be found
+ - 404: if the user with the given `snowflake` could not be found
- ### PATCH /bot/members/<snowflake:int>
- Update the member with the given `snowflake`.
+ ### PATCH /bot/users/<snowflake:int>
+ Update the user with the given `snowflake`.
All fields in the request body are optional.
#### Request body
@@ -635,21 +639,22 @@ class MemberViewSet(BulkCreateModelMixin, ModelViewSet):
... 'avatar': str,
... 'name': str,
... 'discriminator': int,
- ... 'roles': List[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 member with the given `snowflake` could not be found
+ - 404: if the user with the given `snowflake` could not be found
- ### DELETE /bot/members/<snowflake:int>
- Deletes the member with the given `snowflake`.
+ ### DELETE /bot/users/<snowflake:int>
+ Deletes the user with the given `snowflake`.
#### Status codes
- 204: returned on success
- - 404: if a member with the given `snowflake` does not exist
+ - 404: if a user with the given `snowflake` does not exist
"""
- serializer_class = MemberSerializer
- queryset = Member.objects.all()
+ serializer_class = UserSerializer
+ queryset = User.objects.all()