aboutsummaryrefslogtreecommitdiffstats
path: root/pydis_site
diff options
context:
space:
mode:
authorGravatar Leon Sandøy <[email protected]>2020-08-27 00:59:39 +0200
committerGravatar GitHub <[email protected]>2020-08-27 00:59:39 +0200
commit05a0575e28abfebb54a7f3996c182ae6ae091ab6 (patch)
tree61fa0951297cd0745bda4b95a3c40f04e042f4ad /pydis_site
parentOT: Rename variable `ext` to `other_names` (diff)
parentMerge pull request #374 from Numerlor/reminder-direct-retrieve (diff)
Merge branch 'master' into off-topic-non-random
Diffstat (limited to 'pydis_site')
-rw-r--r--pydis_site/apps/api/migrations/0007_tag.py2
-rw-r--r--pydis_site/apps/api/migrations/0009_snakefact.py2
-rw-r--r--pydis_site/apps/api/migrations/0010_snakeidiom.py2
-rw-r--r--pydis_site/apps/api/migrations/0012_specialsnake.py2
-rw-r--r--pydis_site/apps/api/migrations/0018_messagedeletioncontext.py2
-rw-r--r--pydis_site/apps/api/migrations/0019_deletedmessage.py2
-rw-r--r--pydis_site/apps/api/migrations/0020_infraction.py2
-rw-r--r--pydis_site/apps/api/migrations/0025_allow_custom_inserted_at_infraction_field.py4
-rw-r--r--pydis_site/apps/api/migrations/0030_reminder.py2
-rw-r--r--pydis_site/apps/api/migrations/0031_nomination.py2
-rw-r--r--pydis_site/apps/api/migrations/0032_botsetting.py2
-rw-r--r--pydis_site/apps/api/migrations/0035_create_table_log_entry.py2
-rw-r--r--pydis_site/apps/api/migrations/0049_offensivemessage.py4
-rw-r--r--pydis_site/apps/api/migrations/0051_allow_blank_message_embeds.py21
-rw-r--r--pydis_site/apps/api/migrations/0052_remove_user_avatar_hash.py17
-rw-r--r--pydis_site/apps/api/migrations/0053_user_roles_to_array.py24
-rw-r--r--pydis_site/apps/api/migrations/0054_user_invalidate_unknown_role.py21
-rw-r--r--pydis_site/apps/api/migrations/0055_merge_20200714_2027.py14
-rw-r--r--pydis_site/apps/api/migrations/0055_reminder_mentions.py20
-rw-r--r--pydis_site/apps/api/migrations/0056_allow_blank_user_roles.py21
-rw-r--r--pydis_site/apps/api/migrations/0057_merge_20200716_0751.py14
-rw-r--r--pydis_site/apps/api/migrations/0058_create_new_filterlist_model.py33
-rw-r--r--pydis_site/apps/api/migrations/0059_populate_filterlists.py153
-rw-r--r--pydis_site/apps/api/migrations/0060_populate_filterlists_fix.py85
-rw-r--r--pydis_site/apps/api/models/__init__.py2
-rw-r--r--pydis_site/apps/api/models/bot/__init__.py1
-rw-r--r--pydis_site/apps/api/models/bot/bot_setting.py2
-rw-r--r--pydis_site/apps/api/models/bot/documentation_link.py2
-rw-r--r--pydis_site/apps/api/models/bot/filter_list.py41
-rw-r--r--pydis_site/apps/api/models/bot/infraction.py2
-rw-r--r--pydis_site/apps/api/models/bot/message.py3
-rw-r--r--pydis_site/apps/api/models/bot/message_deletion_context.py2
-rw-r--r--pydis_site/apps/api/models/bot/nomination.py2
-rw-r--r--pydis_site/apps/api/models/bot/off_topic_channel_name.py2
-rw-r--r--pydis_site/apps/api/models/bot/offensive_message.py2
-rw-r--r--pydis_site/apps/api/models/bot/reminder.py16
-rw-r--r--pydis_site/apps/api/models/bot/role.py2
-rw-r--r--pydis_site/apps/api/models/bot/tag.py2
-rw-r--r--pydis_site/apps/api/models/bot/user.py38
-rw-r--r--pydis_site/apps/api/models/log_entry.py2
-rw-r--r--pydis_site/apps/api/models/mixins.py (renamed from pydis_site/apps/api/models/utils.py)14
-rw-r--r--pydis_site/apps/api/serializers.py52
-rw-r--r--pydis_site/apps/api/tests/test_deleted_messages.py2
-rw-r--r--pydis_site/apps/api/tests/test_filterlists.py122
-rw-r--r--pydis_site/apps/api/tests/test_infractions.py7
-rw-r--r--pydis_site/apps/api/tests/test_models.py57
-rw-r--r--pydis_site/apps/api/tests/test_nominations.py2
-rw-r--r--pydis_site/apps/api/tests/test_reminders.py33
-rw-r--r--pydis_site/apps/api/tests/test_users.py10
-rw-r--r--pydis_site/apps/api/urls.py24
-rw-r--r--pydis_site/apps/api/views.py3
-rw-r--r--pydis_site/apps/api/viewsets/__init__.py1
-rw-r--r--pydis_site/apps/api/viewsets/bot/__init__.py1
-rw-r--r--pydis_site/apps/api/viewsets/bot/filter_list.py97
-rw-r--r--pydis_site/apps/api/viewsets/bot/reminder.py62
-rw-r--r--pydis_site/apps/api/viewsets/bot/user.py7
-rw-r--r--pydis_site/apps/home/forms/account_deletion.py14
-rw-r--r--pydis_site/apps/home/signals.py10
-rw-r--r--pydis_site/apps/home/tests/mock_github_api_response.json2
-rw-r--r--pydis_site/apps/home/tests/test_signal_listener.py15
-rw-r--r--pydis_site/apps/home/views/home.py4
-rw-r--r--pydis_site/apps/staff/tests/test_logs_view.py3
-rw-r--r--pydis_site/settings.py16
-rw-r--r--pydis_site/static/images/events/summer_code_jam_2020.pngbin0 -> 271282 bytes
-rw-r--r--pydis_site/templates/base/navbar.html4
-rw-r--r--pydis_site/templates/home/account/delete.html9
-rw-r--r--pydis_site/templates/home/index.html4
-rw-r--r--pydis_site/templates/wiki/base.html2
-rw-r--r--pydis_site/tests/__init__.py0
-rw-r--r--pydis_site/tests/test_utils_account.py79
-rw-r--r--pydis_site/utils/account.py2
71 files changed, 1026 insertions, 206 deletions
diff --git a/pydis_site/apps/api/migrations/0007_tag.py b/pydis_site/apps/api/migrations/0007_tag.py
index c22715f9..b6d146fe 100644
--- a/pydis_site/apps/api/migrations/0007_tag.py
+++ b/pydis_site/apps/api/migrations/0007_tag.py
@@ -18,6 +18,6 @@ class Migration(migrations.Migration):
('title', models.CharField(help_text='The title of this tag, shown in searches and providing a quick overview over what this embed contains.', max_length=100, primary_key=True, serialize=False)),
('embed', django.contrib.postgres.fields.jsonb.JSONField(help_text='The actual embed shown by this tag.')),
],
- bases=(pydis_site.apps.api.models.ModelReprMixin, models.Model),
+ bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model),
),
]
diff --git a/pydis_site/apps/api/migrations/0009_snakefact.py b/pydis_site/apps/api/migrations/0009_snakefact.py
index 4fc63bc9..fd583846 100644
--- a/pydis_site/apps/api/migrations/0009_snakefact.py
+++ b/pydis_site/apps/api/migrations/0009_snakefact.py
@@ -16,6 +16,6 @@ class Migration(migrations.Migration):
fields=[
('fact', models.CharField(help_text='A fact about snakes.', max_length=200, primary_key=True, serialize=False)),
],
- bases=(pydis_site.apps.api.models.ModelReprMixin, models.Model),
+ bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model),
),
]
diff --git a/pydis_site/apps/api/migrations/0010_snakeidiom.py b/pydis_site/apps/api/migrations/0010_snakeidiom.py
index be089cf4..7d06ce5f 100644
--- a/pydis_site/apps/api/migrations/0010_snakeidiom.py
+++ b/pydis_site/apps/api/migrations/0010_snakeidiom.py
@@ -16,6 +16,6 @@ class Migration(migrations.Migration):
fields=[
('idiom', models.CharField(help_text='A snake idiom', max_length=140, primary_key=True, serialize=False)),
],
- bases=(pydis_site.apps.api.models.ModelReprMixin, models.Model),
+ bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model),
),
]
diff --git a/pydis_site/apps/api/migrations/0012_specialsnake.py b/pydis_site/apps/api/migrations/0012_specialsnake.py
index 77072526..ed0c1563 100644
--- a/pydis_site/apps/api/migrations/0012_specialsnake.py
+++ b/pydis_site/apps/api/migrations/0012_specialsnake.py
@@ -17,6 +17,6 @@ class Migration(migrations.Migration):
('name', models.CharField(max_length=140, primary_key=True, serialize=False)),
('info', models.TextField()),
],
- bases=(pydis_site.apps.api.models.ModelReprMixin, models.Model),
+ bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model),
),
]
diff --git a/pydis_site/apps/api/migrations/0018_messagedeletioncontext.py b/pydis_site/apps/api/migrations/0018_messagedeletioncontext.py
index dced1288..7e372d04 100644
--- a/pydis_site/apps/api/migrations/0018_messagedeletioncontext.py
+++ b/pydis_site/apps/api/migrations/0018_messagedeletioncontext.py
@@ -19,6 +19,6 @@ class Migration(migrations.Migration):
('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=(pydis_site.apps.api.models.ModelReprMixin, models.Model),
+ bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model),
),
]
diff --git a/pydis_site/apps/api/migrations/0019_deletedmessage.py b/pydis_site/apps/api/migrations/0019_deletedmessage.py
index 4b028f0c..33746253 100644
--- a/pydis_site/apps/api/migrations/0019_deletedmessage.py
+++ b/pydis_site/apps/api/migrations/0019_deletedmessage.py
@@ -25,6 +25,6 @@ class Migration(migrations.Migration):
options={
'abstract': False,
},
- bases=(pydis_site.apps.api.models.ModelReprMixin, models.Model),
+ bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model),
),
]
diff --git a/pydis_site/apps/api/migrations/0020_infraction.py b/pydis_site/apps/api/migrations/0020_infraction.py
index 6bef6b77..96c71687 100644
--- a/pydis_site/apps/api/migrations/0020_infraction.py
+++ b/pydis_site/apps/api/migrations/0020_infraction.py
@@ -25,6 +25,6 @@ class Migration(migrations.Migration):
('actor', models.ForeignKey(help_text='The user which applied the infraction.', on_delete=django.db.models.deletion.CASCADE, related_name='infractions_given', to='api.User')),
('user', models.ForeignKey(help_text='The user to which the infraction was applied.', on_delete=django.db.models.deletion.CASCADE, related_name='infractions_received', to='api.User')),
],
- bases=(pydis_site.apps.api.models.ModelReprMixin, models.Model),
+ bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model),
),
]
diff --git a/pydis_site/apps/api/migrations/0025_allow_custom_inserted_at_infraction_field.py b/pydis_site/apps/api/migrations/0025_allow_custom_inserted_at_infraction_field.py
index 0c02cb91..c7fac012 100644
--- a/pydis_site/apps/api/migrations/0025_allow_custom_inserted_at_infraction_field.py
+++ b/pydis_site/apps/api/migrations/0025_allow_custom_inserted_at_infraction_field.py
@@ -1,7 +1,7 @@
# Generated by Django 2.1.4 on 2019-01-06 16:01
-import datetime
from django.db import migrations, models
+from django.utils import timezone
class Migration(migrations.Migration):
@@ -14,6 +14,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='infraction',
name='inserted_at',
- field=models.DateTimeField(default=datetime.datetime.utcnow, help_text='The date and time of the creation of this infraction.'),
+ field=models.DateTimeField(default=timezone.now, help_text='The date and time of the creation of this infraction.'),
),
]
diff --git a/pydis_site/apps/api/migrations/0030_reminder.py b/pydis_site/apps/api/migrations/0030_reminder.py
index 8c42f6dc..e1f1afc3 100644
--- a/pydis_site/apps/api/migrations/0030_reminder.py
+++ b/pydis_site/apps/api/migrations/0030_reminder.py
@@ -22,6 +22,6 @@ class Migration(migrations.Migration):
('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=(pydis_site.apps.api.models.ModelReprMixin, models.Model),
+ bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model),
),
]
diff --git a/pydis_site/apps/api/migrations/0031_nomination.py b/pydis_site/apps/api/migrations/0031_nomination.py
index 75e69701..f39436c1 100644
--- a/pydis_site/apps/api/migrations/0031_nomination.py
+++ b/pydis_site/apps/api/migrations/0031_nomination.py
@@ -21,6 +21,6 @@ class Migration(migrations.Migration):
('inserted_at', models.DateTimeField(auto_now_add=True, help_text='The creation date of this nomination.')),
('author', models.ForeignKey(help_text='The staff member that nominated this user.', on_delete=django.db.models.deletion.CASCADE, related_name='nomination_set', to='api.User')),
],
- bases=(pydis_site.apps.api.models.ModelReprMixin, models.Model),
+ bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model),
),
]
diff --git a/pydis_site/apps/api/migrations/0032_botsetting.py b/pydis_site/apps/api/migrations/0032_botsetting.py
index 25186a2b..3304edef 100644
--- a/pydis_site/apps/api/migrations/0032_botsetting.py
+++ b/pydis_site/apps/api/migrations/0032_botsetting.py
@@ -18,6 +18,6 @@ class Migration(migrations.Migration):
('name', models.CharField(max_length=50, primary_key=True, serialize=False)),
('data', django.contrib.postgres.fields.jsonb.JSONField(help_text='The actual settings of this setting.')),
],
- bases=(pydis_site.apps.api.models.ModelReprMixin, models.Model),
+ bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model),
),
]
diff --git a/pydis_site/apps/api/migrations/0035_create_table_log_entry.py b/pydis_site/apps/api/migrations/0035_create_table_log_entry.py
index a8256a0e..c9a1ad19 100644
--- a/pydis_site/apps/api/migrations/0035_create_table_log_entry.py
+++ b/pydis_site/apps/api/migrations/0035_create_table_log_entry.py
@@ -24,6 +24,6 @@ class Migration(migrations.Migration):
('line', models.PositiveSmallIntegerField(help_text='The line at which the log line was emitted.')),
('message', models.TextField(help_text='The textual content of the log line.')),
],
- bases=(pydis_site.apps.api.models.ModelReprMixin, models.Model),
+ bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model),
),
]
diff --git a/pydis_site/apps/api/migrations/0049_offensivemessage.py b/pydis_site/apps/api/migrations/0049_offensivemessage.py
index fe4a1961..f342cec3 100644
--- a/pydis_site/apps/api/migrations/0049_offensivemessage.py
+++ b/pydis_site/apps/api/migrations/0049_offensivemessage.py
@@ -3,7 +3,7 @@
import django.core.validators
from django.db import migrations, models
import pydis_site.apps.api.models.bot.offensive_message
-import pydis_site.apps.api.models.utils
+import pydis_site.apps.api.models.mixins
class Migration(migrations.Migration):
@@ -20,6 +20,6 @@ class Migration(migrations.Migration):
('channel_id', models.BigIntegerField(help_text='The channel ID that the message was sent in, taken from Discord.', validators=[django.core.validators.MinValueValidator(limit_value=0, message='Channel IDs cannot be negative.')])),
('delete_date', models.DateTimeField(help_text='The date on which the message will be auto-deleted.', validators=[pydis_site.apps.api.models.bot.offensive_message.future_date_validator])),
],
- bases=(pydis_site.apps.api.models.utils.ModelReprMixin, models.Model),
+ bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model),
),
]
diff --git a/pydis_site/apps/api/migrations/0051_allow_blank_message_embeds.py b/pydis_site/apps/api/migrations/0051_allow_blank_message_embeds.py
new file mode 100644
index 00000000..e617e1c9
--- /dev/null
+++ b/pydis_site/apps/api/migrations/0051_allow_blank_message_embeds.py
@@ -0,0 +1,21 @@
+# Generated by Django 3.0.4 on 2020-03-21 17:05
+
+import django.contrib.postgres.fields
+import django.contrib.postgres.fields.jsonb
+from django.db import migrations
+import pydis_site.apps.api.models.bot.tag
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0050_remove_infractions_active_default_value'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='deletedmessage',
+ name='embeds',
+ field=django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.jsonb.JSONField(validators=[pydis_site.apps.api.models.bot.tag.validate_tag_embed]), blank=True, help_text='Embeds attached to this message.', size=None),
+ ),
+ ]
diff --git a/pydis_site/apps/api/migrations/0052_remove_user_avatar_hash.py b/pydis_site/apps/api/migrations/0052_remove_user_avatar_hash.py
new file mode 100644
index 00000000..26b3b954
--- /dev/null
+++ b/pydis_site/apps/api/migrations/0052_remove_user_avatar_hash.py
@@ -0,0 +1,17 @@
+# Generated by Django 2.2.11 on 2020-05-27 07:17
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0051_create_news_setting'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='user',
+ name='avatar_hash',
+ ),
+ ]
diff --git a/pydis_site/apps/api/migrations/0053_user_roles_to_array.py b/pydis_site/apps/api/migrations/0053_user_roles_to_array.py
new file mode 100644
index 00000000..7ff3a548
--- /dev/null
+++ b/pydis_site/apps/api/migrations/0053_user_roles_to_array.py
@@ -0,0 +1,24 @@
+# Generated by Django 2.2.11 on 2020-06-02 13:42
+
+import django.contrib.postgres.fields
+import django.core.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0052_remove_user_avatar_hash'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='user',
+ name='roles',
+ ),
+ migrations.AddField(
+ model_name='user',
+ name='roles',
+ field=django.contrib.postgres.fields.ArrayField(base_field=models.BigIntegerField(validators=[django.core.validators.MinValueValidator(limit_value=0, message='Role IDs cannot be negative.')]), default=list, help_text='IDs of roles the user has on the server', size=None),
+ ),
+ ]
diff --git a/pydis_site/apps/api/migrations/0054_user_invalidate_unknown_role.py b/pydis_site/apps/api/migrations/0054_user_invalidate_unknown_role.py
new file mode 100644
index 00000000..96230015
--- /dev/null
+++ b/pydis_site/apps/api/migrations/0054_user_invalidate_unknown_role.py
@@ -0,0 +1,21 @@
+# Generated by Django 2.2.11 on 2020-06-02 20:08
+
+import django.contrib.postgres.fields
+import django.core.validators
+from django.db import migrations, models
+import pydis_site.apps.api.models.bot.user
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0053_user_roles_to_array'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='user',
+ name='roles',
+ field=django.contrib.postgres.fields.ArrayField(base_field=models.BigIntegerField(validators=[django.core.validators.MinValueValidator(limit_value=0, message='Role IDs cannot be negative.'), pydis_site.apps.api.models.bot.user._validate_existing_role]), default=list, help_text='IDs of roles the user has on the server', size=None),
+ ),
+ ]
diff --git a/pydis_site/apps/api/migrations/0055_merge_20200714_2027.py b/pydis_site/apps/api/migrations/0055_merge_20200714_2027.py
new file mode 100644
index 00000000..f2a0e638
--- /dev/null
+++ b/pydis_site/apps/api/migrations/0055_merge_20200714_2027.py
@@ -0,0 +1,14 @@
+# Generated by Django 3.0.8 on 2020-07-14 20:27
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0051_allow_blank_message_embeds'),
+ ('api', '0054_user_invalidate_unknown_role'),
+ ]
+
+ operations = [
+ ]
diff --git a/pydis_site/apps/api/migrations/0055_reminder_mentions.py b/pydis_site/apps/api/migrations/0055_reminder_mentions.py
new file mode 100644
index 00000000..d73b450d
--- /dev/null
+++ b/pydis_site/apps/api/migrations/0055_reminder_mentions.py
@@ -0,0 +1,20 @@
+# Generated by Django 2.2.14 on 2020-07-15 07:37
+
+import django.contrib.postgres.fields
+import django.core.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0054_user_invalidate_unknown_role'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='reminder',
+ name='mentions',
+ field=django.contrib.postgres.fields.ArrayField(base_field=models.BigIntegerField(validators=[django.core.validators.MinValueValidator(limit_value=0, message='Mention IDs cannot be negative.')]), blank=True, default=list, help_text='IDs of roles or users to ping with the reminder.', size=None),
+ ),
+ ]
diff --git a/pydis_site/apps/api/migrations/0056_allow_blank_user_roles.py b/pydis_site/apps/api/migrations/0056_allow_blank_user_roles.py
new file mode 100644
index 00000000..489941c7
--- /dev/null
+++ b/pydis_site/apps/api/migrations/0056_allow_blank_user_roles.py
@@ -0,0 +1,21 @@
+# Generated by Django 3.0.8 on 2020-07-14 20:35
+
+import django.contrib.postgres.fields
+import django.core.validators
+from django.db import migrations, models
+import pydis_site.apps.api.models.bot.user
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0055_merge_20200714_2027'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='user',
+ name='roles',
+ field=django.contrib.postgres.fields.ArrayField(base_field=models.BigIntegerField(validators=[django.core.validators.MinValueValidator(limit_value=0, message='Role IDs cannot be negative.'), pydis_site.apps.api.models.bot.user._validate_existing_role]), blank=True, default=list, help_text='IDs of roles the user has on the server', size=None),
+ ),
+ ]
diff --git a/pydis_site/apps/api/migrations/0057_merge_20200716_0751.py b/pydis_site/apps/api/migrations/0057_merge_20200716_0751.py
new file mode 100644
index 00000000..47a6d2d4
--- /dev/null
+++ b/pydis_site/apps/api/migrations/0057_merge_20200716_0751.py
@@ -0,0 +1,14 @@
+# Generated by Django 2.2.14 on 2020-07-16 07:51
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0055_reminder_mentions'),
+ ('api', '0056_allow_blank_user_roles'),
+ ]
+
+ operations = [
+ ]
diff --git a/pydis_site/apps/api/migrations/0058_create_new_filterlist_model.py b/pydis_site/apps/api/migrations/0058_create_new_filterlist_model.py
new file mode 100644
index 00000000..aecfdad7
--- /dev/null
+++ b/pydis_site/apps/api/migrations/0058_create_new_filterlist_model.py
@@ -0,0 +1,33 @@
+# Generated by Django 3.0.8 on 2020-07-15 11:23
+
+from django.db import migrations, models
+import pydis_site.apps.api.models.mixins
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('api', '0057_merge_20200716_0751'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='FilterList',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ('type', models.CharField(
+ choices=[('GUILD_INVITE', 'Guild Invite'), ('FILE_FORMAT', 'File Format'),
+ ('DOMAIN_NAME', 'Domain Name'), ('FILTER_TOKEN', 'Filter Token')],
+ help_text='The type of allowlist this is on.', max_length=50)),
+ ('allowed', models.BooleanField(help_text='Whether this item is on the allowlist or the denylist.')),
+ ('content', models.TextField(help_text='The data to add to the allow or denylist.')),
+ ('comment', models.TextField(help_text="Optional comment on this entry.", null=True)),
+ ],
+ bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model),
+ ),
+ migrations.AddConstraint(
+ model_name='filterlist',
+ constraint=models.UniqueConstraint(fields=('content', 'type'), name='unique_filter_list')
+ )
+ ]
diff --git a/pydis_site/apps/api/migrations/0059_populate_filterlists.py b/pydis_site/apps/api/migrations/0059_populate_filterlists.py
new file mode 100644
index 00000000..8c550191
--- /dev/null
+++ b/pydis_site/apps/api/migrations/0059_populate_filterlists.py
@@ -0,0 +1,153 @@
+from django.db import migrations
+
+guild_invite_whitelist = [
+ ("discord.gg/python", "Python Discord", True),
+ ("discord.gg/4JJdJKb", "RLBot", True),
+ ("discord.gg/djPtTRJ", "Kivy", True),
+ ("discord.gg/QXyegWe", "Pyglet", True),
+ ("discord.gg/9XsucTT", "Panda3D", True),
+ ("discord.gg/AP3rq2k", "PyWeek", True),
+ ("discord.gg/vSPsP9t", "Microsoft Python", True),
+ ("discord.gg/bRCvFy9", "Discord.js Official", True),
+ ("discord.gg/9zT7NHP", "Programming Discussions", True),
+ ("discord.gg/ysd6M4r", "JetBrains Community", True),
+ ("discord.gg/4xJeCgy", "Raspberry Pie", True),
+ ("discord.gg/AStb3kZ", "Ren'Py", True),
+ ("discord.gg/t655QNV", "Python Discord: Emojis 1", True),
+ ("discord.gg/vRZPkqC", "Python Discord: Emojis 2", True),
+ ("discord.gg/jTtgWuy", "Django", True),
+ ("discord.gg/W9BypZF", "STEM", True),
+ ("discord.gg/dpy", "discord.py", True),
+ ("discord.gg/programming", "Programmers Hangout", True),
+ ("discord.gg/qhGUjGD", "SpeakJS", True),
+ ("discord.gg/eTbWSZj", "Functional Programming", True),
+ ("discord.gg/r8yreB6", "PyGame", True),
+ ("discord.gg/5UBnR3P", "Python Atlanta", True),
+ ("discord.gg/ccyrDKv", "C#", True),
+]
+
+domain_name_blacklist = [
+ ("pornhub.com", None, False),
+ ("liveleak.com", None, False),
+ ("grabify.link", None, False),
+ ("bmwforum.co", None, False),
+ ("leancoding.co", None, False),
+ ("spottyfly.com", None, False),
+ ("stopify.co", None, False),
+ ("yoütu.be", None, False),
+ ("discörd.com", None, False),
+ ("minecräft.com", None, False),
+ ("freegiftcards.co", None, False),
+ ("disçordapp.com", None, False),
+ ("fortnight.space", None, False),
+ ("fortnitechat.site", None, False),
+ ("joinmy.site", None, False),
+ ("curiouscat.club", None, False),
+ ("catsnthings.fun", None, False),
+ ("yourtube.site", None, False),
+ ("youtubeshort.watch", None, False),
+ ("catsnthing.com", None, False),
+ ("youtubeshort.pro", None, False),
+ ("canadianlumberjacks.online", None, False),
+ ("poweredbydialup.club", None, False),
+ ("poweredbydialup.online", None, False),
+ ("poweredbysecurity.org", None, False),
+ ("poweredbysecurity.online", None, False),
+ ("ssteam.site", None, False),
+ ("steamwalletgift.com", None, False),
+ ("discord.gift", None, False),
+ ("lmgtfy.com", None, False),
+]
+
+filter_token_blacklist = [
+ ("\bgoo+ks*\b", None, False),
+ ("\bky+s+\b", None, False),
+ ("\bki+ke+s*\b", None, False),
+ ("\bbeaner+s?\b", None, False),
+ ("\bcoo+ns*\b", None, False),
+ ("\bnig+lets*\b", None, False),
+ ("\bslant-eyes*\b", None, False),
+ ("\btowe?l-?head+s*\b", None, False),
+ ("\bchi*n+k+s*\b", None, False),
+ ("\bspick*s*\b", None, False),
+ ("\bkill* +(?:yo)?urself+\b", None, False),
+ ("\bjew+s*\b", None, False),
+ ("\bsuicide\b", None, False),
+ ("\brape\b", None, False),
+ ("\b(re+)tar+(d+|t+)(ed)?\b", None, False),
+ ("\bta+r+d+\b", None, False),
+ ("\bcunts*\b", None, False),
+ ("\btrann*y\b", None, False),
+ ("\bshemale\b", None, False),
+ ("fa+g+s*", None, False),
+ ("卐", None, False),
+ ("卍", None, False),
+ ("࿖", None, False),
+ ("࿕", None, False),
+ ("࿘", None, False),
+ ("࿗", None, False),
+ ("cuck(?!oo+)", None, False),
+ ("nigg+(?:e*r+|a+h*?|u+h+)s?", None, False),
+ ("fag+o+t+s*", None, False),
+]
+
+file_format_whitelist = [
+ (".3gp", None, True),
+ (".3g2", None, True),
+ (".avi", None, True),
+ (".bmp", None, True),
+ (".gif", None, True),
+ (".h264", None, True),
+ (".jpg", None, True),
+ (".jpeg", None, True),
+ (".m4v", None, True),
+ (".mkv", None, True),
+ (".mov", None, True),
+ (".mp4", None, True),
+ (".mpeg", None, True),
+ (".mpg", None, True),
+ (".png", None, True),
+ (".tiff", None, True),
+ (".wmv", None, True),
+ (".svg", None, True),
+ (".psd", "Photoshop", True),
+ (".ai", "Illustrator", True),
+ (".aep", "After Effects", True),
+ (".xcf", "GIMP", True),
+ (".mp3", None, True),
+ (".wav", None, True),
+ (".ogg", None, True),
+ (".webm", None, True),
+ (".webp", None, True),
+]
+
+populate_data = {
+ "FILTER_TOKEN": filter_token_blacklist,
+ "DOMAIN_NAME": domain_name_blacklist,
+ "FILE_FORMAT": file_format_whitelist,
+ "GUILD_INVITE": guild_invite_whitelist,
+}
+
+
+class Migration(migrations.Migration):
+ dependencies = [("api", "0058_create_new_filterlist_model")]
+
+ def populate_filterlists(app, _):
+ FilterList = app.get_model("api", "FilterList")
+
+ for filterlist_type, metadata in populate_data.items():
+ for content, comment, allowed in metadata:
+ FilterList.objects.create(
+ type=filterlist_type,
+ allowed=allowed,
+ content=content,
+ comment=comment,
+ )
+
+ def clear_filterlists(app, _):
+ FilterList = app.get_model("api", "FilterList")
+ FilterList.objects.all().delete()
+
+ operations = [
+ migrations.RunPython(populate_filterlists, clear_filterlists)
+ ]
diff --git a/pydis_site/apps/api/migrations/0060_populate_filterlists_fix.py b/pydis_site/apps/api/migrations/0060_populate_filterlists_fix.py
new file mode 100644
index 00000000..53846f02
--- /dev/null
+++ b/pydis_site/apps/api/migrations/0060_populate_filterlists_fix.py
@@ -0,0 +1,85 @@
+from django.db import migrations
+
+bad_guild_invite_whitelist = [
+ ("discord.gg/python", "Python Discord", True),
+ ("discord.gg/4JJdJKb", "RLBot", True),
+ ("discord.gg/djPtTRJ", "Kivy", True),
+ ("discord.gg/QXyegWe", "Pyglet", True),
+ ("discord.gg/9XsucTT", "Panda3D", True),
+ ("discord.gg/AP3rq2k", "PyWeek", True),
+ ("discord.gg/vSPsP9t", "Microsoft Python", True),
+ ("discord.gg/bRCvFy9", "Discord.js Official", True),
+ ("discord.gg/9zT7NHP", "Programming Discussions", True),
+ ("discord.gg/ysd6M4r", "JetBrains Community", True),
+ ("discord.gg/4xJeCgy", "Raspberry Pie", True),
+ ("discord.gg/AStb3kZ", "Ren'Py", True),
+ ("discord.gg/t655QNV", "Python Discord: Emojis 1", True),
+ ("discord.gg/vRZPkqC", "Python Discord: Emojis 2", True),
+ ("discord.gg/jTtgWuy", "Django", True),
+ ("discord.gg/W9BypZF", "STEM", True),
+ ("discord.gg/dpy", "discord.py", True),
+ ("discord.gg/programming", "Programmers Hangout", True),
+ ("discord.gg/qhGUjGD", "SpeakJS", True),
+ ("discord.gg/eTbWSZj", "Functional Programming", True),
+ ("discord.gg/r8yreB6", "PyGame", True),
+ ("discord.gg/5UBnR3P", "Python Atlanta", True),
+ ("discord.gg/ccyrDKv", "C#", True),
+]
+
+guild_invite_whitelist = [
+ ("267624335836053506", "Python Discord", True),
+ ("348658686962696195", "RLBot", True),
+ ("423249981340778496", "Kivy", True),
+ ("438622377094414346", "Pyglet", True),
+ ("524691714909274162", "Panda3D", True),
+ ("666560367173828639", "PyWeek", True),
+ ("702724176489873509", "Microsoft Python", True),
+ ("222078108977594368", "Discord.js Official", True),
+ ("238666723824238602", "Programming Discussions", True),
+ ("433980600391696384", "JetBrains Community", True),
+ ("204621105720328193", "Raspberry Pie", True),
+ ("286633898581164032", "Ren'Py", True),
+ ("440186186024222721", "Python Discord: Emojis 1", True),
+ ("578587418123304970", "Python Discord: Emojis 2", True),
+ ("159039020565790721", "Django", True),
+ ("273944235143593984", "STEM", True),
+ ("336642139381301249", "discord.py", True),
+ ("244230771232079873", "Programmers Hangout", True),
+ ("239433591950540801", "SpeakJS", True),
+ ("280033776820813825", "Functional Programming", True),
+ ("349505959032389632", "PyGame", True),
+ ("488751051629920277", "Python Atlanta", True),
+ ("143867839282020352", "C#", True),
+]
+
+
+class Migration(migrations.Migration):
+ dependencies = [("api", "0059_populate_filterlists")]
+
+ def fix_filterlist(app, _):
+ FilterList = app.get_model("api", "FilterList")
+ FilterList.objects.filter(type="GUILD_INVITE").delete() # Clear out the stuff added in 0059.
+
+ for content, comment, allowed in guild_invite_whitelist:
+ FilterList.objects.create(
+ type="GUILD_INVITE",
+ allowed=allowed,
+ content=content,
+ comment=comment,
+ )
+
+ def restore_bad_filterlist(app, _):
+ FilterList = app.get_model("api", "FilterList")
+ FilterList.objects.filter(type="GUILD_INVITE").delete()
+
+ for content, comment, allowed in bad_guild_invite_whitelist:
+ FilterList.objects.create(
+ type="GUILD_INVITE",
+ allowed=allowed,
+ content=content,
+ comment=comment,
+ )
+
+ operations = [
+ migrations.RunPython(fix_filterlist, restore_bad_filterlist)
+ ]
diff --git a/pydis_site/apps/api/models/__init__.py b/pydis_site/apps/api/models/__init__.py
index 450d18cd..1d0ab7ea 100644
--- a/pydis_site/apps/api/models/__init__.py
+++ b/pydis_site/apps/api/models/__init__.py
@@ -1,5 +1,6 @@
# flake8: noqa
from .bot import (
+ FilterList,
BotSetting,
DocumentationLink,
DeletedMessage,
@@ -15,4 +16,3 @@ from .bot import (
User
)
from .log_entry import LogEntry
-from .utils import ModelReprMixin
diff --git a/pydis_site/apps/api/models/bot/__init__.py b/pydis_site/apps/api/models/bot/__init__.py
index 8ae47746..efd98184 100644
--- a/pydis_site/apps/api/models/bot/__init__.py
+++ b/pydis_site/apps/api/models/bot/__init__.py
@@ -1,4 +1,5 @@
# flake8: noqa
+from .filter_list import FilterList
from .bot_setting import BotSetting
from .deleted_message import DeletedMessage
from .documentation_link import DocumentationLink
diff --git a/pydis_site/apps/api/models/bot/bot_setting.py b/pydis_site/apps/api/models/bot/bot_setting.py
index 8d48eac7..2a3944f8 100644
--- a/pydis_site/apps/api/models/bot/bot_setting.py
+++ b/pydis_site/apps/api/models/bot/bot_setting.py
@@ -2,7 +2,7 @@ from django.contrib.postgres import fields as pgfields
from django.core.exceptions import ValidationError
from django.db import models
-from pydis_site.apps.api.models.utils import ModelReprMixin
+from pydis_site.apps.api.models.mixins import ModelReprMixin
def validate_bot_setting_name(name: str) -> None:
diff --git a/pydis_site/apps/api/models/bot/documentation_link.py b/pydis_site/apps/api/models/bot/documentation_link.py
index f844ae04..5a46460b 100644
--- a/pydis_site/apps/api/models/bot/documentation_link.py
+++ b/pydis_site/apps/api/models/bot/documentation_link.py
@@ -1,6 +1,6 @@
from django.db import models
-from pydis_site.apps.api.models.utils import ModelReprMixin
+from pydis_site.apps.api.models.mixins import ModelReprMixin
class DocumentationLink(ModelReprMixin, models.Model):
diff --git a/pydis_site/apps/api/models/bot/filter_list.py b/pydis_site/apps/api/models/bot/filter_list.py
new file mode 100644
index 00000000..d279e137
--- /dev/null
+++ b/pydis_site/apps/api/models/bot/filter_list.py
@@ -0,0 +1,41 @@
+from django.db import models
+
+from pydis_site.apps.api.models.mixins import ModelReprMixin, ModelTimestampMixin
+
+
+class FilterList(ModelTimestampMixin, ModelReprMixin, models.Model):
+ """An item that is either allowed or denied."""
+
+ FilterListType = models.TextChoices(
+ 'FilterListType',
+ 'GUILD_INVITE '
+ 'FILE_FORMAT '
+ 'DOMAIN_NAME '
+ 'FILTER_TOKEN '
+ )
+ type = models.CharField(
+ max_length=50,
+ help_text="The type of allowlist this is on.",
+ choices=FilterListType.choices,
+ )
+ allowed = models.BooleanField(
+ help_text="Whether this item is on the allowlist or the denylist."
+ )
+ content = models.TextField(
+ help_text="The data to add to the allow or denylist."
+ )
+ comment = models.TextField(
+ help_text="Optional comment on this entry.",
+ null=True
+ )
+
+ class Meta:
+ """Metaconfig for this model."""
+
+ # This constraint ensures only one filterlist with the
+ # same content can exist. This means that we cannot have both an allow
+ # and a deny for the same item, and we cannot have duplicates of the
+ # same item.
+ constraints = [
+ models.UniqueConstraint(fields=['content', 'type'], name='unique_filter_list'),
+ ]
diff --git a/pydis_site/apps/api/models/bot/infraction.py b/pydis_site/apps/api/models/bot/infraction.py
index f58e89a3..7660cbba 100644
--- a/pydis_site/apps/api/models/bot/infraction.py
+++ b/pydis_site/apps/api/models/bot/infraction.py
@@ -2,7 +2,7 @@ from django.db import models
from django.utils import timezone
from pydis_site.apps.api.models.bot.user import User
-from pydis_site.apps.api.models.utils import ModelReprMixin
+from pydis_site.apps.api.models.mixins import ModelReprMixin
class Infraction(ModelReprMixin, models.Model):
diff --git a/pydis_site/apps/api/models/bot/message.py b/pydis_site/apps/api/models/bot/message.py
index 8b18fc9f..78dcbf1d 100644
--- a/pydis_site/apps/api/models/bot/message.py
+++ b/pydis_site/apps/api/models/bot/message.py
@@ -7,7 +7,7 @@ from django.utils import timezone
from pydis_site.apps.api.models.bot.tag import validate_tag_embed
from pydis_site.apps.api.models.bot.user import User
-from pydis_site.apps.api.models.utils import ModelReprMixin
+from pydis_site.apps.api.models.mixins import ModelReprMixin
class Message(ModelReprMixin, models.Model):
@@ -49,6 +49,7 @@ class Message(ModelReprMixin, models.Model):
pgfields.JSONField(
validators=(validate_tag_embed,)
),
+ blank=True,
help_text="Embeds attached to this message."
)
attachments = pgfields.ArrayField(
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 44a0c8ae..04ae8d34 100644
--- a/pydis_site/apps/api/models/bot/message_deletion_context.py
+++ b/pydis_site/apps/api/models/bot/message_deletion_context.py
@@ -1,7 +1,7 @@
from django.db import models
from pydis_site.apps.api.models.bot.user import User
-from pydis_site.apps.api.models.utils import ModelReprMixin
+from pydis_site.apps.api.models.mixins import ModelReprMixin
class MessageDeletionContext(ModelReprMixin, models.Model):
diff --git a/pydis_site/apps/api/models/bot/nomination.py b/pydis_site/apps/api/models/bot/nomination.py
index cd9951aa..21e34e87 100644
--- a/pydis_site/apps/api/models/bot/nomination.py
+++ b/pydis_site/apps/api/models/bot/nomination.py
@@ -1,7 +1,7 @@
from django.db import models
from pydis_site.apps.api.models.bot.user import User
-from pydis_site.apps.api.models.utils import ModelReprMixin
+from pydis_site.apps.api.models.mixins import ModelReprMixin
class Nomination(ModelReprMixin, models.Model):
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 413cbfae..403c7465 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
@@ -1,7 +1,7 @@
from django.core.validators import RegexValidator
from django.db import models
-from pydis_site.apps.api.models.utils import ModelReprMixin
+from pydis_site.apps.api.models.mixins import ModelReprMixin
class OffTopicChannelName(ModelReprMixin, models.Model):
diff --git a/pydis_site/apps/api/models/bot/offensive_message.py b/pydis_site/apps/api/models/bot/offensive_message.py
index b466d9c2..6c0e5ffb 100644
--- a/pydis_site/apps/api/models/bot/offensive_message.py
+++ b/pydis_site/apps/api/models/bot/offensive_message.py
@@ -4,7 +4,7 @@ from django.core.exceptions import ValidationError
from django.core.validators import MinValueValidator
from django.db import models
-from pydis_site.apps.api.models.utils import ModelReprMixin
+from pydis_site.apps.api.models.mixins import ModelReprMixin
def future_date_validator(date: datetime.date) -> None:
diff --git a/pydis_site/apps/api/models/bot/reminder.py b/pydis_site/apps/api/models/bot/reminder.py
index d53fedb5..7d968a0e 100644
--- a/pydis_site/apps/api/models/bot/reminder.py
+++ b/pydis_site/apps/api/models/bot/reminder.py
@@ -1,8 +1,9 @@
+from django.contrib.postgres.fields import ArrayField
from django.core.validators import MinValueValidator
from django.db import models
from pydis_site.apps.api.models.bot.user import User
-from pydis_site.apps.api.models.utils import ModelReprMixin
+from pydis_site.apps.api.models.mixins import ModelReprMixin
class Reminder(ModelReprMixin, models.Model):
@@ -45,6 +46,19 @@ class Reminder(ModelReprMixin, models.Model):
expiration = models.DateTimeField(
help_text="When this reminder should be sent."
)
+ mentions = ArrayField(
+ models.BigIntegerField(
+ validators=(
+ MinValueValidator(
+ limit_value=0,
+ message="Mention IDs cannot be negative."
+ ),
+ )
+ ),
+ default=list,
+ blank=True,
+ help_text="IDs of roles or users to ping with the reminder."
+ )
def __str__(self):
"""Returns some info on the current reminder, for display purposes."""
diff --git a/pydis_site/apps/api/models/bot/role.py b/pydis_site/apps/api/models/bot/role.py
index 58bbf8b4..721e4815 100644
--- a/pydis_site/apps/api/models/bot/role.py
+++ b/pydis_site/apps/api/models/bot/role.py
@@ -3,7 +3,7 @@ from __future__ import annotations
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
-from pydis_site.apps.api.models.utils import ModelReprMixin
+from pydis_site.apps.api.models.mixins import ModelReprMixin
class Role(ModelReprMixin, models.Model):
diff --git a/pydis_site/apps/api/models/bot/tag.py b/pydis_site/apps/api/models/bot/tag.py
index 5d4cc393..5e53582f 100644
--- a/pydis_site/apps/api/models/bot/tag.py
+++ b/pydis_site/apps/api/models/bot/tag.py
@@ -6,7 +6,7 @@ from django.core.exceptions import ValidationError
from django.core.validators import MaxLengthValidator, MinLengthValidator
from django.db import models
-from pydis_site.apps.api.models.utils import ModelReprMixin
+from pydis_site.apps.api.models.mixins import ModelReprMixin
def is_bool_validator(value: Any) -> None:
diff --git a/pydis_site/apps/api/models/bot/user.py b/pydis_site/apps/api/models/bot/user.py
index 5140d2bf..cd2d58b9 100644
--- a/pydis_site/apps/api/models/bot/user.py
+++ b/pydis_site/apps/api/models/bot/user.py
@@ -1,8 +1,18 @@
+from django.contrib.postgres.fields import ArrayField
+from django.core.exceptions import ValidationError
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from pydis_site.apps.api.models.bot.role import Role
-from pydis_site.apps.api.models.utils import ModelReprMixin
+from pydis_site.apps.api.models.mixins import ModelReprMixin
+
+
+def _validate_existing_role(value: int) -> None:
+ """Validate that a role exists when given in to the user model."""
+ role = Role.objects.filter(id=value)
+
+ if not role:
+ raise ValidationError(f"Role with ID {value} does not exist")
class User(ModelReprMixin, models.Model):
@@ -31,17 +41,19 @@ class User(ModelReprMixin, models.Model):
),
help_text="The discriminator of this user, taken from Discord."
)
- avatar_hash = models.CharField(
- max_length=100,
- help_text=(
- "The user's avatar hash, taken from Discord. "
- "Null if the user does not have any custom avatar."
+ roles = ArrayField(
+ models.BigIntegerField(
+ validators=(
+ MinValueValidator(
+ limit_value=0,
+ message="Role IDs cannot be negative."
+ ),
+ _validate_existing_role
+ )
),
- null=True
- )
- roles = models.ManyToManyField(
- Role,
- help_text="Any roles this user has on our server."
+ default=list,
+ blank=True,
+ help_text="IDs of roles the user has on the server"
)
in_guild = models.BooleanField(
default=True,
@@ -59,7 +71,7 @@ class User(ModelReprMixin, models.Model):
This will fall back to the Developers role if the user does not have any roles.
"""
- roles = self.roles.all()
+ roles = Role.objects.filter(id__in=self.roles)
if not roles:
return Role.objects.get(name="Developers")
- return max(self.roles.all())
+ return max(roles)
diff --git a/pydis_site/apps/api/models/log_entry.py b/pydis_site/apps/api/models/log_entry.py
index 488af48e..752cd2ca 100644
--- a/pydis_site/apps/api/models/log_entry.py
+++ b/pydis_site/apps/api/models/log_entry.py
@@ -1,7 +1,7 @@
from django.db import models
from django.utils import timezone
-from pydis_site.apps.api.models.utils import ModelReprMixin
+from pydis_site.apps.api.models.mixins import ModelReprMixin
class LogEntry(ModelReprMixin, models.Model):
diff --git a/pydis_site/apps/api/models/utils.py b/pydis_site/apps/api/models/mixins.py
index 0540c4de..5d75b78b 100644
--- a/pydis_site/apps/api/models/utils.py
+++ b/pydis_site/apps/api/models/mixins.py
@@ -1,5 +1,7 @@
from operator import itemgetter
+from django.db import models
+
class ModelReprMixin:
"""Mixin providing a `__repr__()` to display model class name and initialisation parameters."""
@@ -15,3 +17,15 @@ class ModelReprMixin:
if not attribute.startswith('_')
)
return f'<{self.__class__.__name__}({attributes})>'
+
+
+class ModelTimestampMixin(models.Model):
+ """Mixin providing created_at and updated_at fields."""
+
+ created_at = models.DateTimeField(auto_now_add=True)
+ updated_at = models.DateTimeField(auto_now=True)
+
+ class Meta:
+ """Metaconfig for the mixin."""
+
+ abstract = True
diff --git a/pydis_site/apps/api/serializers.py b/pydis_site/apps/api/serializers.py
index e11c4af2..52e0d972 100644
--- a/pydis_site/apps/api/serializers.py
+++ b/pydis_site/apps/api/serializers.py
@@ -4,13 +4,20 @@ from rest_framework.validators import UniqueTogetherValidator
from rest_framework_bulk import BulkSerializerMixin
from .models import (
- BotSetting, DeletedMessage,
- DocumentationLink, Infraction,
- LogEntry, MessageDeletionContext,
- Nomination, OffTopicChannelName,
+ BotSetting,
+ DeletedMessage,
+ DocumentationLink,
+ FilterList,
+ Infraction,
+ LogEntry,
+ MessageDeletionContext,
+ Nomination,
+ OffTopicChannelName,
OffensiveMessage,
- Reminder, Role,
- Tag, User
+ Reminder,
+ Role,
+ Tag,
+ User
)
@@ -97,6 +104,31 @@ class DocumentationLinkSerializer(ModelSerializer):
fields = ('package', 'base_url', 'inventory_url')
+class FilterListSerializer(ModelSerializer):
+ """A class providing (de-)serialization of `FilterList` instances."""
+
+ class Meta:
+ """Metadata defined for the Django REST Framework."""
+
+ model = FilterList
+ fields = ('id', 'created_at', 'updated_at', 'type', 'allowed', 'content', 'comment')
+
+ # This validator ensures only one filterlist with the
+ # same content can exist. This means that we cannot have both an allow
+ # and a deny for the same item, and we cannot have duplicates of the
+ # same item.
+ validators = [
+ UniqueTogetherValidator(
+ queryset=FilterList.objects.all(),
+ fields=['content', 'type'],
+ message=(
+ "A filterlist for this item already exists. "
+ "Please note that you cannot add the same item to both allow and deny."
+ )
+ ),
+ ]
+
+
class InfractionSerializer(ModelSerializer):
"""A class providing (de-)serialization of `Infraction` instances."""
@@ -203,7 +235,9 @@ class ReminderSerializer(ModelSerializer):
"""Metadata defined for the Django REST Framework."""
model = Reminder
- fields = ('active', 'author', 'jump_url', 'channel_id', 'content', 'expiration', 'id')
+ fields = (
+ 'active', 'author', 'jump_url', 'channel_id', 'content', 'expiration', 'id', 'mentions'
+ )
class RoleSerializer(ModelSerializer):
@@ -229,13 +263,11 @@ class TagSerializer(ModelSerializer):
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')
+ fields = ('id', 'name', 'discriminator', 'roles', 'in_guild')
depth = 1
diff --git a/pydis_site/apps/api/tests/test_deleted_messages.py b/pydis_site/apps/api/tests/test_deleted_messages.py
index fb93cae6..f079a8dd 100644
--- a/pydis_site/apps/api/tests/test_deleted_messages.py
+++ b/pydis_site/apps/api/tests/test_deleted_messages.py
@@ -13,7 +13,6 @@ class DeletedMessagesWithoutActorTests(APISubdomainTestCase):
id=55,
name='Robbie Rotten',
discriminator=55,
- avatar_hash=None
)
cls.data = {
@@ -54,7 +53,6 @@ class DeletedMessagesWithActorTests(APISubdomainTestCase):
id=12904,
name='Joe Armstrong',
discriminator=1245,
- avatar_hash=None
)
cls.data = {
diff --git a/pydis_site/apps/api/tests/test_filterlists.py b/pydis_site/apps/api/tests/test_filterlists.py
new file mode 100644
index 00000000..188c0fff
--- /dev/null
+++ b/pydis_site/apps/api/tests/test_filterlists.py
@@ -0,0 +1,122 @@
+from django_hosts.resolvers import reverse
+
+from pydis_site.apps.api.models import FilterList
+from pydis_site.apps.api.tests.base import APISubdomainTestCase
+
+URL = reverse('bot:filterlist-list', host='api')
+JPEG_ALLOWLIST = {
+ "type": 'FILE_FORMAT',
+ "allowed": True,
+ "content": ".jpeg",
+}
+PNG_ALLOWLIST = {
+ "type": 'FILE_FORMAT',
+ "allowed": True,
+ "content": ".png",
+}
+
+
+class UnauthenticatedTests(APISubdomainTestCase):
+ def setUp(self):
+ super().setUp()
+ self.client.force_authenticate(user=None)
+
+ def test_cannot_read_allowedlist_list(self):
+ response = self.client.get(URL)
+
+ self.assertEqual(response.status_code, 401)
+
+
+class EmptyDatabaseTests(APISubdomainTestCase):
+ @classmethod
+ def setUpTestData(cls):
+ FilterList.objects.all().delete()
+
+ def test_returns_empty_object(self):
+ response = self.client.get(URL)
+
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.json(), [])
+
+
+class FetchTests(APISubdomainTestCase):
+ @classmethod
+ def setUpTestData(cls):
+ FilterList.objects.all().delete()
+ cls.jpeg_format = FilterList.objects.create(**JPEG_ALLOWLIST)
+ cls.png_format = FilterList.objects.create(**PNG_ALLOWLIST)
+
+ def test_returns_name_in_list(self):
+ response = self.client.get(URL)
+
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.json()[0]["content"], self.jpeg_format.content)
+ self.assertEqual(response.json()[1]["content"], self.png_format.content)
+
+ def test_returns_single_item_by_id(self):
+ response = self.client.get(f'{URL}/{self.jpeg_format.id}')
+
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.json().get("content"), self.jpeg_format.content)
+
+ def test_returns_filter_list_types(self):
+ response = self.client.get(f'{URL}/get-types')
+
+ self.assertEqual(response.status_code, 200)
+ for api_type, model_type in zip(response.json(), FilterList.FilterListType.choices):
+ self.assertEquals(api_type[0], model_type[0])
+ self.assertEquals(api_type[1], model_type[1])
+
+
+class CreationTests(APISubdomainTestCase):
+ @classmethod
+ def setUpTestData(cls):
+ FilterList.objects.all().delete()
+
+ def test_returns_400_for_missing_params(self):
+ no_type_json = {
+ "allowed": True,
+ "content": ".jpeg"
+ }
+ no_allowed_json = {
+ "type": "FILE_FORMAT",
+ "content": ".jpeg"
+ }
+ no_content_json = {
+ "allowed": True,
+ "type": "FILE_FORMAT"
+ }
+ cases = [{}, no_type_json, no_allowed_json, no_content_json]
+
+ for case in cases:
+ with self.subTest(case=case):
+ response = self.client.post(URL, data=case)
+ self.assertEqual(response.status_code, 400)
+
+ def test_returns_201_for_successful_creation(self):
+ response = self.client.post(URL, data=JPEG_ALLOWLIST)
+ self.assertEqual(response.status_code, 201)
+
+ def test_returns_400_for_duplicate_creation(self):
+ self.client.post(URL, data=JPEG_ALLOWLIST)
+ response = self.client.post(URL, data=JPEG_ALLOWLIST)
+ self.assertEqual(response.status_code, 400)
+
+
+class DeletionTests(APISubdomainTestCase):
+ @classmethod
+ def setUpTestData(cls):
+ FilterList.objects.all().delete()
+ cls.jpeg_format = FilterList.objects.create(**JPEG_ALLOWLIST)
+ cls.png_format = FilterList.objects.create(**PNG_ALLOWLIST)
+
+ def test_deleting_unknown_id_returns_404(self):
+ response = self.client.delete(f"{URL}/200")
+ self.assertEqual(response.status_code, 404)
+
+ def test_deleting_known_id_returns_204(self):
+ response = self.client.delete(f"{URL}/{self.jpeg_format.id}")
+ self.assertEqual(response.status_code, 204)
+
+ response = self.client.get(f"{URL}/{self.jpeg_format.id}")
+ self.assertNotIn(self.png_format.content, response.json())
diff --git a/pydis_site/apps/api/tests/test_infractions.py b/pydis_site/apps/api/tests/test_infractions.py
index bc258b77..93ef8171 100644
--- a/pydis_site/apps/api/tests/test_infractions.py
+++ b/pydis_site/apps/api/tests/test_infractions.py
@@ -47,7 +47,6 @@ class InfractionTests(APISubdomainTestCase):
id=5,
name='james',
discriminator=1,
- avatar_hash=None
)
cls.ban_hidden = Infraction.objects.create(
user_id=cls.user.id,
@@ -169,13 +168,11 @@ class CreationTests(APISubdomainTestCase):
id=5,
name='james',
discriminator=1,
- avatar_hash=None
)
cls.second_user = User.objects.create(
id=6,
name='carl',
discriminator=2,
- avatar_hash=None
)
def test_accepts_valid_data(self):
@@ -522,7 +519,6 @@ class ExpandedTests(APISubdomainTestCase):
id=5,
name='james',
discriminator=1,
- avatar_hash=None
)
cls.kick = Infraction.objects.create(
user_id=cls.user.id,
@@ -540,7 +536,7 @@ class ExpandedTests(APISubdomainTestCase):
def check_expanded_fields(self, infraction):
for key in ('user', 'actor'):
obj = infraction[key]
- for field in ('id', 'name', 'discriminator', 'avatar_hash', 'roles', 'in_guild'):
+ for field in ('id', 'name', 'discriminator', 'roles', 'in_guild'):
self.assertTrue(field in obj, msg=f'field "{field}" missing from {key}')
def test_list_expanded(self):
@@ -599,7 +595,6 @@ class SerializerTests(APISubdomainTestCase):
id=5,
name='james',
discriminator=1,
- avatar_hash=None
)
def create_infraction(self, _type: str, active: bool):
diff --git a/pydis_site/apps/api/tests/test_models.py b/pydis_site/apps/api/tests/test_models.py
index a97d3251..e0e347bb 100644
--- a/pydis_site/apps/api/tests/test_models.py
+++ b/pydis_site/apps/api/tests/test_models.py
@@ -3,13 +3,12 @@ from datetime import datetime as dt
from django.test import SimpleTestCase
from django.utils import timezone
-from ..models import (
+from pydis_site.apps.api.models import (
DeletedMessage,
DocumentationLink,
Infraction,
Message,
MessageDeletionContext,
- ModelReprMixin,
Nomination,
OffTopicChannelName,
OffensiveMessage,
@@ -18,6 +17,7 @@ from ..models import (
Tag,
User
)
+from pydis_site.apps.api.models.mixins import ModelReprMixin
class SimpleClass(ModelReprMixin):
@@ -39,12 +39,14 @@ class StringDunderMethodTests(SimpleTestCase):
self.nomination = Nomination(
id=123,
actor=User(
- id=9876, name='Mr. Hemlock',
- discriminator=6666, avatar_hash=None
+ id=9876,
+ name='Mr. Hemlock',
+ discriminator=6666,
),
user=User(
- id=9876, name="Hemlock's Cat",
- discriminator=7777, avatar_hash=None
+ id=9876,
+ name="Hemlock's Cat",
+ discriminator=7777,
),
reason="He purrrrs like the best!",
)
@@ -53,15 +55,17 @@ class StringDunderMethodTests(SimpleTestCase):
DeletedMessage(
id=45,
author=User(
- id=444, name='bill',
- discriminator=5, avatar_hash=None
+ id=444,
+ name='bill',
+ discriminator=5,
),
channel_id=666,
content="wooey",
deletion_context=MessageDeletionContext(
actor=User(
- id=5555, name='shawn',
- discriminator=555, avatar_hash=None
+ id=5555,
+ name='shawn',
+ discriminator=555,
),
creation=dt.utcnow()
),
@@ -84,8 +88,9 @@ class StringDunderMethodTests(SimpleTestCase):
Message(
id=45,
author=User(
- id=444, name='bill',
- discriminator=5, avatar_hash=None
+ id=444,
+ name='bill',
+ discriminator=5,
),
channel_id=666,
content="wooey",
@@ -93,8 +98,9 @@ class StringDunderMethodTests(SimpleTestCase):
),
MessageDeletionContext(
actor=User(
- id=5555, name='shawn',
- discriminator=555, avatar_hash=None
+ id=5555,
+ name='shawn',
+ discriminator=555,
),
creation=dt.utcnow()
),
@@ -103,22 +109,29 @@ class StringDunderMethodTests(SimpleTestCase):
embed={'content': "the builder"}
),
User(
- id=5, name='bob',
- discriminator=1, avatar_hash=None
+ id=5,
+ name='bob',
+ discriminator=1,
),
Infraction(
- user_id=5, actor_id=5,
- type='kick', reason='He terk my jerb!'
+ user_id=5,
+ actor_id=5,
+ type='kick',
+ reason='He terk my jerb!'
),
Infraction(
- user_id=5, actor_id=5, hidden=True,
- type='kick', reason='He terk my jerb!',
+ 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
+ id=452,
+ name='billy',
+ discriminator=5,
),
channel_id=555,
jump_url=(
diff --git a/pydis_site/apps/api/tests/test_nominations.py b/pydis_site/apps/api/tests/test_nominations.py
index 76cb4112..92c62c87 100644
--- a/pydis_site/apps/api/tests/test_nominations.py
+++ b/pydis_site/apps/api/tests/test_nominations.py
@@ -13,7 +13,6 @@ class CreationTests(APISubdomainTestCase):
id=1234,
name='joe dart',
discriminator=1111,
- avatar_hash=None
)
def test_accepts_valid_data(self):
@@ -190,7 +189,6 @@ class NominationTests(APISubdomainTestCase):
id=1234,
name='joe dart',
discriminator=1111,
- avatar_hash=None
)
cls.active_nomination = Nomination.objects.create(
diff --git a/pydis_site/apps/api/tests/test_reminders.py b/pydis_site/apps/api/tests/test_reminders.py
index 3441e0cc..9dffb668 100644
--- a/pydis_site/apps/api/tests/test_reminders.py
+++ b/pydis_site/apps/api/tests/test_reminders.py
@@ -53,7 +53,6 @@ class ReminderCreationTests(APISubdomainTestCase):
id=1234,
name='Mermaid Man',
discriminator=1234,
- avatar_hash=None,
)
def test_accepts_valid_data(self):
@@ -63,6 +62,7 @@ class ReminderCreationTests(APISubdomainTestCase):
'expiration': datetime.utcnow().isoformat(),
'jump_url': "https://www.google.com",
'channel_id': 123,
+ 'mentions': [8888, 9999],
}
url = reverse('bot:reminder-list', host='api')
response = self.client.post(url, data=data)
@@ -86,7 +86,6 @@ class ReminderDeletionTests(APISubdomainTestCase):
id=6789,
name='Barnacle Boy',
discriminator=6789,
- avatar_hash=None,
)
cls.reminder = Reminder.objects.create(
@@ -118,7 +117,6 @@ class ReminderListTests(APISubdomainTestCase):
id=6789,
name='Patrick Star',
discriminator=6789,
- avatar_hash=None,
)
cls.reminder_one = Reminder.objects.create(
@@ -165,6 +163,34 @@ class ReminderListTests(APISubdomainTestCase):
self.assertEqual(response.json(), [self.rem_dict_one])
+class ReminderRetrieveTests(APISubdomainTestCase):
+ @classmethod
+ def setUpTestData(cls):
+ cls.author = User.objects.create(
+ id=6789,
+ name='Reminder author',
+ discriminator=6789,
+ )
+
+ cls.reminder = Reminder.objects.create(
+ author=cls.author,
+ content="Reminder content",
+ expiration=datetime.utcnow().isoformat(),
+ jump_url="http://example.com/",
+ channel_id=123
+ )
+
+ def test_retrieve_unknown_returns_404(self):
+ url = reverse('bot:reminder-detail', args=("not_an_id",), host='api')
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 404)
+
+ def test_retrieve_known_returns_200(self):
+ url = reverse('bot:reminder-detail', args=(self.reminder.id,), host='api')
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 200)
+
+
class ReminderUpdateTests(APISubdomainTestCase):
@classmethod
def setUpTestData(cls):
@@ -172,7 +198,6 @@ class ReminderUpdateTests(APISubdomainTestCase):
id=666,
name='Man Ray',
discriminator=666,
- avatar_hash=None,
)
cls.reminder = Reminder.objects.create(
diff --git a/pydis_site/apps/api/tests/test_users.py b/pydis_site/apps/api/tests/test_users.py
index 86799f19..4c0f6e27 100644
--- a/pydis_site/apps/api/tests/test_users.py
+++ b/pydis_site/apps/api/tests/test_users.py
@@ -49,7 +49,6 @@ class CreationTests(APISubdomainTestCase):
url = reverse('bot:user-list', host='api')
data = {
'id': 42,
- 'avatar_hash': "validavatarhashiswear",
'name': "Test",
'discriminator': 42,
'roles': [
@@ -63,7 +62,6 @@ class CreationTests(APISubdomainTestCase):
self.assertEqual(response.json(), data)
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'])
@@ -73,7 +71,6 @@ class CreationTests(APISubdomainTestCase):
data = [
{
'id': 5,
- 'avatar_hash': "hahayes",
'name': "test man",
'discriminator': 42,
'roles': [
@@ -83,7 +80,6 @@ class CreationTests(APISubdomainTestCase):
},
{
'id': 8,
- 'avatar_hash': "maybenot",
'name': "another test man",
'discriminator': 555,
'roles': [],
@@ -99,7 +95,6 @@ class CreationTests(APISubdomainTestCase):
url = reverse('bot:user-list', host='api')
data = {
'id': 5,
- 'avatar_hash': "hahayes",
'name': "test man",
'discriminator': 42,
'roles': [
@@ -114,7 +109,6 @@ class CreationTests(APISubdomainTestCase):
url = reverse('bot:user-list', host='api')
data = {
'id': True,
- 'avatar_hash': 1902831,
'discriminator': "totally!"
}
@@ -148,16 +142,14 @@ class UserModelTests(APISubdomainTestCase):
)
cls.user_with_roles = User.objects.create(
id=1,
- avatar_hash="coolavatarhash",
name="Test User with two roles",
discriminator=1111,
in_guild=True,
)
- cls.user_with_roles.roles.add(cls.role_bottom, cls.role_top)
+ cls.user_with_roles.roles.extend([cls.role_bottom.id, cls.role_top.id])
cls.user_without_roles = User.objects.create(
id=2,
- avatar_hash="coolavatarhash",
name="Test User without roles",
discriminator=2222,
in_guild=True,
diff --git a/pydis_site/apps/api/urls.py b/pydis_site/apps/api/urls.py
index 4a0281b4..a4fd5b2e 100644
--- a/pydis_site/apps/api/urls.py
+++ b/pydis_site/apps/api/urls.py
@@ -3,18 +3,28 @@ from rest_framework.routers import DefaultRouter
from .views import HealthcheckView, RulesView
from .viewsets import (
- BotSettingViewSet, DeletedMessageViewSet,
- DocumentationLinkViewSet, InfractionViewSet,
- LogEntryViewSet, NominationViewSet,
+ BotSettingViewSet,
+ DeletedMessageViewSet,
+ DocumentationLinkViewSet,
+ FilterListViewSet,
+ InfractionViewSet,
+ LogEntryViewSet,
+ NominationViewSet,
OffTopicChannelNameViewSet,
- OffensiveMessageViewSet, ReminderViewSet,
- RoleViewSet, TagViewSet, UserViewSet
+ OffensiveMessageViewSet,
+ ReminderViewSet,
+ RoleViewSet,
+ TagViewSet,
+ UserViewSet
)
-
# http://www.django-rest-framework.org/api-guide/routers/#defaultrouter
bot_router = DefaultRouter(trailing_slash=False)
bot_router.register(
+ 'filter-lists',
+ FilterListViewSet
+)
+bot_router.register(
'bot-settings',
BotSettingViewSet
)
@@ -41,7 +51,7 @@ bot_router.register(
bot_router.register(
'off-topic-channel-names',
OffTopicChannelNameViewSet,
- base_name='offtopicchannelname'
+ basename='offtopicchannelname'
)
bot_router.register(
'reminders',
diff --git a/pydis_site/apps/api/views.py b/pydis_site/apps/api/views.py
index a73d4718..7ac56641 100644
--- a/pydis_site/apps/api/views.py
+++ b/pydis_site/apps/api/views.py
@@ -140,7 +140,8 @@ class RulesView(APIView):
),
(
"No spamming or unapproved advertising, including requests for paid work. "
- "Open-source projects can be showcased in #show-your-projects."
+ "Open-source projects can be shared with others in #python-general and "
+ "code reviews can be asked for in a help channel."
),
(
"Keep discussions relevant to channel topics and guidelines."
diff --git a/pydis_site/apps/api/viewsets/__init__.py b/pydis_site/apps/api/viewsets/__init__.py
index 3cf9f641..8699517e 100644
--- a/pydis_site/apps/api/viewsets/__init__.py
+++ b/pydis_site/apps/api/viewsets/__init__.py
@@ -1,5 +1,6 @@
# flake8: noqa
from .bot import (
+ FilterListViewSet,
BotSettingViewSet,
DeletedMessageViewSet,
DocumentationLinkViewSet,
diff --git a/pydis_site/apps/api/viewsets/bot/__init__.py b/pydis_site/apps/api/viewsets/bot/__init__.py
index b3e0fa4d..e64e3988 100644
--- a/pydis_site/apps/api/viewsets/bot/__init__.py
+++ b/pydis_site/apps/api/viewsets/bot/__init__.py
@@ -1,4 +1,5 @@
# flake8: noqa
+from .filter_list import FilterListViewSet
from .bot_setting import BotSettingViewSet
from .deleted_message import DeletedMessageViewSet
from .documentation_link import DocumentationLinkViewSet
diff --git a/pydis_site/apps/api/viewsets/bot/filter_list.py b/pydis_site/apps/api/viewsets/bot/filter_list.py
new file mode 100644
index 00000000..2cb21ab9
--- /dev/null
+++ b/pydis_site/apps/api/viewsets/bot/filter_list.py
@@ -0,0 +1,97 @@
+from rest_framework.decorators import action
+from rest_framework.request import Request
+from rest_framework.response import Response
+from rest_framework.viewsets import ModelViewSet
+
+from pydis_site.apps.api.models.bot.filter_list import FilterList
+from pydis_site.apps.api.serializers import FilterListSerializer
+
+
+class FilterListViewSet(ModelViewSet):
+ """
+ View providing CRUD operations on items allowed or denied by our bot.
+
+ ## Routes
+ ### GET /bot/filter-lists
+ Returns all filterlist items in the database.
+
+ #### Response format
+ >>> [
+ ... {
+ ... 'id': "2309268224",
+ ... 'created_at': "01-01-2020 ...",
+ ... 'updated_at': "01-01-2020 ...",
+ ... 'type': "file_format",
+ ... 'allowed': 'true',
+ ... 'content': ".jpeg",
+ ... 'comment': "Popular image format.",
+ ... },
+ ... ...
+ ... ]
+
+ #### Status codes
+ - 200: returned on success
+ - 401: returned if unauthenticated
+
+ ### GET /bot/filter-lists/<id:int>
+ Returns a specific FilterList item from the database.
+
+ #### Response format
+ >>> {
+ ... 'id': "2309268224",
+ ... 'created_at': "01-01-2020 ...",
+ ... 'updated_at': "01-01-2020 ...",
+ ... 'type': "file_format",
+ ... 'allowed': 'true',
+ ... 'content': ".jpeg",
+ ... 'comment': "Popular image format.",
+ ... }
+
+ #### Status codes
+ - 200: returned on success
+ - 404: returned if the id was not found.
+
+ ### GET /bot/filter-lists/get-types
+ Returns a list of valid list types that can be used in POST requests.
+
+ #### Response format
+ >>> [
+ ... ["GUILD_INVITE","Guild Invite"],
+ ... ["FILE_FORMAT","File Format"],
+ ... ["DOMAIN_NAME","Domain Name"],
+ ... ["FILTER_TOKEN","Filter Token"]
+ ... ]
+
+ #### Status codes
+ - 200: returned on success
+
+ ### POST /bot/filter-lists
+ Adds a single FilterList item to the database.
+
+ #### Request body
+ >>> {
+ ... 'type': str,
+ ... 'allowed': bool,
+ ... 'content': str,
+ ... 'comment': Optional[str],
+ ... }
+
+ #### Status codes
+ - 201: returned on success
+ - 400: if one of the given fields is invalid
+
+ ### DELETE /bot/filter-lists/<id:int>
+ Deletes the FilterList item with the given `id`.
+
+ #### Status codes
+ - 204: returned on success
+ - 404: if a tag with the given `id` does not exist
+ """
+
+ serializer_class = FilterListSerializer
+ queryset = FilterList.objects.all()
+
+ @action(detail=False, url_path='get-types', methods=["get"])
+ def get_types(self, _: Request) -> Response:
+ """Get a list of all the types of FilterLists we support."""
+ return Response(FilterList.FilterListType.choices)
diff --git a/pydis_site/apps/api/viewsets/bot/reminder.py b/pydis_site/apps/api/viewsets/bot/reminder.py
index 147f6dbc..111660d9 100644
--- a/pydis_site/apps/api/viewsets/bot/reminder.py
+++ b/pydis_site/apps/api/viewsets/bot/reminder.py
@@ -4,6 +4,7 @@ from rest_framework.mixins import (
CreateModelMixin,
DestroyModelMixin,
ListModelMixin,
+ RetrieveModelMixin,
UpdateModelMixin
)
from rest_framework.viewsets import GenericViewSet
@@ -13,7 +14,12 @@ from pydis_site.apps.api.serializers import ReminderSerializer
class ReminderViewSet(
- CreateModelMixin, ListModelMixin, DestroyModelMixin, UpdateModelMixin, GenericViewSet
+ CreateModelMixin,
+ RetrieveModelMixin,
+ ListModelMixin,
+ DestroyModelMixin,
+ UpdateModelMixin,
+ GenericViewSet,
):
"""
View providing CRUD access to reminders.
@@ -27,9 +33,16 @@ class ReminderViewSet(
... {
... 'active': True,
... 'author': 1020103901030,
+ ... 'mentions': [
+ ... 336843820513755157,
+ ... 165023948638126080,
+ ... 267628507062992896
+ ... ],
... 'content': "Make dinner",
... 'expiration': '5018-11-20T15:52:00Z',
- ... 'id': 11
+ ... 'id': 11,
+ ... 'channel_id': 634547009956872193,
+ ... 'jump_url': "https://discord.com/channels/<guild_id>/<channel_id>/<message_id>"
... },
... ...
... ]
@@ -37,14 +50,41 @@ class ReminderViewSet(
#### Status codes
- 200: returned on success
+ ### GET /bot/reminders/<id:int>
+ Fetches the reminder with the given id.
+
+ #### Response format
+ >>>
+ ... {
+ ... 'active': True,
+ ... 'author': 1020103901030,
+ ... 'mentions': [
+ ... 336843820513755157,
+ ... 165023948638126080,
+ ... 267628507062992896
+ ... ],
+ ... 'content': "Make dinner",
+ ... 'expiration': '5018-11-20T15:52:00Z',
+ ... 'id': 11,
+ ... 'channel_id': 634547009956872193,
+ ... 'jump_url': "https://discord.com/channels/<guild_id>/<channel_id>/<message_id>"
+ ... }
+
+ #### Status codes
+ - 200: returned on success
+ - 404: returned when the reminder doesn't exist
+
### POST /bot/reminders
Create a new reminder.
#### Request body
>>> {
... 'author': int,
+ ... 'mentions': List[int],
... 'content': str,
- ... 'expiration': str # ISO-formatted datetime
+ ... 'expiration': str, # ISO-formatted datetime
+ ... 'channel_id': int,
+ ... 'jump_url': str
... }
#### Status codes
@@ -52,6 +92,22 @@ class ReminderViewSet(
- 400: if the body format is invalid
- 404: if no user with the given ID could be found
+ ### PATCH /bot/reminders/<id:int>
+ Update the user with the given `id`.
+ All fields in the request body are optional.
+
+ #### Request body
+ >>> {
+ ... 'mentions': List[int],
+ ... 'content': str,
+ ... 'expiration': str # ISO-formatted datetime
+ ... }
+
+ #### Status codes
+ - 200: 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`.
diff --git a/pydis_site/apps/api/viewsets/bot/user.py b/pydis_site/apps/api/viewsets/bot/user.py
index a407787e..9571b3d7 100644
--- a/pydis_site/apps/api/viewsets/bot/user.py
+++ b/pydis_site/apps/api/viewsets/bot/user.py
@@ -17,7 +17,6 @@ class UserViewSet(BulkCreateModelMixin, ModelViewSet):
>>> [
... {
... 'id': 409107086526644234,
- ... 'avatar': "3ba3c1acce584c20b1e96fc04bbe80eb",
... 'name': "Python",
... 'discriminator': 4329,
... 'roles': [
@@ -39,7 +38,6 @@ class UserViewSet(BulkCreateModelMixin, ModelViewSet):
#### Response format
>>> {
... 'id': 409107086526644234,
- ... 'avatar': "3ba3c1acce584c20b1e96fc04bbe80eb",
... 'name': "Python",
... 'discriminator': 4329,
... 'roles': [
@@ -62,7 +60,6 @@ class UserViewSet(BulkCreateModelMixin, ModelViewSet):
#### Request body
>>> {
... 'id': int,
- ... 'avatar': str,
... 'name': str,
... 'discriminator': int,
... 'roles': List[int],
@@ -83,7 +80,6 @@ class UserViewSet(BulkCreateModelMixin, ModelViewSet):
#### Request body
>>> {
... 'id': int,
- ... 'avatar': str,
... 'name': str,
... 'discriminator': int,
... 'roles': List[int],
@@ -102,7 +98,6 @@ class UserViewSet(BulkCreateModelMixin, ModelViewSet):
#### Request body
>>> {
... 'id': int,
- ... 'avatar': str,
... 'name': str,
... 'discriminator': int,
... 'roles': List[int],
@@ -123,4 +118,4 @@ class UserViewSet(BulkCreateModelMixin, ModelViewSet):
"""
serializer_class = UserSerializer
- queryset = User.objects.prefetch_related('roles')
+ queryset = User.objects
diff --git a/pydis_site/apps/home/forms/account_deletion.py b/pydis_site/apps/home/forms/account_deletion.py
index 17ffe5c1..eec70bea 100644
--- a/pydis_site/apps/home/forms/account_deletion.py
+++ b/pydis_site/apps/home/forms/account_deletion.py
@@ -1,23 +1,9 @@
-from crispy_forms.helper import FormHelper
-from crispy_forms.layout import Layout
from django.forms import CharField, Form
-from django_crispy_bulma.layout import IconField, Submit
class AccountDeletionForm(Form):
"""Account deletion form, to collect username for confirmation of removal."""
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.helper = FormHelper()
-
- self.helper.form_method = "post"
- self.helper.add_input(Submit("submit", "I understand, delete my account"))
-
- self.helper.layout = Layout(
- IconField("username", icon_prepend="user")
- )
-
username = CharField(
label="Username",
required=True
diff --git a/pydis_site/apps/home/signals.py b/pydis_site/apps/home/signals.py
index 4cb4564b..8af48c15 100644
--- a/pydis_site/apps/home/signals.py
+++ b/pydis_site/apps/home/signals.py
@@ -150,7 +150,7 @@ class AllauthSignalListener:
social_account = SocialAccount.objects.get(user=user, provider=DiscordProvider.id)
discord_user = DiscordUser.objects.get(id=int(social_account.uid))
- mappings = RoleMapping.objects.filter(role__in=discord_user.roles.all()).all()
+ mappings = RoleMapping.objects.filter(role__id__in=discord_user.roles).all()
is_staff = any(m.is_staff for m in mappings)
if user.is_staff != is_staff:
@@ -185,7 +185,7 @@ class AllauthSignalListener:
self.mapping_model_deleted(RoleMapping, instance=old_instance)
accounts = SocialAccount.objects.filter(
- uid__in=(u.id for u in instance.role.user_set.all())
+ uid__in=(u.id for u in DiscordUser.objects.filter(roles__contains=[instance.role.id]))
)
for account in accounts:
@@ -198,7 +198,7 @@ class AllauthSignalListener:
discord_user = DiscordUser.objects.get(id=int(account.uid))
mappings = RoleMapping.objects.filter(
- role__in=discord_user.roles.all()
+ role__id__in=discord_user.roles
).exclude(id=instance.id).all()
is_staff = any(m.is_staff for m in mappings)
@@ -289,9 +289,9 @@ class AllauthSignalListener:
new_groups = []
is_staff = False
- for role in user.roles.all():
+ for role in user.roles:
try:
- mapping = mappings.get(role=role)
+ mapping = mappings.get(role__id=role)
except RoleMapping.DoesNotExist:
continue # No mapping exists
diff --git a/pydis_site/apps/home/tests/mock_github_api_response.json b/pydis_site/apps/home/tests/mock_github_api_response.json
index 37dc672e..35604a85 100644
--- a/pydis_site/apps/home/tests/mock_github_api_response.json
+++ b/pydis_site/apps/home/tests/mock_github_api_response.json
@@ -28,7 +28,7 @@
"forks_count": 31
},
{
- "full_name": "python-discord/django-crispy-bulma",
+ "full_name": "python-discord/flake8-annotations",
"description": "test",
"stargazers_count": 97,
"language": "Python",
diff --git a/pydis_site/apps/home/tests/test_signal_listener.py b/pydis_site/apps/home/tests/test_signal_listener.py
index 66a67252..d99d81a5 100644
--- a/pydis_site/apps/home/tests/test_signal_listener.py
+++ b/pydis_site/apps/home/tests/test_signal_listener.py
@@ -81,24 +81,21 @@ class SignalListenerTests(TestCase):
id=0,
name="user",
discriminator=0,
- avatar_hash=None
)
cls.discord_unmapped = DiscordUser.objects.create(
id=2,
name="unmapped",
discriminator=0,
- avatar_hash=None
)
- cls.discord_unmapped.roles.add(cls.unmapped_role)
+ cls.discord_unmapped.roles.append(cls.unmapped_role.id)
cls.discord_unmapped.save()
cls.discord_not_in_guild = DiscordUser.objects.create(
id=3,
name="not-in-guild",
discriminator=0,
- avatar_hash=None,
in_guild=False
)
@@ -106,20 +103,18 @@ class SignalListenerTests(TestCase):
id=1,
name="admin",
discriminator=0,
- avatar_hash=None
)
- cls.discord_admin.roles.set([cls.admin_role])
+ cls.discord_admin.roles = [cls.admin_role.id]
cls.discord_admin.save()
cls.discord_moderator = DiscordUser.objects.create(
id=4,
name="admin",
discriminator=0,
- avatar_hash=None
)
- cls.discord_moderator.roles.set([cls.moderator_role])
+ cls.discord_moderator.roles = [cls.moderator_role.id]
cls.discord_moderator.save()
cls.django_user_discordless = DjangoUser.objects.create(username="no-discord")
@@ -338,7 +333,7 @@ class SignalListenerTests(TestCase):
handler._apply_groups(self.discord_admin, self.social_admin)
self.assertEqual(self.django_user_discordless.groups.all().count(), 0)
- self.discord_admin.roles.add(self.admin_role)
+ self.discord_admin.roles.append(self.admin_role.id)
self.discord_admin.save()
def test_apply_groups_moderator(self):
@@ -365,7 +360,7 @@ class SignalListenerTests(TestCase):
handler._apply_groups(self.discord_moderator, self.social_moderator)
self.assertEqual(self.django_user_discordless.groups.all().count(), 0)
- self.discord_moderator.roles.add(self.moderator_role)
+ self.discord_moderator.roles.append(self.moderator_role.id)
self.discord_moderator.save()
def test_apply_groups_other(self):
diff --git a/pydis_site/apps/home/views/home.py b/pydis_site/apps/home/views/home.py
index 4cf22594..20e38ab0 100644
--- a/pydis_site/apps/home/views/home.py
+++ b/pydis_site/apps/home/views/home.py
@@ -23,8 +23,8 @@ class HomeView(View):
"python-discord/bot",
"python-discord/snekbox",
"python-discord/seasonalbot",
+ "python-discord/flake8-annotations",
"python-discord/django-simple-bulma",
- "python-discord/django-crispy-bulma",
]
def _get_api_data(self) -> Dict[str, Dict[str, str]]:
@@ -61,7 +61,7 @@ class HomeView(View):
# Try to get new data from the API. If it fails, return the cached data.
try:
api_repositories = self._get_api_data()
- except TypeError:
+ except (TypeError, ConnectionError):
return RepositoryMetadata.objects.all()
database_repositories = []
diff --git a/pydis_site/apps/staff/tests/test_logs_view.py b/pydis_site/apps/staff/tests/test_logs_view.py
index 1415c558..17910bb6 100644
--- a/pydis_site/apps/staff/tests/test_logs_view.py
+++ b/pydis_site/apps/staff/tests/test_logs_view.py
@@ -21,10 +21,9 @@ class TestLogsView(TestCase):
id=12345678901,
name='Alan Turing',
discriminator=1912,
- avatar_hash=None
)
- cls.author.roles.add(cls.developers_role)
+ cls.author.roles.append(cls.developers_role.id)
cls.deletion_context = MessageDeletionContext.objects.create(
actor=cls.actor,
diff --git a/pydis_site/settings.py b/pydis_site/settings.py
index 5f80a414..2c87007c 100644
--- a/pydis_site/settings.py
+++ b/pydis_site/settings.py
@@ -52,6 +52,8 @@ if DEBUG:
'api.pythondiscord.local',
'admin.pythondiscord.local',
'staff.pythondiscord.local',
+ '0.0.0.0', # noqa: S104
+ 'localhost',
'web',
'api.web',
'admin.web',
@@ -104,8 +106,6 @@ INSTALLED_APPS = [
'allauth.socialaccount.providers.discord',
'allauth.socialaccount.providers.github',
- 'crispy_forms',
- 'django_crispy_bulma',
'django_hosts',
'django_filters',
'django_nyt.apps.DjangoNytConfig',
@@ -288,7 +288,6 @@ LOGGING = {
}
# Django Messages framework config
-
MESSAGE_TAGS = {
messages.DEBUG: 'primary',
messages.INFO: 'info',
@@ -297,17 +296,6 @@ MESSAGE_TAGS = {
messages.ERROR: 'danger',
}
-# Custom settings for Crispyforms
-CRISPY_ALLOWED_TEMPLATE_PACKS = (
- "bootstrap",
- "uni_form",
- "bootstrap3",
- "bootstrap4",
- "bulma",
-)
-
-CRISPY_TEMPLATE_PACK = "bulma"
-
# Custom settings for django-simple-bulma
BULMA_SETTINGS = {
"variables": { # If you update these colours, please update the notification.css file
diff --git a/pydis_site/static/images/events/summer_code_jam_2020.png b/pydis_site/static/images/events/summer_code_jam_2020.png
new file mode 100644
index 00000000..63c311b0
--- /dev/null
+++ b/pydis_site/static/images/events/summer_code_jam_2020.png
Binary files differ
diff --git a/pydis_site/templates/base/navbar.html b/pydis_site/templates/base/navbar.html
index d2ea9589..c2915025 100644
--- a/pydis_site/templates/base/navbar.html
+++ b/pydis_site/templates/base/navbar.html
@@ -87,8 +87,8 @@
<div class="navbar-item">
<strong>Events</strong>
</div>
- <a class="navbar-item" href="{% url 'wiki:get' path="events/game-jam-2020/" %}">
- Most Recent: Game Jam 2020
+ <a class="navbar-item" href="{% url 'wiki:get' path="code-jams/code-jam-7/" %}">
+ Most Recent: Code Jam 7
</a>
<a class="navbar-item" href="{% url 'wiki:get' path="events/" %}">
All events
diff --git a/pydis_site/templates/home/account/delete.html b/pydis_site/templates/home/account/delete.html
index 1020a82b..0d44e32a 100644
--- a/pydis_site/templates/home/account/delete.html
+++ b/pydis_site/templates/home/account/delete.html
@@ -1,6 +1,4 @@
{% extends 'base/base.html' %}
-
-{% load crispy_forms_tags %}
{% load static %}
{% block title %}Delete Account{% endblock %}
@@ -36,7 +34,12 @@
<div class="columns is-centered">
<div class="column is-half-desktop is-full-tablet is-full-mobile">
- {% crispy form %}
+ <form method="post">
+ {% csrf_token %}
+ <label for="id_username" class="label requiredField">Username</label>
+ <input id="id_username" class="input" type="text" required name="username">
+ <input style="margin-top: 1em;" type="submit" value="I understand, delete my account" class="button is-primary">
+ </form>
</div>
</div>
</div>
diff --git a/pydis_site/templates/home/index.html b/pydis_site/templates/home/index.html
index c30cbee6..3e96cc91 100644
--- a/pydis_site/templates/home/index.html
+++ b/pydis_site/templates/home/index.html
@@ -39,8 +39,8 @@
{# Right column container #}
<div class="column is-half-desktop">
- <a href="https://pythondiscord.com/pages/events/game-jam-2020/">
- <img src="https://raw.githubusercontent.com/python-discord/branding/master/events/game%20jam%202020/game%20jam%202020%20-%20website%20banner.png">
+ <a href="https://pythondiscord.com/pages/code-jams/code-jam-7/">
+ <img src="{% static "images/events/summer_code_jam_2020.png" %}">
</a>
</div>
</div>
diff --git a/pydis_site/templates/wiki/base.html b/pydis_site/templates/wiki/base.html
index 9f904324..846492ab 100644
--- a/pydis_site/templates/wiki/base.html
+++ b/pydis_site/templates/wiki/base.html
@@ -7,7 +7,7 @@
{% block head %}
{{ block.super }}
- <script src="{% static "wiki/js/jquery-3.3.1.min.js" %}" type="text/javascript"></script>
+ <script src="{% static "wiki/js/jquery-3.4.1.min.js" %}" type="text/javascript"></script>
<script src="{% static "wiki/js/core.js" %}" type="text/javascript"></script>
<script src="{% static "js/wiki/simplemde.min.js" %}" type="text/javascript"></script>
diff --git a/pydis_site/tests/__init__.py b/pydis_site/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/pydis_site/tests/__init__.py
diff --git a/pydis_site/tests/test_utils_account.py b/pydis_site/tests/test_utils_account.py
index d2946368..6f8338b4 100644
--- a/pydis_site/tests/test_utils_account.py
+++ b/pydis_site/tests/test_utils_account.py
@@ -5,7 +5,7 @@ from allauth.socialaccount.models import SocialAccount, SocialLogin
from django.contrib.auth.models import User
from django.contrib.messages.storage.base import BaseStorage
from django.http import HttpRequest
-from django.test import TestCase
+from django.test import RequestFactory, TestCase
from pydis_site.apps.api.models import Role, User as DiscordUser
from pydis_site.utils.account import AccountAdapter, SocialAccountAdapter
@@ -13,28 +13,43 @@ from pydis_site.utils.account import AccountAdapter, SocialAccountAdapter
class AccountUtilsTests(TestCase):
def setUp(self):
+ # Create the user
self.django_user = User.objects.create(username="user")
+ # Create the roles
+ developers_role = Role.objects.create(
+ id=1,
+ name="Developers",
+ colour=0,
+ permissions=0,
+ position=1
+ )
+ everyone_role = Role.objects.create(
+ id=0,
+ name="@everyone",
+ colour=0,
+ permissions=0,
+ position=0
+ )
+
+ # Create the social accounts
self.discord_account = SocialAccount.objects.create(
user=self.django_user, provider="discord", uid=0
)
-
- self.discord_account_role = SocialAccount.objects.create(
+ self.discord_account_one_role = SocialAccount.objects.create(
user=self.django_user, provider="discord", uid=1
)
-
self.discord_account_two_roles = SocialAccount.objects.create(
user=self.django_user, provider="discord", uid=2
)
-
self.discord_account_not_present = SocialAccount.objects.create(
user=self.django_user, provider="discord", uid=3
)
-
self.github_account = SocialAccount.objects.create(
user=self.django_user, provider="github", uid=0
)
+ # Create DiscordUsers
self.discord_user = DiscordUser.objects.create(
id=0,
name="user",
@@ -44,33 +59,18 @@ class AccountUtilsTests(TestCase):
self.discord_user_role = DiscordUser.objects.create(
id=1,
name="user present",
- discriminator=0
+ discriminator=0,
+ roles=[everyone_role.id]
)
self.discord_user_two_roles = DiscordUser.objects.create(
id=2,
name="user with both roles",
- discriminator=0
+ discriminator=0,
+ roles=[everyone_role.id, developers_role.id]
)
- everyone_role = Role.objects.create(
- id=0,
- name="@everyone",
- colour=0,
- permissions=0,
- position=0
- )
-
- self.discord_user_role.roles.add(everyone_role)
- self.discord_user_two_roles.roles.add(everyone_role)
-
- self.discord_user_two_roles.roles.add(Role.objects.create(
- id=1,
- name="Developers",
- colour=0,
- permissions=0,
- position=1
- ))
+ self.request_factory = RequestFactory()
def test_account_adapter(self):
"""Test that our Allauth account adapter functions correctly."""
@@ -83,13 +83,13 @@ class AccountUtilsTests(TestCase):
adapter = SocialAccountAdapter()
discord_login = SocialLogin(account=self.discord_account)
- discord_login_role = SocialLogin(account=self.discord_account_role)
- discord_login_two_roles = SocialLogin(account=self.discord_account_two_roles)
+ discord_login_role = SocialLogin(account=self.discord_account_one_role)
discord_login_not_present = SocialLogin(account=self.discord_account_not_present)
+ discord_login_two_roles = SocialLogin(account=self.discord_account_two_roles)
github_login = SocialLogin(account=self.github_account)
- messages_request = HttpRequest()
+ messages_request = self.request_factory.get("/")
messages_request._messages = BaseStorage(messages_request)
with patch("pydis_site.utils.account.reverse") as mock_reverse:
@@ -106,12 +106,14 @@ class AccountUtilsTests(TestCase):
with self.assertRaises(ImmediateHttpResponse):
adapter.is_open_for_signup(messages_request, discord_login_not_present)
+ self.assertTrue(
+ adapter.is_open_for_signup(messages_request, discord_login_two_roles)
+ )
+
self.assertEqual(len(messages_request._messages._queued_messages), 4)
self.assertEqual(mock_redirect.call_count, 4)
self.assertEqual(mock_reverse.call_count, 4)
- self.assertTrue(adapter.is_open_for_signup(HttpRequest(), discord_login_two_roles))
-
def test_social_account_adapter_populate(self):
"""Test that our Allauth social account adapter correctly handles data population."""
adapter = SocialAccountAdapter()
@@ -120,13 +122,18 @@ class AccountUtilsTests(TestCase):
account=self.discord_account,
user=self.django_user
)
-
discord_login.account.extra_data["discriminator"] = "0000"
- user = adapter.populate_user(
- HttpRequest(), discord_login,
+ discord_user = adapter.populate_user(
+ self.request_factory.get("/"), discord_login,
{"username": "user"}
)
+ self.assertEqual(discord_user.username, "user#0000")
+ self.assertEqual(discord_user.first_name, "user#0000")
- self.assertEqual(user.username, "user#0000")
- self.assertEqual(user.first_name, "user#0000")
+ discord_login.account.provider = "not_discord"
+ not_discord_user = adapter.populate_user(
+ self.request_factory.get("/"), discord_login,
+ {"username": "user"}
+ )
+ self.assertEqual(not_discord_user.username, "user")
diff --git a/pydis_site/utils/account.py b/pydis_site/utils/account.py
index 2d699c88..b4e41198 100644
--- a/pydis_site/utils/account.py
+++ b/pydis_site/utils/account.py
@@ -54,7 +54,7 @@ class SocialAccountAdapter(DefaultSocialAccountAdapter):
raise ImmediateHttpResponse(redirect(reverse("home")))
- if user.roles.count() <= 1:
+ if len(user.roles) <= 1:
add_message(request, ERROR, ERROR_JOIN_DISCORD)
raise ImmediateHttpResponse(redirect(reverse("home")))