diff options
author | 2019-08-15 18:24:06 +0200 | |
---|---|---|
committer | 2019-08-15 18:24:06 +0200 | |
commit | 1d08bd96cb089a0881ffec2ee0e9c7f4adf5493d (patch) | |
tree | 8815494032873c9dc62f89447df49b2b3f674c30 /pydis_site | |
parent | Merge pull request #235 from python-discord/ouroboros-logging-handler (diff) |
Adding position, role hierarchy comparisons to Role model; top_role to User model
Diffstat (limited to 'pydis_site')
-rw-r--r-- | pydis_site/apps/api/migrations/0039_add_position_field_to_role.py | 18 | ||||
-rw-r--r-- | pydis_site/apps/api/models/bot/role.py | 10 | ||||
-rw-r--r-- | pydis_site/apps/api/models/bot/user.py | 4 | ||||
-rw-r--r-- | pydis_site/apps/api/serializers.py | 2 | ||||
-rw-r--r-- | pydis_site/apps/api/tests/test_models.py | 3 | ||||
-rw-r--r-- | pydis_site/apps/api/tests/test_roles.py | 159 | ||||
-rw-r--r-- | pydis_site/apps/api/tests/test_users.py | 30 | ||||
-rw-r--r-- | pydis_site/apps/api/viewsets/bot/role.py | 21 |
8 files changed, 239 insertions, 8 deletions
diff --git a/pydis_site/apps/api/migrations/0039_add_position_field_to_role.py b/pydis_site/apps/api/migrations/0039_add_position_field_to_role.py new file mode 100644 index 00000000..0b8b0db3 --- /dev/null +++ b/pydis_site/apps/api/migrations/0039_add_position_field_to_role.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.3 on 2019-08-15 11:42 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0038_merge_20190719_1817'), + ] + + operations = [ + migrations.AddField( + model_name='role', + name='position', + field=models.IntegerField(default=-1, help_text='The position of the role in the role hierarchy of the Discord Guild.'), + ), + ] diff --git a/pydis_site/apps/api/models/bot/role.py b/pydis_site/apps/api/models/bot/role.py index 34e74009..777168b8 100644 --- a/pydis_site/apps/api/models/bot/role.py +++ b/pydis_site/apps/api/models/bot/role.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models @@ -43,7 +45,15 @@ class Role(ModelReprMixin, models.Model): ), help_text="The integer value of the permission bitset of this role from Discord." ) + position = models.IntegerField( + default=-1, + help_text="The position of the role in the role hierarchy of the Discord Guild." + ) def __str__(self): """Returns the name of the current role, for display purposes.""" return self.name + + def __lt__(self, other: Role): + """Compares the roles based on their position in the role hierarchy of the guild.""" + return self.position < other.position diff --git a/pydis_site/apps/api/models/bot/user.py b/pydis_site/apps/api/models/bot/user.py index d4deb630..1c566989 100644 --- a/pydis_site/apps/api/models/bot/user.py +++ b/pydis_site/apps/api/models/bot/user.py @@ -51,3 +51,7 @@ 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}" + + @property + def top_role(self) -> Role: + return max(self.roles.all()) diff --git a/pydis_site/apps/api/serializers.py b/pydis_site/apps/api/serializers.py index 51ad9276..38275c82 100644 --- a/pydis_site/apps/api/serializers.py +++ b/pydis_site/apps/api/serializers.py @@ -195,7 +195,7 @@ class RoleSerializer(ModelSerializer): """Metadata defined for the Django REST Framework.""" model = Role - fields = ('id', 'name', 'colour', 'permissions') + fields = ('id', 'name', 'colour', 'permissions', 'position') class TagSerializer(ModelSerializer): diff --git a/pydis_site/apps/api/tests/test_models.py b/pydis_site/apps/api/tests/test_models.py index cfc8464e..2120b056 100644 --- a/pydis_site/apps/api/tests/test_models.py +++ b/pydis_site/apps/api/tests/test_models.py @@ -51,7 +51,8 @@ class StringDunderMethodTests(SimpleTestCase): OffTopicChannelName(name='bob-the-builders-playground'), Role( id=5, name='test role', - colour=0x5, permissions=0 + colour=0x5, permissions=0, + position=10, ), Message( id=45, diff --git a/pydis_site/apps/api/tests/test_roles.py b/pydis_site/apps/api/tests/test_roles.py new file mode 100644 index 00000000..882083bb --- /dev/null +++ b/pydis_site/apps/api/tests/test_roles.py @@ -0,0 +1,159 @@ +from django_hosts.resolvers import reverse + +from .base import APISubdomainTestCase +from ..models import Role + + +class CreationTests(APISubdomainTestCase): + @classmethod + def setUpTestData(cls): # noqa + cls.admins_role = Role.objects.create( + id=1, + name="Admins", + colour=1, + permissions=1, + position=4, + ) + cls.developers_role = Role.objects.create( + id=4, + name="Helpers", + colour=4, + permissions=4, + position=1, + ) + cls.everyone_role = Role.objects.create( + id=5, + name="@everyone", + colour=5, + permissions=5, + position=0, + ) + + def _validate_roledict(self, role_dict: dict) -> None: + """Helper method to validate a dict representing a role.""" + self.assertIsInstance(role_dict, dict) + self.assertEqual(len(role_dict), 5) + attributes = ('id', 'name', 'colour', 'permissions', 'position') + self.assertTrue(all(attribute in role_dict for attribute in attributes)) + + def test_role_ordering(self): + """Tests the __lt__ comparisons based on role position in the hierarchy.""" + self.assertTrue(self.everyone_role < self.developers_role) + self.assertFalse(self.developers_role > self.admins_role) + + top_role = max([self.developers_role, self.admins_role, self.everyone_role]) + self.assertIs(top_role, self.admins_role) + + def test_role_list(self): + """Tests the GET list-view and validates the contents.""" + url = reverse('bot:role-list', host='api') + + response = self.client.get(url) + self.assertContains(response, text="id", count=3, status_code=200) + + roles = response.json() + self.assertIsInstance(roles, list) + self.assertEqual(len(roles), 3) + + for role in roles: + self._validate_roledict(role) + + def test_role_get_detail_success(self): + """Tests GET detail view of an existing role.""" + url = reverse('bot:role-detail', host='api', args=(self.admins_role.id, )) + response = self.client.get(url) + self.assertContains(response, text="id", count=1, status_code=200) + + role = response.json() + self._validate_roledict(role) + + admins_role = Role.objects.get(id=role["id"]) + self.assertEqual(admins_role.name, role["name"]) + self.assertEqual(admins_role.colour, role["colour"]) + self.assertEqual(admins_role.permissions, role["permissions"]) + self.assertEqual(admins_role.position, role["position"]) + + def test_role_post_201(self): + """Tests creation of a role with a valid request.""" + url = reverse('bot:role-list', host='api') + data = { + "id": 1234567890, + "name": "Role Creation Test", + "permissions": 0b01010010101, + "colour": 1, + "position": 10, + } + response = self.client.post(url, data=data) + self.assertEqual(response.status_code, 201) + + def test_role_post_invalid_request_body(self): + """Tests creation of a role with an invalid request body.""" + url = reverse('bot:role-list', host='api') + data = { + "name": "Role Creation Test", + "permissions": 0b01010010101, + "colour": 1, + "position": 10, + } + response = self.client.post(url, data=data) + self.assertEqual(response.status_code, 400) + self.assertJSONEqual(response.content, '{"id": ["This field is required."]}') + + def test_role_put_200(self): + """Tests PUT role request with valid request body.""" + url = reverse('bot:role-detail', host='api', args=(self.admins_role.id,)) + data = { + "id": 123454321, + "name": "Role Put Alteration Test", + "permissions": 255, + "colour": 999, + "position": 20, + } + + response = self.client.put(url, data=data) + self.assertEqual(response.status_code, 200) + + admins_role = Role.objects.get(id=data["id"]) + self.assertEqual(admins_role.name, data["name"]) + self.assertEqual(admins_role.permissions, data["permissions"]) + self.assertEqual(admins_role.colour, data["colour"]) + self.assertEqual(admins_role.position, data["position"]) + + def test_role_put_invalid_request_body(self): + """Tests PUT role request with invalid request body.""" + url = reverse('bot:role-detail', host='api', args=(self.admins_role.id,)) + data = { + "name": "Role Put Alteration Test", + "permissions": 255, + "colour": 999, + "position": 20, + } + response = self.client.put(url, data=data) + self.assertEqual(response.status_code, 400) + + def test_role_patch_200(self): + """Tests PATCH role request with valid request body.""" + url = reverse('bot:role-detail', host='api', args=(self.admins_role.id,)) + data = { + "name": "Owners" + } + response = self.client.patch(url, data=data) + self.assertEqual(response.status_code, 200) + + admins_role = Role.objects.get(id=self.admins_role.id) + self.assertEqual(admins_role.name, data["name"]) + + def test_role_delete_200(self): + """Tests DELETE requests for existing role.""" + url = reverse('bot:role-detail', host='api', args=(self.admins_role.id,)) + response = self.client.delete(url) + self.assertEqual(response.status_code, 204) + + def test_role_detail_404_all_methods(self): + """Tests detail view with non-existing ID.""" + url = reverse('bot:role-detail', host='api', args=(20190815,)) + + for method in ('get', 'put', 'patch', 'delete'): + response = getattr(self.client, method)(url) + self.assertEqual(response.status_code, 404) + self.assertJSONEqual(response.content, '{"detail": "Not found."}') diff --git a/pydis_site/apps/api/tests/test_users.py b/pydis_site/apps/api/tests/test_users.py index 90bc3d30..44ce481e 100644 --- a/pydis_site/apps/api/tests/test_users.py +++ b/pydis_site/apps/api/tests/test_users.py @@ -41,8 +41,31 @@ class CreationTests(APISubdomainTestCase): id=5, name="Test role pls ignore", colour=2, - permissions=0b01010010101 + permissions=0b01010010101, + position=1 ) + cls.role_bottom = Role.objects.create( + id=6, + name="Low test role", + colour=2, + permissions=0b01010010101, + position=0, + ) + cls.role_top = Role.objects.create( + id=7, + name="High test role", + colour=2, + permissions=0b01010010101, + position=10, + ) + cls.role_test_user = User.objects.create( + id=1, + avatar_hash="coolavatarhash", + name="Test User", + discriminator=1111, + in_guild=True, + ) + cls.role_test_user.roles.add(cls.role_bottom, cls.role_top) def test_accepts_valid_data(self): url = reverse('bot:user-list', host='api') @@ -119,3 +142,8 @@ class CreationTests(APISubdomainTestCase): response = self.client.post(url, data=data) self.assertEqual(response.status_code, 400) + + def test_correct_top_role_property(self): + """Tests if the top_role property returns the correct role.""" + self.assertIsInstance(self.role_test_user.top_role, Role) + self.assertEqual(self.role_test_user.top_role.id, self.role_top.id) diff --git a/pydis_site/apps/api/viewsets/bot/role.py b/pydis_site/apps/api/viewsets/bot/role.py index 213f0a19..1f82208d 100644 --- a/pydis_site/apps/api/viewsets/bot/role.py +++ b/pydis_site/apps/api/viewsets/bot/role.py @@ -20,7 +20,8 @@ class RoleViewSet(ModelViewSet): ... 'id': 267628507062992896, ... 'name': "Admins", ... 'colour': 1337, - ... 'permissions': 8 + ... 'permissions': 8, + ... 'position': 1 ... } ... ] @@ -35,7 +36,8 @@ class RoleViewSet(ModelViewSet): ... 'id': 267628507062992896, ... 'name': "Admins", ... 'colour': 1337, - ... 'permissions': 8 + ... 'permissions': 8, + ... 'position': 1 ... } #### Status codes @@ -51,6 +53,7 @@ class RoleViewSet(ModelViewSet): ... 'name': str, ... 'colour': int, ... 'permissions': int, + ... 'position': 1, ... } #### Status codes @@ -66,24 +69,32 @@ class RoleViewSet(ModelViewSet): ... 'id': int, ... 'name': str, ... 'colour': int, - ... 'permissions': int + ... 'permissions': int, + ... 'position': 1, ... } #### Status codes - 200: returned on success - 400: if the request body was invalid + - 404: if a role with the given `snowflake` does not exist ### PATCH /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 + ... 'permissions': int, + ... 'position': 1, ... } + #### Status codes + - 200: returned on success + - 400: if the request body was invalid + - 404: if a role with the given `snowflake` does not exist + ### DELETE /bot/roles/<snowflake:int> Deletes the role with the given `snowflake`. |