aboutsummaryrefslogtreecommitdiffstats
path: root/pysite/apps
diff options
context:
space:
mode:
authorGravatar Gareth Coles <[email protected]>2019-04-05 18:24:32 +0100
committerGravatar Gareth Coles <[email protected]>2019-04-05 18:24:32 +0100
commitab8b798547e82ca79882ba28b1920077c803425f (patch)
treeb28a9005d05dfd2c9da62351671bf2aa6e37f7dc /pysite/apps
parent[#158 #160] Automatically run collectstatic in containers/setup script (diff)
pysite -> pydis_site
Diffstat (limited to 'pysite/apps')
-rw-r--r--pysite/apps/__init__.py0
-rw-r--r--pysite/apps/admin/__init__.py0
-rw-r--r--pysite/apps/admin/urls.py7
-rw-r--r--pysite/apps/api/__init__.py0
-rw-r--r--pysite/apps/api/admin.py26
-rw-r--r--pysite/apps/api/apps.py5
-rw-r--r--pysite/apps/api/migrations/0001_initial.py21
-rw-r--r--pysite/apps/api/migrations/0002_documentationlink.py21
-rw-r--r--pysite/apps/api/migrations/0003_offtopicchannelname.py20
-rw-r--r--pysite/apps/api/migrations/0004_role.py23
-rw-r--r--pysite/apps/api/migrations/0005_user.py38
-rw-r--r--pysite/apps/api/migrations/0006_add_help_texts.py44
-rw-r--r--pysite/apps/api/migrations/0007_tag.py23
-rw-r--r--pysite/apps/api/migrations/0008_tag_embed_validator.py23
-rw-r--r--pysite/apps/api/migrations/0009_snakefact.py21
-rw-r--r--pysite/apps/api/migrations/0010_snakeidiom.py21
-rw-r--r--pysite/apps/api/migrations/0011_auto_20181020_1904.py18
-rw-r--r--pysite/apps/api/migrations/0012_specialsnake.py22
-rw-r--r--pysite/apps/api/migrations/0013_specialsnake_image.py21
-rw-r--r--pysite/apps/api/migrations/0014_auto_20181025_1959.py23
-rw-r--r--pysite/apps/api/migrations/0015_auto_20181027_1617.py19
-rw-r--r--pysite/apps/api/migrations/0016_auto_20181027_1619.py18
-rw-r--r--pysite/apps/api/migrations/0017_auto_20181029_1921.py19
-rw-r--r--pysite/apps/api/migrations/0018_messagedeletioncontext.py24
-rw-r--r--pysite/apps/api/migrations/0018_user_rename.py17
-rw-r--r--pysite/apps/api/migrations/0019_deletedmessage.py34
-rw-r--r--pysite/apps/api/migrations/0019_user_in_guild.py18
-rw-r--r--pysite/apps/api/migrations/0020_add_snake_field_validators.py24
-rw-r--r--pysite/apps/api/migrations/0020_infraction.py30
-rw-r--r--pysite/apps/api/migrations/0021_add_special_snake_validator.py19
-rw-r--r--pysite/apps/api/migrations/0021_infraction_reason_null.py18
-rw-r--r--pysite/apps/api/migrations/0021_merge_20181125_1015.py14
-rw-r--r--pysite/apps/api/migrations/0022_infraction_remove_note.py18
-rw-r--r--pysite/apps/api/migrations/0023_merge_infractions_snake_validators.py14
-rw-r--r--pysite/apps/api/migrations/0024_add_note_infraction_type.py18
-rw-r--r--pysite/apps/api/migrations/0025_allow_custom_inserted_at_infraction_field.py19
-rw-r--r--pysite/apps/api/migrations/0026_use_proper_default_for_infraction_insertion_date.py19
-rw-r--r--pysite/apps/api/migrations/0027_merge_20190120_0852.py14
-rw-r--r--pysite/apps/api/migrations/0028_allow_message_content_blank.py18
-rw-r--r--pysite/apps/api/migrations/0029_add_infraction_type_watch.py18
-rw-r--r--pysite/apps/api/migrations/0030_reminder.py27
-rw-r--r--pysite/apps/api/migrations/0031_nomination.py26
-rw-r--r--pysite/apps/api/migrations/0032_botsetting.py23
-rw-r--r--pysite/apps/api/migrations/0033_create_defcon_settings.py30
-rw-r--r--pysite/apps/api/migrations/0034_add_botsetting_name_validator.py20
-rw-r--r--pysite/apps/api/migrations/__init__.py0
-rw-r--r--pysite/apps/api/models.py452
-rw-r--r--pysite/apps/api/serializers.py174
-rw-r--r--pysite/apps/api/tests/__init__.py0
-rw-r--r--pysite/apps/api/tests/base.py69
-rw-r--r--pysite/apps/api/tests/test_deleted_messages.py43
-rw-r--r--pysite/apps/api/tests/test_documentation_links.py161
-rw-r--r--pysite/apps/api/tests/test_healthcheck.py16
-rw-r--r--pysite/apps/api/tests/test_infractions.py359
-rw-r--r--pysite/apps/api/tests/test_models.py113
-rw-r--r--pysite/apps/api/tests/test_nominations.py41
-rw-r--r--pysite/apps/api/tests/test_off_topic_channel_names.py152
-rw-r--r--pysite/apps/api/tests/test_rules.py35
-rw-r--r--pysite/apps/api/tests/test_snake_names.py67
-rw-r--r--pysite/apps/api/tests/test_users.py121
-rw-r--r--pysite/apps/api/tests/test_validators.py213
-rw-r--r--pysite/apps/api/urls.py86
-rw-r--r--pysite/apps/api/validators.py164
-rw-r--r--pysite/apps/api/views.py161
-rw-r--r--pysite/apps/api/viewsets.py890
-rw-r--r--pysite/apps/home/__init__.py0
-rw-r--r--pysite/apps/home/admin.py3
-rw-r--r--pysite/apps/home/apps.py5
-rw-r--r--pysite/apps/home/migrations/__init__.py0
-rw-r--r--pysite/apps/home/models.py3
-rw-r--r--pysite/apps/home/tests.py9
-rw-r--r--pysite/apps/home/urls.py10
-rw-r--r--pysite/apps/home/views.py3
-rw-r--r--pysite/apps/wiki/__init__.py0
-rw-r--r--pysite/apps/wiki/admin.py3
-rw-r--r--pysite/apps/wiki/apps.py5
-rw-r--r--pysite/apps/wiki/migrations/__init__.py0
-rw-r--r--pysite/apps/wiki/models.py3
-rw-r--r--pysite/apps/wiki/tests.py3
-rw-r--r--pysite/apps/wiki/views.py3
80 files changed, 0 insertions, 4262 deletions
diff --git a/pysite/apps/__init__.py b/pysite/apps/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/pysite/apps/__init__.py
+++ /dev/null
diff --git a/pysite/apps/admin/__init__.py b/pysite/apps/admin/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/pysite/apps/admin/__init__.py
+++ /dev/null
diff --git a/pysite/apps/admin/urls.py b/pysite/apps/admin/urls.py
deleted file mode 100644
index 146c6496..00000000
--- a/pysite/apps/admin/urls.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from django.contrib import admin
-from django.urls import path
-
-
-urlpatterns = (
- path('', admin.site.urls),
-)
diff --git a/pysite/apps/api/__init__.py b/pysite/apps/api/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/pysite/apps/api/__init__.py
+++ /dev/null
diff --git a/pysite/apps/api/admin.py b/pysite/apps/api/admin.py
deleted file mode 100644
index 3ae7f3c5..00000000
--- a/pysite/apps/api/admin.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from django.contrib import admin
-
-from .models import (
- BotSetting, DeletedMessage,
- DocumentationLink, Infraction,
- MessageDeletionContext, OffTopicChannelName,
- Role, SnakeFact,
- SnakeIdiom, SnakeName,
- SpecialSnake, Tag,
- User
-)
-
-
-admin.site.register(BotSetting)
-admin.site.register(DeletedMessage)
-admin.site.register(DocumentationLink)
-admin.site.register(Infraction)
-admin.site.register(MessageDeletionContext)
-admin.site.register(OffTopicChannelName)
-admin.site.register(Role)
-admin.site.register(SnakeFact)
-admin.site.register(SnakeIdiom)
-admin.site.register(SnakeName)
-admin.site.register(SpecialSnake)
-admin.site.register(Tag)
-admin.site.register(User)
diff --git a/pysite/apps/api/apps.py b/pysite/apps/api/apps.py
deleted file mode 100644
index d87006dd..00000000
--- a/pysite/apps/api/apps.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from django.apps import AppConfig
-
-
-class ApiConfig(AppConfig):
- name = 'api'
diff --git a/pysite/apps/api/migrations/0001_initial.py b/pysite/apps/api/migrations/0001_initial.py
deleted file mode 100644
index dca6d17f..00000000
--- a/pysite/apps/api/migrations/0001_initial.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# Generated by Django 2.1 on 2018-08-15 17:28
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- initial = True
-
- dependencies = [
- ]
-
- operations = [
- migrations.CreateModel(
- name='SnakeName',
- fields=[
- ('name', models.CharField(max_length=100, primary_key=True, serialize=False)),
- ('scientific', models.CharField(max_length=150)),
- ],
- ),
- ]
diff --git a/pysite/apps/api/migrations/0002_documentationlink.py b/pysite/apps/api/migrations/0002_documentationlink.py
deleted file mode 100644
index 5dee679a..00000000
--- a/pysite/apps/api/migrations/0002_documentationlink.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# Generated by Django 2.1 on 2018-08-16 19:42
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0001_initial'),
- ]
-
- operations = [
- migrations.CreateModel(
- name='DocumentationLink',
- fields=[
- ('package', models.CharField(max_length=50, primary_key=True, serialize=False)),
- ('base_url', models.URLField()),
- ('inventory_url', models.URLField()),
- ],
- ),
- ]
diff --git a/pysite/apps/api/migrations/0003_offtopicchannelname.py b/pysite/apps/api/migrations/0003_offtopicchannelname.py
deleted file mode 100644
index 2f19bfd8..00000000
--- a/pysite/apps/api/migrations/0003_offtopicchannelname.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# Generated by Django 2.1 on 2018-08-31 22:21
-
-import django.core.validators
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0002_documentationlink'),
- ]
-
- operations = [
- migrations.CreateModel(
- name='OffTopicChannelName',
- fields=[
- ('name', models.CharField(max_length=96, primary_key=True, serialize=False, validators=[django.core.validators.RegexValidator(regex='^[a-z0-9-]+$')])),
- ],
- ),
- ]
diff --git a/pysite/apps/api/migrations/0004_role.py b/pysite/apps/api/migrations/0004_role.py
deleted file mode 100644
index 0a6b6c43..00000000
--- a/pysite/apps/api/migrations/0004_role.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# Generated by Django 2.1 on 2018-09-01 19:54
-
-import django.core.validators
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0003_offtopicchannelname'),
- ]
-
- operations = [
- migrations.CreateModel(
- name='Role',
- fields=[
- ('id', models.BigIntegerField(help_text="The role's ID, taken from Discord.", primary_key=True, serialize=False, validators=[django.core.validators.MinValueValidator(limit_value=0, message='Role IDs cannot be negative.')])),
- ('name', models.CharField(help_text="The role's name, taken from Discord.", max_length=100)),
- ('colour', models.IntegerField(help_text='The integer value of the colour of this role from Discord.', validators=[django.core.validators.MinValueValidator(limit_value=0, message='Colour hex cannot be negative.')])),
- ('permissions', models.IntegerField(help_text='The integer value of the permission bitset of this role from Discord.', validators=[django.core.validators.MinValueValidator(limit_value=0, message='Role permissions cannot be negative.'), django.core.validators.MaxValueValidator(limit_value=8589934592, message='Role permission bitset exceeds value of having all permissions')])),
- ],
- ),
- ]
diff --git a/pysite/apps/api/migrations/0005_user.py b/pysite/apps/api/migrations/0005_user.py
deleted file mode 100644
index a771119c..00000000
--- a/pysite/apps/api/migrations/0005_user.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# Generated by Django 2.1 on 2018-09-01 20:02
-
-import django.core.validators
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0004_role'),
- ]
-
- operations = [
- migrations.CreateModel(
- name='Member',
- fields=[
- ('id', models.BigIntegerField(help_text='The ID of this user, taken from Discord.', primary_key=True, serialize=False, validators=[django.core.validators.MinValueValidator(limit_value=0, message='User IDs cannot be negative.')])),
- ('name', models.CharField(help_text='The username, taken from Discord.', max_length=32)),
- ('discriminator', models.PositiveSmallIntegerField(help_text='The discriminator of this user, taken from Discord.', validators=[django.core.validators.MaxValueValidator(limit_value=9999, message='Discriminators may not exceed `9999`.')])),
- ('avatar_hash', models.CharField(help_text="The user's avatar hash, taken from Discord. Null if the user does not have any custom avatar.", max_length=100, null=True)),
- ],
- ),
- migrations.AlterField(
- model_name='role',
- name='id',
- field=models.BigIntegerField(help_text='The role ID, taken from Discord.', primary_key=True, serialize=False, validators=[django.core.validators.MinValueValidator(limit_value=0, message='Role IDs cannot be negative.')]),
- ),
- migrations.AlterField(
- model_name='role',
- name='name',
- field=models.CharField(help_text='The role name, taken from Discord.', max_length=100),
- ),
- migrations.AddField(
- model_name='member',
- name='roles',
- field=models.ManyToManyField(help_text='Any roles this user has on our server.', to='api.Role'),
- ),
- ]
diff --git a/pysite/apps/api/migrations/0006_add_help_texts.py b/pysite/apps/api/migrations/0006_add_help_texts.py
deleted file mode 100644
index a57d2289..00000000
--- a/pysite/apps/api/migrations/0006_add_help_texts.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# Generated by Django 2.1.1 on 2018-09-21 20:26
-
-import django.core.validators
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0005_user'),
- ]
-
- operations = [
- migrations.AlterField(
- model_name='documentationlink',
- name='base_url',
- field=models.URLField(help_text='The base URL from which documentation will be available for this project. Used to generate links to various symbols within this package.'),
- ),
- migrations.AlterField(
- model_name='documentationlink',
- name='inventory_url',
- field=models.URLField(help_text='The URL at which the Sphinx inventory is available for this package.'),
- ),
- migrations.AlterField(
- model_name='documentationlink',
- name='package',
- field=models.CharField(help_text='The Python package name that this documentation link belongs to.', max_length=50, primary_key=True, serialize=False),
- ),
- migrations.AlterField(
- model_name='offtopicchannelname',
- name='name',
- field=models.CharField(help_text='The actual channel name that will be used on our Discord server.', max_length=96, primary_key=True, serialize=False, validators=[django.core.validators.RegexValidator(regex='^[a-z0-9-]+$')]),
- ),
- migrations.AlterField(
- model_name='snakename',
- name='name',
- field=models.CharField(help_text="The regular name for this snake, e.g. 'Python'.", max_length=100, primary_key=True, serialize=False),
- ),
- migrations.AlterField(
- model_name='snakename',
- name='scientific',
- field=models.CharField(help_text="The scientific name for this snake, e.g. 'Python bivittatus'.", max_length=150),
- ),
- ]
diff --git a/pysite/apps/api/migrations/0007_tag.py b/pysite/apps/api/migrations/0007_tag.py
deleted file mode 100644
index d5546ccc..00000000
--- a/pysite/apps/api/migrations/0007_tag.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# Generated by Django 2.1.1 on 2018-09-21 22:05
-
-import pysite.apps.api.models
-import django.contrib.postgres.fields.jsonb
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0006_add_help_texts'),
- ]
-
- operations = [
- migrations.CreateModel(
- name='Tag',
- fields=[
- ('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=(pysite.apps.api.models.ModelReprMixin, models.Model),
- ),
- ]
diff --git a/pysite/apps/api/migrations/0008_tag_embed_validator.py b/pysite/apps/api/migrations/0008_tag_embed_validator.py
deleted file mode 100644
index fd2385d1..00000000
--- a/pysite/apps/api/migrations/0008_tag_embed_validator.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# Generated by Django 2.1.1 on 2018-09-23 10:07
-
-import pysite.apps.api.validators
-import django.contrib.postgres.fields.jsonb
-from django.db import migrations
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0007_tag'),
- ]
-
- operations = [
- migrations.AlterField(
- model_name='tag',
- name='embed',
- field=django.contrib.postgres.fields.jsonb.JSONField(help_text='The actual embed shown by this tag.', validators=[
-
-
- pysite.apps.api.validators.validate_tag_embed]),
- ),
- ]
diff --git a/pysite/apps/api/migrations/0009_snakefact.py b/pysite/apps/api/migrations/0009_snakefact.py
deleted file mode 100644
index 2a136f83..00000000
--- a/pysite/apps/api/migrations/0009_snakefact.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# Generated by Django 2.1.2 on 2018-10-11 14:25
-
-import pysite.apps.api.models
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0008_tag_embed_validator'),
- ]
-
- operations = [
- migrations.CreateModel(
- name='SnakeFact',
- fields=[
- ('fact', models.CharField(help_text='A fact about snakes.', max_length=200, primary_key=True, serialize=False)),
- ],
- bases=(pysite.apps.api.models.ModelReprMixin, models.Model),
- ),
- ]
diff --git a/pysite/apps/api/migrations/0010_snakeidiom.py b/pysite/apps/api/migrations/0010_snakeidiom.py
deleted file mode 100644
index 3eb99198..00000000
--- a/pysite/apps/api/migrations/0010_snakeidiom.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# Generated by Django 2.1.2 on 2018-10-19 16:27
-
-import pysite.apps.api.models
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0009_snakefact'),
- ]
-
- operations = [
- migrations.CreateModel(
- name='SnakeIdiom',
- fields=[
- ('idiom', models.CharField(help_text='A snake idiom', max_length=140, primary_key=True, serialize=False)),
- ],
- bases=(pysite.apps.api.models.ModelReprMixin, models.Model),
- ),
- ]
diff --git a/pysite/apps/api/migrations/0011_auto_20181020_1904.py b/pysite/apps/api/migrations/0011_auto_20181020_1904.py
deleted file mode 100644
index bb5a6325..00000000
--- a/pysite/apps/api/migrations/0011_auto_20181020_1904.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Generated by Django 2.1.2 on 2018-10-20 19:04
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0010_snakeidiom'),
- ]
-
- operations = [
- migrations.AlterField(
- model_name='snakeidiom',
- name='idiom',
- field=models.CharField(help_text='A saying about a snake.', max_length=140, primary_key=True, serialize=False),
- ),
- ]
diff --git a/pysite/apps/api/migrations/0012_specialsnake.py b/pysite/apps/api/migrations/0012_specialsnake.py
deleted file mode 100644
index ecf1b9d9..00000000
--- a/pysite/apps/api/migrations/0012_specialsnake.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Generated by Django 2.1.2 on 2018-10-22 09:53
-
-import pysite.apps.api.models
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0011_auto_20181020_1904'),
- ]
-
- operations = [
- migrations.CreateModel(
- name='SpecialSnake',
- fields=[
- ('name', models.CharField(max_length=140, primary_key=True, serialize=False)),
- ('info', models.TextField()),
- ],
- bases=(pysite.apps.api.models.ModelReprMixin, models.Model),
- ),
- ]
diff --git a/pysite/apps/api/migrations/0013_specialsnake_image.py b/pysite/apps/api/migrations/0013_specialsnake_image.py
deleted file mode 100644
index a0d0d318..00000000
--- a/pysite/apps/api/migrations/0013_specialsnake_image.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# Generated by Django 2.1.2 on 2018-10-23 11:51
-
-import datetime
-from django.db import migrations, models
-from django.utils.timezone import utc
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0012_specialsnake'),
- ]
-
- operations = [
- migrations.AddField(
- model_name='specialsnake',
- name='image',
- field=models.URLField(default=datetime.datetime(2018, 10, 23, 11, 51, 23, 703868, tzinfo=utc)),
- preserve_default=False,
- ),
- ]
diff --git a/pysite/apps/api/migrations/0014_auto_20181025_1959.py b/pysite/apps/api/migrations/0014_auto_20181025_1959.py
deleted file mode 100644
index 3599d2cd..00000000
--- a/pysite/apps/api/migrations/0014_auto_20181025_1959.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# Generated by Django 2.1.2 on 2018-10-25 19:59
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0013_specialsnake_image'),
- ]
-
- operations = [
- migrations.AlterField(
- model_name='specialsnake',
- name='info',
- field=models.TextField(help_text='Info about a special snake.'),
- ),
- migrations.AlterField(
- model_name='specialsnake',
- name='name',
- field=models.CharField(help_text='A special snake name.', max_length=140, primary_key=True, serialize=False),
- ),
- ]
diff --git a/pysite/apps/api/migrations/0015_auto_20181027_1617.py b/pysite/apps/api/migrations/0015_auto_20181027_1617.py
deleted file mode 100644
index 8973ff6d..00000000
--- a/pysite/apps/api/migrations/0015_auto_20181027_1617.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# Generated by Django 2.1.2 on 2018-10-27 16:17
-
-import django.contrib.postgres.fields
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0014_auto_20181025_1959'),
- ]
-
- operations = [
- migrations.AlterField(
- model_name='specialsnake',
- name='image',
- field=django.contrib.postgres.fields.ArrayField(base_field=models.URLField(), size=None),
- ),
- ]
diff --git a/pysite/apps/api/migrations/0016_auto_20181027_1619.py b/pysite/apps/api/migrations/0016_auto_20181027_1619.py
deleted file mode 100644
index b8bdfb16..00000000
--- a/pysite/apps/api/migrations/0016_auto_20181027_1619.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Generated by Django 2.1.2 on 2018-10-27 16:19
-
-from django.db import migrations
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0015_auto_20181027_1617'),
- ]
-
- operations = [
- migrations.RenameField(
- model_name='specialsnake',
- old_name='image',
- new_name='images',
- ),
- ]
diff --git a/pysite/apps/api/migrations/0017_auto_20181029_1921.py b/pysite/apps/api/migrations/0017_auto_20181029_1921.py
deleted file mode 100644
index 012bda61..00000000
--- a/pysite/apps/api/migrations/0017_auto_20181029_1921.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# Generated by Django 2.1.2 on 2018-10-29 19:21
-
-import django.contrib.postgres.fields
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0016_auto_20181027_1619'),
- ]
-
- operations = [
- migrations.AlterField(
- model_name='specialsnake',
- name='images',
- field=django.contrib.postgres.fields.ArrayField(base_field=models.URLField(), help_text='Images displaying this special snake.', size=None),
- ),
- ]
diff --git a/pysite/apps/api/migrations/0018_messagedeletioncontext.py b/pysite/apps/api/migrations/0018_messagedeletioncontext.py
deleted file mode 100644
index 10428ceb..00000000
--- a/pysite/apps/api/migrations/0018_messagedeletioncontext.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# Generated by Django 2.1.1 on 2018-11-18 20:12
-
-import pysite.apps.api.models
-from django.db import migrations, models
-import django.db.models.deletion
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0017_auto_20181029_1921'),
- ]
-
- operations = [
- migrations.CreateModel(
- name='MessageDeletionContext',
- fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('creation', models.DateTimeField(help_text='When this deletion took place.')),
- ('actor', models.ForeignKey(help_text='The original actor causing this deletion. Could be the author of a manual clean command invocation, the bot when executing automatic actions, or nothing to indicate that the bulk deletion was not issued by us.', null=True, on_delete=django.db.models.deletion.CASCADE, to='api.User')),
- ],
- bases=(pysite.apps.api.models.ModelReprMixin, models.Model),
- ),
- ]
diff --git a/pysite/apps/api/migrations/0018_user_rename.py b/pysite/apps/api/migrations/0018_user_rename.py
deleted file mode 100644
index f88eb5bc..00000000
--- a/pysite/apps/api/migrations/0018_user_rename.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# Generated by Django 2.1.3 on 2018-11-19 20:09
-
-from django.db import migrations
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0017_auto_20181029_1921'),
- ]
-
- operations = [
- migrations.RenameModel(
- old_name='Member',
- new_name='User',
- ),
- ]
diff --git a/pysite/apps/api/migrations/0019_deletedmessage.py b/pysite/apps/api/migrations/0019_deletedmessage.py
deleted file mode 100644
index cb4c59f2..00000000
--- a/pysite/apps/api/migrations/0019_deletedmessage.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# Generated by Django 2.1.1 on 2018-11-18 20:26
-
-import pysite.apps.api.models
-import pysite.apps.api.validators
-from django.db import migrations, models
-import django.db.models.deletion
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0018_messagedeletioncontext'),
- ]
-
- operations = [
- migrations.CreateModel(
- name='DeletedMessage',
- fields=[
- ('id', models.BigIntegerField(help_text='The message ID as taken from Discord.', primary_key=True, serialize=False, validators=[django.core.validators.MinValueValidator(limit_value=0, message='Message IDs cannot be negative.')])),
- ('channel_id', models.BigIntegerField(help_text='The channel ID that this message was sent in, taken from Discord.', validators=[django.core.validators.MinValueValidator(limit_value=0, message='Channel IDs cannot be negative.')])),
- ('content', models.CharField(help_text='The content of this message, taken from Discord.', max_length=2000)),
- ('embeds', django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.jsonb.JSONField(validators=[
-
-
- pysite.apps.api.validators.validate_tag_embed]), help_text='Embeds attached to this message.', size=None)),
- ('author', models.ForeignKey(help_text='The author of this message.', on_delete=django.db.models.deletion.CASCADE, to='api.User')),
- ('deletion_context', models.ForeignKey(help_text='The deletion context this message is part of.', on_delete=django.db.models.deletion.CASCADE, to='api.MessageDeletionContext')),
- ],
- options={
- 'abstract': False,
- },
- bases=(pysite.apps.api.models.ModelReprMixin, models.Model),
- ),
- ]
diff --git a/pysite/apps/api/migrations/0019_user_in_guild.py b/pysite/apps/api/migrations/0019_user_in_guild.py
deleted file mode 100644
index fda008c4..00000000
--- a/pysite/apps/api/migrations/0019_user_in_guild.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Generated by Django 2.1.3 on 2018-11-19 20:30
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0018_user_rename'),
- ]
-
- operations = [
- migrations.AddField(
- model_name='user',
- name='in_guild',
- field=models.BooleanField(default=True, help_text='Whether this user is in our server.'),
- ),
- ]
diff --git a/pysite/apps/api/migrations/0020_add_snake_field_validators.py b/pysite/apps/api/migrations/0020_add_snake_field_validators.py
deleted file mode 100644
index 3b625f9b..00000000
--- a/pysite/apps/api/migrations/0020_add_snake_field_validators.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# Generated by Django 2.1.2 on 2018-11-24 17:11
-
-import django.core.validators
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0019_user_in_guild'),
- ]
-
- operations = [
- migrations.AlterField(
- model_name='snakename',
- name='name',
- field=models.CharField(help_text="The regular name for this snake, e.g. 'Python'.", max_length=100, primary_key=True, serialize=False, validators=[django.core.validators.RegexValidator(regex='^([^0-9])+$')]),
- ),
- migrations.AlterField(
- model_name='snakename',
- name='scientific',
- field=models.CharField(help_text="The scientific name for this snake, e.g. 'Python bivittatus'.", max_length=150, validators=[django.core.validators.RegexValidator(regex='^([^0-9])+$')]),
- ),
- ]
diff --git a/pysite/apps/api/migrations/0020_infraction.py b/pysite/apps/api/migrations/0020_infraction.py
deleted file mode 100644
index 7f7d5a41..00000000
--- a/pysite/apps/api/migrations/0020_infraction.py
+++ /dev/null
@@ -1,30 +0,0 @@
-# Generated by Django 2.1.3 on 2018-11-19 22:02
-
-import pysite.apps.api.models
-from django.db import migrations, models
-import django.db.models.deletion
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0019_user_in_guild'),
- ]
-
- operations = [
- migrations.CreateModel(
- name='Infraction',
- fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('inserted_at', models.DateTimeField(auto_now_add=True, help_text='The date and time of the creation of this infraction.')),
- ('expires_at', models.DateTimeField(help_text="The date and time of the expiration of this infraction. Null if the infraction is permanent or it can't expire.", null=True)),
- ('active', models.BooleanField(default=True, help_text='Whether the infraction is still active.')),
- ('type', models.CharField(choices=[('note', 'Note'), ('warning', 'Warning'), ('mute', 'Mute'), ('ban', 'Ban'), ('kick', 'Kick'), ('superstar', 'Superstar')], help_text='The type of the infraction.', max_length=9)),
- ('reason', models.TextField(help_text='The reason for the infraction.')),
- ('hidden', models.BooleanField(default=False, help_text='Whether the infraction is a shadow infraction.')),
- ('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=(pysite.apps.api.models.ModelReprMixin, models.Model),
- ),
- ]
diff --git a/pysite/apps/api/migrations/0021_add_special_snake_validator.py b/pysite/apps/api/migrations/0021_add_special_snake_validator.py
deleted file mode 100644
index d41b96e5..00000000
--- a/pysite/apps/api/migrations/0021_add_special_snake_validator.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# Generated by Django 2.1.2 on 2018-11-25 14:59
-
-import django.core.validators
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0020_add_snake_field_validators'),
- ]
-
- operations = [
- migrations.AlterField(
- model_name='specialsnake',
- name='name',
- field=models.CharField(help_text='A special snake name.', max_length=140, primary_key=True, serialize=False, validators=[django.core.validators.RegexValidator(regex='^([^0-9])+$')]),
- ),
- ]
diff --git a/pysite/apps/api/migrations/0021_infraction_reason_null.py b/pysite/apps/api/migrations/0021_infraction_reason_null.py
deleted file mode 100644
index 6600f230..00000000
--- a/pysite/apps/api/migrations/0021_infraction_reason_null.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Generated by Django 2.1.3 on 2018-11-21 00:50
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0020_infraction'),
- ]
-
- operations = [
- migrations.AlterField(
- model_name='infraction',
- name='reason',
- field=models.TextField(help_text='The reason for the infraction.', null=True),
- ),
- ]
diff --git a/pysite/apps/api/migrations/0021_merge_20181125_1015.py b/pysite/apps/api/migrations/0021_merge_20181125_1015.py
deleted file mode 100644
index d8eaa510..00000000
--- a/pysite/apps/api/migrations/0021_merge_20181125_1015.py
+++ /dev/null
@@ -1,14 +0,0 @@
-# Generated by Django 2.1.1 on 2018-11-25 10:15
-
-from django.db import migrations
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0020_add_snake_field_validators'),
- ('api', '0019_deletedmessage'),
- ]
-
- operations = [
- ]
diff --git a/pysite/apps/api/migrations/0022_infraction_remove_note.py b/pysite/apps/api/migrations/0022_infraction_remove_note.py
deleted file mode 100644
index eba84610..00000000
--- a/pysite/apps/api/migrations/0022_infraction_remove_note.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Generated by Django 2.1.3 on 2018-11-21 21:07
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0021_infraction_reason_null'),
- ]
-
- operations = [
- migrations.AlterField(
- model_name='infraction',
- name='type',
- field=models.CharField(choices=[('warning', 'Warning'), ('mute', 'Mute'), ('ban', 'Ban'), ('kick', 'Kick'), ('superstar', 'Superstar')], help_text='The type of the infraction.', max_length=9),
- ),
- ]
diff --git a/pysite/apps/api/migrations/0023_merge_infractions_snake_validators.py b/pysite/apps/api/migrations/0023_merge_infractions_snake_validators.py
deleted file mode 100644
index 916f78f2..00000000
--- a/pysite/apps/api/migrations/0023_merge_infractions_snake_validators.py
+++ /dev/null
@@ -1,14 +0,0 @@
-# Generated by Django 2.1.3 on 2018-11-29 19:37
-
-from django.db import migrations
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0022_infraction_remove_note'),
- ('api', '0021_add_special_snake_validator'),
- ]
-
- operations = [
- ]
diff --git a/pysite/apps/api/migrations/0024_add_note_infraction_type.py b/pysite/apps/api/migrations/0024_add_note_infraction_type.py
deleted file mode 100644
index 4adb53b8..00000000
--- a/pysite/apps/api/migrations/0024_add_note_infraction_type.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Generated by Django 2.1.4 on 2019-01-05 14:52
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0023_merge_infractions_snake_validators'),
- ]
-
- operations = [
- migrations.AlterField(
- model_name='infraction',
- name='type',
- field=models.CharField(choices=[('note', 'Note'), ('warning', 'Warning'), ('mute', 'Mute'), ('kick', 'Kick'), ('ban', 'Ban'), ('superstar', 'Superstar')], help_text='The type of the infraction.', max_length=9),
- ),
- ]
diff --git a/pysite/apps/api/migrations/0025_allow_custom_inserted_at_infraction_field.py b/pysite/apps/api/migrations/0025_allow_custom_inserted_at_infraction_field.py
deleted file mode 100644
index 0c02cb91..00000000
--- a/pysite/apps/api/migrations/0025_allow_custom_inserted_at_infraction_field.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# Generated by Django 2.1.4 on 2019-01-06 16:01
-
-import datetime
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0024_add_note_infraction_type'),
- ]
-
- operations = [
- 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.'),
- ),
- ]
diff --git a/pysite/apps/api/migrations/0026_use_proper_default_for_infraction_insertion_date.py b/pysite/apps/api/migrations/0026_use_proper_default_for_infraction_insertion_date.py
deleted file mode 100644
index 56f3b2b8..00000000
--- a/pysite/apps/api/migrations/0026_use_proper_default_for_infraction_insertion_date.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# Generated by Django 2.1.5 on 2019-01-09 19:50
-
-from django.db import migrations, models
-import django.utils.timezone
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0025_allow_custom_inserted_at_infraction_field'),
- ]
-
- operations = [
- migrations.AlterField(
- model_name='infraction',
- name='inserted_at',
- field=models.DateTimeField(default=django.utils.timezone.now, help_text='The date and time of the creation of this infraction.'),
- ),
- ]
diff --git a/pysite/apps/api/migrations/0027_merge_20190120_0852.py b/pysite/apps/api/migrations/0027_merge_20190120_0852.py
deleted file mode 100644
index 6fab4fd0..00000000
--- a/pysite/apps/api/migrations/0027_merge_20190120_0852.py
+++ /dev/null
@@ -1,14 +0,0 @@
-# Generated by Django 2.1.5 on 2019-01-20 08:52
-
-from django.db import migrations
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0026_use_proper_default_for_infraction_insertion_date'),
- ('api', '0021_merge_20181125_1015'),
- ]
-
- operations = [
- ]
diff --git a/pysite/apps/api/migrations/0028_allow_message_content_blank.py b/pysite/apps/api/migrations/0028_allow_message_content_blank.py
deleted file mode 100644
index 6d57db27..00000000
--- a/pysite/apps/api/migrations/0028_allow_message_content_blank.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Generated by Django 2.1.5 on 2019-01-20 09:41
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0027_merge_20190120_0852'),
- ]
-
- operations = [
- migrations.AlterField(
- model_name='deletedmessage',
- name='content',
- field=models.CharField(blank=True, help_text='The content of this message, taken from Discord.', max_length=2000),
- ),
- ]
diff --git a/pysite/apps/api/migrations/0029_add_infraction_type_watch.py b/pysite/apps/api/migrations/0029_add_infraction_type_watch.py
deleted file mode 100644
index c6f88a11..00000000
--- a/pysite/apps/api/migrations/0029_add_infraction_type_watch.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Generated by Django 2.1.5 on 2019-01-20 11:52
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0028_allow_message_content_blank'),
- ]
-
- operations = [
- migrations.AlterField(
- model_name='infraction',
- name='type',
- field=models.CharField(choices=[('note', 'Note'), ('warning', 'Warning'), ('watch', 'Watch'), ('mute', 'Mute'), ('kick', 'Kick'), ('ban', 'Ban'), ('superstar', 'Superstar')], help_text='The type of the infraction.', max_length=9),
- ),
- ]
diff --git a/pysite/apps/api/migrations/0030_reminder.py b/pysite/apps/api/migrations/0030_reminder.py
deleted file mode 100644
index 8448ac8c..00000000
--- a/pysite/apps/api/migrations/0030_reminder.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# Generated by Django 2.1.5 on 2019-01-22 22:17
-
-import pysite.apps.api.models
-from django.db import migrations, models
-import django.db.models.deletion
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0029_add_infraction_type_watch'),
- ]
-
- operations = [
- migrations.CreateModel(
- name='Reminder',
- fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('active', models.BooleanField(default=True, help_text='Whether this reminder is still active. If not, it has been sent out to the user.')),
- ('channel_id', models.BigIntegerField(help_text='The channel ID that this message was sent in, taken from Discord.', validators=[django.core.validators.MinValueValidator(limit_value=0, message='Channel IDs cannot be negative.')])),
- ('content', models.CharField(help_text='The content that the user wants to be reminded of.', max_length=1500)),
- ('expiration', models.DateTimeField(help_text='When this reminder should be sent.')),
- ('author', models.ForeignKey(help_text='The creator of this reminder.', on_delete=django.db.models.deletion.CASCADE, to='api.User')),
- ],
- bases=(pysite.apps.api.models.ModelReprMixin, models.Model),
- ),
- ]
diff --git a/pysite/apps/api/migrations/0031_nomination.py b/pysite/apps/api/migrations/0031_nomination.py
deleted file mode 100644
index f15da5c3..00000000
--- a/pysite/apps/api/migrations/0031_nomination.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# Generated by Django 2.1.5 on 2019-01-27 11:01
-
-import pysite.apps.api.models
-from django.db import migrations, models
-import django.db.models.deletion
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0030_reminder'),
- ]
-
- operations = [
- migrations.CreateModel(
- name='Nomination',
- fields=[
- ('active', models.BooleanField(default=True, help_text='Whether this nomination is still relevant.')),
- ('reason', models.TextField(help_text='Why this user was nominated.')),
- ('user', models.OneToOneField(help_text='The nominated user.', on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='nomination', serialize=False, to='api.User')),
- ('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=(pysite.apps.api.models.ModelReprMixin, models.Model),
- ),
- ]
diff --git a/pysite/apps/api/migrations/0032_botsetting.py b/pysite/apps/api/migrations/0032_botsetting.py
deleted file mode 100644
index b7916dff..00000000
--- a/pysite/apps/api/migrations/0032_botsetting.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# Generated by Django 2.1.5 on 2019-02-07 19:03
-
-import pysite.apps.api.models
-import django.contrib.postgres.fields.jsonb
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0031_nomination'),
- ]
-
- operations = [
- migrations.CreateModel(
- name='BotSetting',
- fields=[
- ('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=(pysite.apps.api.models.ModelReprMixin, models.Model),
- ),
- ]
diff --git a/pysite/apps/api/migrations/0033_create_defcon_settings.py b/pysite/apps/api/migrations/0033_create_defcon_settings.py
deleted file mode 100644
index 830f3fb0..00000000
--- a/pysite/apps/api/migrations/0033_create_defcon_settings.py
+++ /dev/null
@@ -1,30 +0,0 @@
-# Generated by Django 2.1.5 on 2019-02-18 19:30
-
-from django.db import migrations
-
-
-def up(apps, schema_editor):
- BotSetting = apps.get_model('api', 'BotSetting')
- setting = BotSetting(
- name='defcon',
- data={
- 'enabled': False,
- 'days': 0
- }
- ).save()
-
-
-def down(apps, schema_editor): # pragma: no cover - not necessary to test
- BotSetting = apps.get_model('api', 'BotSetting')
- BotSetting.get(name='defcon').delete()
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0032_botsetting'),
- ]
-
- operations = [
- migrations.RunPython(up, down)
- ]
diff --git a/pysite/apps/api/migrations/0034_add_botsetting_name_validator.py b/pysite/apps/api/migrations/0034_add_botsetting_name_validator.py
deleted file mode 100644
index 79aee41e..00000000
--- a/pysite/apps/api/migrations/0034_add_botsetting_name_validator.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# Generated by Django 2.1.5 on 2019-02-18 19:41
-
-import pysite.apps.api.validators
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('api', '0033_create_defcon_settings'),
- ]
-
- operations = [
- migrations.AlterField(
- model_name='botsetting',
- name='name',
- field=models.CharField(max_length=50, primary_key=True, serialize=False, validators=[
- pysite.apps.api.validators.validate_bot_setting_name]),
- ),
- ]
diff --git a/pysite/apps/api/migrations/__init__.py b/pysite/apps/api/migrations/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/pysite/apps/api/migrations/__init__.py
+++ /dev/null
diff --git a/pysite/apps/api/models.py b/pysite/apps/api/models.py
deleted file mode 100644
index 86c99f86..00000000
--- a/pysite/apps/api/models.py
+++ /dev/null
@@ -1,452 +0,0 @@
-from operator import itemgetter
-
-from django.contrib.postgres import fields as pgfields
-from django.core.validators import MaxValueValidator, MinValueValidator, RegexValidator
-from django.db import models
-from django.utils import timezone
-
-from .validators import validate_bot_setting_name, validate_tag_embed
-
-
-class ModelReprMixin:
- """
- Adds a `__repr__` method to the model subclassing this
- mixin which will display the model's class name along
- with all parameters used to construct the object.
- """
-
- def __repr__(self):
- attributes = ' '.join(
- f'{attribute}={value!r}'
- for attribute, value in sorted(
- self.__dict__.items(),
- key=itemgetter(0)
- )
- if not attribute.startswith('_')
- )
- return f'<{self.__class__.__name__}({attributes})>'
-
-
-class BotSetting(ModelReprMixin, models.Model):
- """A configuration entry for the bot."""
-
- name = models.CharField(
- primary_key=True,
- max_length=50,
- validators=(validate_bot_setting_name,)
- )
- data = pgfields.JSONField(
- help_text="The actual settings of this setting."
- )
-
-
-class DocumentationLink(ModelReprMixin, models.Model):
- """A documentation link used by the `!docs` command of the bot."""
-
- package = models.CharField(
- primary_key=True,
- max_length=50,
- help_text="The Python package name that this documentation link belongs to."
- )
- base_url = models.URLField(
- help_text=(
- "The base URL from which documentation will be available for this project. "
- "Used to generate links to various symbols within this package."
- )
- )
- inventory_url = models.URLField(
- help_text="The URL at which the Sphinx inventory is available for this package."
- )
-
- def __str__(self):
- return f"{self.package} - {self.base_url}"
-
-
-class OffTopicChannelName(ModelReprMixin, models.Model):
- name = models.CharField(
- primary_key=True,
- max_length=96,
- validators=(RegexValidator(regex=r'^[a-z0-9-]+$'),),
- help_text="The actual channel name that will be used on our Discord server."
- )
-
- def __str__(self):
- return self.name
-
-
-class Role(ModelReprMixin, models.Model):
- """A role on our Discord server."""
-
- id = models.BigIntegerField( # noqa
- primary_key=True,
- validators=(
- MinValueValidator(
- limit_value=0,
- message="Role IDs cannot be negative."
- ),
- ),
- help_text="The role ID, taken from Discord."
- )
- name = models.CharField(
- max_length=100,
- help_text="The role name, taken from Discord."
- )
- colour = models.IntegerField(
- validators=(
- MinValueValidator(
- limit_value=0,
- message="Colour hex cannot be negative."
- ),
- ),
- help_text="The integer value of the colour of this role from Discord."
- )
- permissions = models.IntegerField(
- validators=(
- MinValueValidator(
- limit_value=0,
- message="Role permissions cannot be negative."
- ),
- MaxValueValidator(
- limit_value=2 << 32,
- message="Role permission bitset exceeds value of having all permissions"
- )
- ),
- help_text="The integer value of the permission bitset of this role from Discord."
- )
-
- def __str__(self):
- return self.name
-
-
-class SnakeFact(ModelReprMixin, models.Model):
- """A snake fact used by the bot's snake cog."""
-
- fact = models.CharField(
- primary_key=True,
- max_length=200,
- help_text="A fact about snakes."
- )
-
- def __str__(self):
- return self.fact
-
-
-class SnakeIdiom(ModelReprMixin, models.Model):
- """A snake idiom used by the snake cog."""
-
- idiom = models.CharField(
- primary_key=True,
- max_length=140,
- help_text="A saying about a snake."
- )
-
- def __str__(self):
- return self.idiom
-
-
-class SnakeName(ModelReprMixin, models.Model):
- """A snake name used by the bot's snake cog."""
-
- name = models.CharField(
- primary_key=True,
- max_length=100,
- help_text="The regular name for this snake, e.g. 'Python'.",
- validators=[RegexValidator(regex=r'^([^0-9])+$')]
- )
- scientific = models.CharField(
- max_length=150,
- help_text="The scientific name for this snake, e.g. 'Python bivittatus'.",
- validators=[RegexValidator(regex=r'^([^0-9])+$')]
- )
-
- def __str__(self):
- return f"{self.name} ({self.scientific})"
-
-
-class SpecialSnake(ModelReprMixin, models.Model):
- """A special snake's name, info and image from our database used by the bot's snake cog."""
-
- name = models.CharField(
- max_length=140,
- primary_key=True,
- help_text='A special snake name.',
- validators=[RegexValidator(regex=r'^([^0-9])+$')]
- )
- info = models.TextField(
- help_text='Info about a special snake.'
- )
- images = pgfields.ArrayField(
- models.URLField(),
- help_text='Images displaying this special snake.'
- )
-
- def __str__(self):
- return self.name
-
-
-class Tag(ModelReprMixin, models.Model):
- """A tag providing (hopefully) useful information."""
-
- title = models.CharField(
- max_length=100,
- help_text=(
- "The title of this tag, shown in searches and providing "
- "a quick overview over what this embed contains."
- ),
- primary_key=True
- )
- embed = pgfields.JSONField(
- help_text="The actual embed shown by this tag.",
- validators=(validate_tag_embed,)
- )
-
- def __str__(self):
- return self.title
-
-
-class User(ModelReprMixin, models.Model):
- """A Discord user."""
-
- id = models.BigIntegerField( # noqa
- primary_key=True,
- validators=(
- MinValueValidator(
- limit_value=0,
- message="User IDs cannot be negative."
- ),
- ),
- help_text="The ID of this user, taken from Discord."
- )
- name = models.CharField(
- max_length=32,
- help_text="The username, taken from Discord."
- )
- discriminator = models.PositiveSmallIntegerField(
- validators=(
- MaxValueValidator(
- limit_value=9999,
- message="Discriminators may not exceed `9999`."
- ),
- ),
- 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."
- ),
- null=True
- )
- roles = models.ManyToManyField(
- Role,
- help_text="Any roles this user has on our server."
- )
- in_guild = models.BooleanField(
- default=True,
- help_text="Whether this user is in our server."
- )
-
- def __str__(self):
- return f"{self.name}#{self.discriminator}"
-
-
-class Message(ModelReprMixin, models.Model):
- id = models.BigIntegerField(
- primary_key=True,
- help_text="The message ID as taken from Discord.",
- validators=(
- MinValueValidator(
- limit_value=0,
- message="Message IDs cannot be negative."
- ),
- )
- )
- author = models.ForeignKey(
- User,
- on_delete=models.CASCADE,
- help_text="The author of this message."
- )
- channel_id = models.BigIntegerField(
- help_text=(
- "The channel ID that this message was "
- "sent in, taken from Discord."
- ),
- validators=(
- MinValueValidator(
- limit_value=0,
- message="Channel IDs cannot be negative."
- ),
- )
- )
- content = models.CharField(
- max_length=2_000,
- help_text="The content of this message, taken from Discord.",
- blank=True
- )
- embeds = pgfields.ArrayField(
- pgfields.JSONField(
- validators=(validate_tag_embed,)
- ),
- help_text="Embeds attached to this message."
- )
-
- class Meta:
- abstract = True
-
-
-class MessageDeletionContext(ModelReprMixin, models.Model):
- actor = models.ForeignKey(
- User,
- on_delete=models.CASCADE,
- help_text=(
- "The original actor causing this deletion. Could be the author "
- "of a manual clean command invocation, the bot when executing "
- "automatic actions, or nothing to indicate that the bulk "
- "deletion was not issued by us."
- ),
- null=True
- )
- creation = models.DateTimeField(
- # Consider whether we want to add a validator here that ensures
- # the deletion context does not take place in the future.
- help_text="When this deletion took place."
- )
-
-
-class DeletedMessage(Message):
- deletion_context = models.ForeignKey(
- MessageDeletionContext,
- help_text="The deletion context this message is part of.",
- on_delete=models.CASCADE
- )
-
-
-class Infraction(ModelReprMixin, models.Model):
- """An infraction for a Discord user."""
-
- TYPE_CHOICES = (
- ("note", "Note"),
- ("warning", "Warning"),
- ("watch", "Watch"),
- ("mute", "Mute"),
- ("kick", "Kick"),
- ("ban", "Ban"),
- ("superstar", "Superstar")
- )
- inserted_at = models.DateTimeField(
- default=timezone.now,
- help_text="The date and time of the creation of this infraction."
- )
- expires_at = models.DateTimeField(
- null=True,
- help_text=(
- "The date and time of the expiration of this infraction. "
- "Null if the infraction is permanent or it can't expire."
- )
- )
- active = models.BooleanField(
- default=True,
- help_text="Whether the infraction is still active."
- )
- user = models.ForeignKey(
- User,
- on_delete=models.CASCADE,
- related_name='infractions_received',
- help_text="The user to which the infraction was applied."
- )
- actor = models.ForeignKey(
- User,
- on_delete=models.CASCADE,
- related_name='infractions_given',
- help_text="The user which applied the infraction."
- )
- type = models.CharField(
- max_length=9,
- choices=TYPE_CHOICES,
- help_text="The type of the infraction."
- )
- reason = models.TextField(
- null=True,
- help_text="The reason for the infraction."
- )
- hidden = models.BooleanField(
- default=False,
- help_text="Whether the infraction is a shadow infraction."
- )
-
- def __str__(self):
- s = f"#{self.id}: {self.type} on {self.user_id}"
- if self.expires_at:
- s += f" until {self.expires_at}"
- if self.hidden:
- s += " (hidden)"
- return s
-
-
-class Reminder(ModelReprMixin, models.Model):
- """A reminder created by a user."""
-
- active = models.BooleanField(
- default=True,
- help_text=(
- "Whether this reminder is still active. "
- "If not, it has been sent out to the user."
- )
- )
- author = models.ForeignKey(
- User,
- on_delete=models.CASCADE,
- help_text="The creator of this reminder."
- )
- channel_id = models.BigIntegerField(
- help_text=(
- "The channel ID that this message was "
- "sent in, taken from Discord."
- ),
- validators=(
- MinValueValidator(
- limit_value=0,
- message="Channel IDs cannot be negative."
- ),
- )
- )
- content = models.CharField(
- max_length=1500,
- help_text="The content that the user wants to be reminded of."
- )
- expiration = models.DateTimeField(
- help_text="When this reminder should be sent."
- )
-
- def __str__(self):
- return f"{self.content} on {self.expiration} by {self.author}"
-
-
-class Nomination(ModelReprMixin, models.Model):
- """A helper nomination created by staff."""
-
- active = models.BooleanField(
- default=True,
- help_text="Whether this nomination is still relevant."
- )
- author = models.ForeignKey(
- User,
- on_delete=models.CASCADE,
- help_text="The staff member that nominated this user.",
- related_name='nomination_set'
- )
- reason = models.TextField(
- help_text="Why this user was nominated."
- )
- user = models.OneToOneField(
- User,
- on_delete=models.CASCADE,
- help_text="The nominated user.",
- primary_key=True,
- related_name='nomination'
- )
- inserted_at = models.DateTimeField(
- auto_now_add=True,
- help_text="The creation date of this nomination."
- )
diff --git a/pysite/apps/api/serializers.py b/pysite/apps/api/serializers.py
deleted file mode 100644
index 9a92313a..00000000
--- a/pysite/apps/api/serializers.py
+++ /dev/null
@@ -1,174 +0,0 @@
-from rest_framework.serializers import ModelSerializer, PrimaryKeyRelatedField, ValidationError
-from rest_framework.validators import UniqueValidator
-from rest_framework_bulk import BulkSerializerMixin
-
-from .models import (
- BotSetting, DeletedMessage,
- DocumentationLink, Infraction,
- MessageDeletionContext, Nomination,
- OffTopicChannelName, Reminder,
- Role, SnakeFact,
- SnakeIdiom, SnakeName,
- SpecialSnake, Tag,
- User
-)
-
-
-class BotSettingSerializer(ModelSerializer):
- class Meta:
- model = BotSetting
- fields = ('name', 'data')
-
-
-class DeletedMessageSerializer(ModelSerializer):
- author = PrimaryKeyRelatedField(
- queryset=User.objects.all()
- )
- deletion_context = PrimaryKeyRelatedField(
- queryset=MessageDeletionContext.objects.all(),
- # This will be overriden in the `create` function
- # of the deletion context serializer.
- required=False
- )
-
- class Meta:
- model = DeletedMessage
- fields = (
- 'id', 'author',
- 'channel_id', 'content',
- 'embeds', 'deletion_context'
- )
-
-
-class MessageDeletionContextSerializer(ModelSerializer):
- deletedmessage_set = DeletedMessageSerializer(many=True)
-
- class Meta:
- model = MessageDeletionContext
- fields = ('actor', 'creation', 'id', 'deletedmessage_set')
- depth = 1
-
- def create(self, validated_data):
- messages = validated_data.pop('deletedmessage_set')
- deletion_context = MessageDeletionContext.objects.create(**validated_data)
- for message in messages:
- DeletedMessage.objects.create(
- deletion_context=deletion_context,
- **message
- )
-
- return deletion_context
-
-
-class DocumentationLinkSerializer(ModelSerializer):
- class Meta:
- model = DocumentationLink
- fields = ('package', 'base_url', 'inventory_url')
-
-
-class InfractionSerializer(ModelSerializer):
- class Meta:
- model = Infraction
- fields = (
- 'id', 'inserted_at', 'expires_at', 'active', 'user', 'actor', 'type', 'reason', 'hidden'
- )
-
- def validate(self, attrs):
- infr_type = attrs.get('type')
-
- expires_at = attrs.get('expires_at')
- if expires_at and infr_type in ('kick', 'warning'):
- raise ValidationError({'expires_at': [f'{infr_type} infractions cannot expire.']})
-
- hidden = attrs.get('hidden')
- if hidden and infr_type in ('superstar',):
- raise ValidationError({'hidden': [f'{infr_type} infractions cannot be hidden.']})
-
- return attrs
-
-
-class ExpandedInfractionSerializer(InfractionSerializer):
- def to_representation(self, instance):
- ret = super().to_representation(instance)
-
- user = User.objects.get(id=ret['user'])
- user_data = UserSerializer(user).data
- ret['user'] = user_data
-
- actor = User.objects.get(id=ret['actor'])
- actor_data = UserSerializer(actor).data
- ret['actor'] = actor_data
-
- return ret
-
-
-class OffTopicChannelNameSerializer(ModelSerializer):
- class Meta:
- model = OffTopicChannelName
- fields = ('name',)
-
- def to_representation(self, obj):
- return obj.name
-
-
-class SnakeFactSerializer(ModelSerializer):
- class Meta:
- model = SnakeFact
- fields = ('fact',)
-
-
-class SnakeIdiomSerializer(ModelSerializer):
- class Meta:
- model = SnakeIdiom
- fields = ('idiom',)
-
-
-class SnakeNameSerializer(ModelSerializer):
- class Meta:
- model = SnakeName
- fields = ('name', 'scientific')
-
-
-class SpecialSnakeSerializer(ModelSerializer):
- class Meta:
- model = SpecialSnake
- fields = ('name', 'images', 'info')
-
-
-class ReminderSerializer(ModelSerializer):
- author = PrimaryKeyRelatedField(queryset=User.objects.all())
-
- class Meta:
- model = Reminder
- fields = ('active', 'author', 'channel_id', 'content', 'expiration', 'id')
-
-
-class RoleSerializer(ModelSerializer):
- class Meta:
- model = Role
- fields = ('id', 'name', 'colour', 'permissions')
-
-
-class TagSerializer(ModelSerializer):
- class Meta:
- model = Tag
- fields = ('title', 'embed')
-
-
-class UserSerializer(BulkSerializerMixin, ModelSerializer):
- roles = PrimaryKeyRelatedField(many=True, queryset=Role.objects.all(), required=False)
-
- class Meta:
- model = User
- fields = ('id', 'avatar_hash', 'name', 'discriminator', 'roles', 'in_guild')
- depth = 1
-
-
-class NominationSerializer(ModelSerializer):
- author = PrimaryKeyRelatedField(queryset=User.objects.all())
- user = PrimaryKeyRelatedField(queryset=User.objects.all())
-
- class Meta:
- model = Nomination
- fields = ('active', 'author', 'reason', 'user', 'inserted_at')
- depth = 1
diff --git a/pysite/apps/api/tests/__init__.py b/pysite/apps/api/tests/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/pysite/apps/api/tests/__init__.py
+++ /dev/null
diff --git a/pysite/apps/api/tests/base.py b/pysite/apps/api/tests/base.py
deleted file mode 100644
index 61829d6f..00000000
--- a/pysite/apps/api/tests/base.py
+++ /dev/null
@@ -1,69 +0,0 @@
-from django.contrib.auth.models import User
-from rest_framework.test import APITestCase
-
-
-test_user, _created = User.objects.get_or_create(
- username='test',
- password='testpass', # noqa: S106
- is_superuser=True,
- is_staff=True
-)
-
-
-class APISubdomainTestCase(APITestCase):
- """
- Configures the test client to use the proper subdomain
- for requests and forces authentication for the test user.
-
- The test user is considered staff and superuser.
- If you want to test for a custom user (for example, to test model permissions),
- create the user, assign the relevant permissions, and use
- `self.client.force_authenticate(user=created_user)` to force authentication
- through the created user.
-
- Using this performs the following niceties for you which ease writing tests:
- - setting the `HTTP_HOST` request header to `api.pythondiscord.local:8000`, and
- - forcing authentication for the test user.
- If you don't want to force authentication (for example, to test a route's response
- for an unauthenticated user), un-force authentication by using the following:
-
- >>> from pysite.apps.api import APISubdomainTestCase
- >>> class UnauthedUserTestCase(APISubdomainTestCase):
- ... def setUp(self):
- ... super().setUp()
- ... self.client.force_authentication(user=None)
- ... def test_can_read_objects_at_my_endpoint(self):
- ... resp = self.client.get('/my-publicly-readable-endpoint')
- ... self.assertEqual(resp.status_code, 200)
- ... def test_cannot_delete_objects_at_my_endpoint(self):
- ... resp = self.client.delete('/my-publicly-readable-endpoint/42')
- ... self.assertEqual(resp.status_code, 401)
-
- Make sure to include the `super().setUp(self)` call, otherwise, you may get
- status code 404 for some URLs due to the missing `HTTP_HOST` header.
-
- ## Example
- Using this in a test case is rather straightforward:
-
- >>> from pysite.apps.api import APISubdomainTestCase
- >>> class MyAPITestCase(APISubdomainTestCase):
- ... def test_that_it_works(self):
- ... response = self.client.get('/my-endpoint')
- ... self.assertEqual(response.status_code, 200)
-
- To reverse URLs of the API host, you need to use `django_hosts`:
-
- >>> from django_hosts.resolvers import reverse
- >>> from pysite.apps.api import APISubdomainTestCase
- >>> class MyReversedTestCase(APISubdomainTestCase):
- ... def test_my_endpoint(self):
- ... url = reverse('user-detail', host='api')
- ... response = self.client.get(url)
- ... self.assertEqual(response.status_code, 200)
- """
-
- def setUp(self):
- super().setUp()
- self.client.defaults['HTTP_HOST'] = 'api.pythondiscord.local:8000'
- self.client.force_authenticate(test_user)
diff --git a/pysite/apps/api/tests/test_deleted_messages.py b/pysite/apps/api/tests/test_deleted_messages.py
deleted file mode 100644
index cd5acab0..00000000
--- a/pysite/apps/api/tests/test_deleted_messages.py
+++ /dev/null
@@ -1,43 +0,0 @@
-from datetime import datetime
-
-from django_hosts.resolvers import reverse
-
-from .base import APISubdomainTestCase
-from ..models import User
-
-
-class DeletedMessagesTests(APISubdomainTestCase):
- @classmethod
- def setUpTestData(cls): # noqa
- cls.author = User.objects.create(
- id=55,
- name='Robbie Rotten',
- discriminator=55,
- avatar_hash=None
- )
-
- cls.data = {
- 'actor': None,
- 'creation': datetime.utcnow().isoformat(),
- 'deletedmessage_set': [
- {
- 'author': cls.author.id,
- 'id': 55,
- 'channel_id': 5555,
- 'content': "Terror Billy is a meanie",
- 'embeds': []
- },
- {
- 'author': cls.author.id,
- 'id': 56,
- 'channel_id': 5555,
- 'content': "If you purge this, you're evil",
- 'embeds': []
- }
- ]
- }
-
- def test_accepts_valid_data(self):
- url = reverse('bot:messagedeletioncontext-list', host='api')
- response = self.client.post(url, data=self.data)
- self.assertEqual(response.status_code, 201)
diff --git a/pysite/apps/api/tests/test_documentation_links.py b/pysite/apps/api/tests/test_documentation_links.py
deleted file mode 100644
index f6c78391..00000000
--- a/pysite/apps/api/tests/test_documentation_links.py
+++ /dev/null
@@ -1,161 +0,0 @@
-from django_hosts.resolvers import reverse
-
-from .base import APISubdomainTestCase
-from ..models import DocumentationLink
-
-
-class UnauthedDocumentationLinkAPITests(APISubdomainTestCase):
- def setUp(self):
- super().setUp()
- self.client.force_authenticate(user=None)
-
- def test_detail_lookup_returns_401(self):
- url = reverse('bot:documentationlink-detail', args=('whatever',), host='api')
- response = self.client.get(url)
-
- self.assertEqual(response.status_code, 401)
-
- def test_list_returns_401(self):
- url = reverse('bot:documentationlink-list', host='api')
- response = self.client.get(url)
-
- self.assertEqual(response.status_code, 401)
-
- def test_create_returns_401(self):
- url = reverse('bot:documentationlink-list', host='api')
- response = self.client.post(url, data={'hi': 'there'})
-
- self.assertEqual(response.status_code, 401)
-
- def test_delete_returns_401(self):
- url = reverse('bot:documentationlink-detail', args=('whatever',), host='api')
- response = self.client.delete(url)
-
- self.assertEqual(response.status_code, 401)
-
-
-class EmptyDatabaseDocumentationLinkAPITests(APISubdomainTestCase):
- def test_detail_lookup_returns_404(self):
- url = reverse('bot:documentationlink-detail', args=('whatever',), host='api')
- response = self.client.get(url)
-
- self.assertEqual(response.status_code, 404)
-
- def test_list_all_returns_empty_list(self):
- url = reverse('bot:documentationlink-list', host='api')
- response = self.client.get(url)
-
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response.json(), [])
-
- def test_delete_returns_404(self):
- url = reverse('bot:documentationlink-detail', args=('whatever',), host='api')
- response = self.client.delete(url)
-
- self.assertEqual(response.status_code, 404)
-
-
-class DetailLookupDocumentationLinkAPITests(APISubdomainTestCase):
- @classmethod
- def setUpTestData(cls): # noqa
- cls.doc_link = DocumentationLink.objects.create(
- package='testpackage',
- base_url='https://example.com',
- inventory_url='https://example.com'
- )
-
- cls.doc_json = {
- 'package': cls.doc_link.package,
- 'base_url': cls.doc_link.base_url,
- 'inventory_url': cls.doc_link.inventory_url
- }
-
- def test_detail_lookup_unknown_package_returns_404(self):
- url = reverse('bot:documentationlink-detail', args=('whatever',), host='api')
- response = self.client.get(url)
-
- self.assertEqual(response.status_code, 404)
-
- def test_detail_lookup_created_package_returns_package(self):
- url = reverse('bot:documentationlink-detail', args=(self.doc_link.package,), host='api')
- response = self.client.get(url)
-
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response.json(), self.doc_json)
-
- def test_list_all_packages_shows_created_package(self):
- url = reverse('bot:documentationlink-list', host='api')
- response = self.client.get(url)
-
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response.json(), [self.doc_json])
-
- def test_create_invalid_body_returns_400(self):
- url = reverse('bot:documentationlink-list', host='api')
- response = self.client.post(url, data={'i': 'am', 'totally': 'valid'})
-
- self.assertEqual(response.status_code, 400)
-
- def test_create_invalid_url_returns_400(self):
- body = {
- 'package': 'example',
- 'base_url': 'https://example.com',
- 'inventory_url': 'totally an url'
- }
-
- url = reverse('bot:documentationlink-list', host='api')
- response = self.client.post(url, data=body)
-
- self.assertEqual(response.status_code, 400)
-
-
-class DocumentationLinkCreationTests(APISubdomainTestCase):
- def setUp(self):
- super().setUp()
-
- self.body = {
- 'package': 'example',
- 'base_url': 'https://example.com',
- 'inventory_url': 'https://docs.example.com'
- }
-
- url = reverse('bot:documentationlink-list', host='api')
- response = self.client.post(url, data=self.body)
-
- self.assertEqual(response.status_code, 201)
-
- def test_package_in_full_list(self):
- url = reverse('bot:documentationlink-list', host='api')
- response = self.client.get(url)
-
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response.json(), [self.body])
-
- def test_detail_lookup_works_with_package(self):
- url = reverse('bot:documentationlink-detail', args=(self.body['package'],), host='api')
- response = self.client.get(url)
-
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response.json(), self.body)
-
-
-class DocumentationLinkDeletionTests(APISubdomainTestCase):
- @classmethod
- def setUpTestData(cls): # noqa
- cls.doc_link = DocumentationLink.objects.create(
- package='example',
- base_url='https://example.com',
- inventory_url='https://docs.example.com'
- )
-
- def test_unknown_package_returns_404(self):
- url = reverse('bot:documentationlink-detail', args=('whatever',), host='api')
- response = self.client.delete(url)
-
- self.assertEqual(response.status_code, 404)
-
- def test_delete_known_package_returns_204(self):
- url = reverse('bot:documentationlink-detail', args=(self.doc_link.package,), host='api')
- response = self.client.delete(url)
-
- self.assertEqual(response.status_code, 204)
diff --git a/pysite/apps/api/tests/test_healthcheck.py b/pysite/apps/api/tests/test_healthcheck.py
deleted file mode 100644
index b0fd71bf..00000000
--- a/pysite/apps/api/tests/test_healthcheck.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from django_hosts.resolvers import reverse
-
-from .base import APISubdomainTestCase
-
-
-class UnauthedHealthcheckAPITests(APISubdomainTestCase):
- def setUp(self):
- super().setUp()
- self.client.force_authenticate(user=None)
-
- def test_can_access_healthcheck_view(self):
- url = reverse('healthcheck', host='api')
- response = self.client.get(url)
-
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response.json(), {'status': 'ok'})
diff --git a/pysite/apps/api/tests/test_infractions.py b/pysite/apps/api/tests/test_infractions.py
deleted file mode 100644
index 7c370c17..00000000
--- a/pysite/apps/api/tests/test_infractions.py
+++ /dev/null
@@ -1,359 +0,0 @@
-from datetime import datetime as dt, timedelta, timezone
-from urllib.parse import quote
-
-from django_hosts.resolvers import reverse
-
-from .base import APISubdomainTestCase
-from ..models import Infraction, User
-
-
-class UnauthenticatedTests(APISubdomainTestCase):
- def setUp(self):
- super().setUp()
- self.client.force_authenticate(user=None)
-
- def test_detail_lookup_returns_401(self):
- url = reverse('bot:infraction-detail', args=(5,), host='api')
- response = self.client.get(url)
-
- self.assertEqual(response.status_code, 401)
-
- def test_list_returns_401(self):
- url = reverse('bot:infraction-list', host='api')
- response = self.client.get(url)
-
- self.assertEqual(response.status_code, 401)
-
- def test_create_returns_401(self):
- url = reverse('bot:infraction-list', host='api')
- response = self.client.post(url, data={'reason': 'Have a nice day.'})
-
- self.assertEqual(response.status_code, 401)
-
- def test_partial_update_returns_401(self):
- url = reverse('bot:infraction-detail', args=(5,), host='api')
- response = self.client.patch(url, data={'reason': 'Have a nice day.'})
-
- self.assertEqual(response.status_code, 401)
-
-
-class InfractionTests(APISubdomainTestCase):
- @classmethod
- def setUpTestData(cls): # noqa
- cls.user = User.objects.create(
- id=5,
- name='james',
- discriminator=1,
- avatar_hash=None
- )
- cls.ban_hidden = Infraction.objects.create(
- user_id=cls.user.id,
- actor_id=cls.user.id,
- type='ban',
- reason='He terk my jerb!',
- hidden=True,
- expires_at=dt(5018, 11, 20, 15, 52, tzinfo=timezone.utc)
- )
- cls.ban_inactive = Infraction.objects.create(
- user_id=cls.user.id,
- actor_id=cls.user.id,
- type='ban',
- reason='James is an ass, and we won\'t be working with him again.',
- active=False
- )
-
- def test_list_all(self):
- url = reverse('bot:infraction-list', host='api')
- response = self.client.get(url)
-
- self.assertEqual(response.status_code, 200)
- infractions = response.json()
-
- self.assertEqual(len(infractions), 2)
- self.assertEqual(infractions[0]['id'], self.ban_hidden.id)
- self.assertEqual(infractions[1]['id'], self.ban_inactive.id)
-
- def test_filter_search(self):
- url = reverse('bot:infraction-list', host='api')
- pattern = quote(r'^James(\s\w+){3},')
- response = self.client.get(f'{url}?search={pattern}')
-
- self.assertEqual(response.status_code, 200)
- infractions = response.json()
-
- self.assertEqual(len(infractions), 1)
- self.assertEqual(infractions[0]['id'], self.ban_inactive.id)
-
- def test_filter_field(self):
- url = reverse('bot:infraction-list', host='api')
- response = self.client.get(f'{url}?type=ban&hidden=true')
-
- self.assertEqual(response.status_code, 200)
- infractions = response.json()
-
- self.assertEqual(len(infractions), 1)
- self.assertEqual(infractions[0]['id'], self.ban_hidden.id)
-
- def test_returns_empty_for_no_match(self):
- url = reverse('bot:infraction-list', host='api')
- response = self.client.get(f'{url}?type=ban&search=poop')
-
- self.assertEqual(response.status_code, 200)
- self.assertEqual(len(response.json()), 0)
-
- def test_ignores_bad_filters(self):
- url = reverse('bot:infraction-list', host='api')
- response = self.client.get(f'{url}?type=ban&hidden=maybe&foo=bar')
-
- self.assertEqual(response.status_code, 200)
- self.assertEqual(len(response.json()), 2)
-
- def test_retrieve_single_from_id(self):
- url = reverse('bot:infraction-detail', args=(self.ban_inactive.id,), host='api')
- response = self.client.get(url)
-
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response.json()['id'], self.ban_inactive.id)
-
- def test_retrieve_returns_404_for_absent_id(self):
- url = reverse('bot:infraction-detail', args=(1337,), host='api')
- response = self.client.get(url)
-
- self.assertEqual(response.status_code, 404)
-
- def test_partial_update(self):
- url = reverse('bot:infraction-detail', args=(self.ban_hidden.id,), host='api')
- data = {
- 'expires_at': '4143-02-15T21:04:31+00:00',
- 'active': False,
- 'reason': 'durka derr'
- }
-
- response = self.client.patch(url, data=data)
- self.assertEqual(response.status_code, 200)
- infraction = Infraction.objects.get(id=self.ban_hidden.id)
-
- # These fields were updated.
- self.assertEqual(infraction.expires_at.isoformat(), data['expires_at'])
- self.assertEqual(infraction.active, data['active'])
- self.assertEqual(infraction.reason, data['reason'])
-
- # These fields are still the same.
- self.assertEqual(infraction.id, self.ban_hidden.id)
- self.assertEqual(infraction.inserted_at, self.ban_hidden.inserted_at)
- self.assertEqual(infraction.user.id, self.ban_hidden.user.id)
- self.assertEqual(infraction.actor.id, self.ban_hidden.actor.id)
- self.assertEqual(infraction.type, self.ban_hidden.type)
- self.assertEqual(infraction.hidden, self.ban_hidden.hidden)
-
- def test_partial_update_returns_400_for_frozen_field(self):
- url = reverse('bot:infraction-detail', args=(self.ban_hidden.id,), host='api')
- data = {'user': 6}
-
- response = self.client.patch(url, data=data)
- self.assertEqual(response.status_code, 400)
- self.assertEqual(response.json(), {
- 'user': ['This field cannot be updated.']
- })
-
-
-class CreationTests(APISubdomainTestCase):
- @classmethod
- def setUpTestData(cls): # noqa
- cls.user = User.objects.create(
- id=5,
- name='james',
- discriminator=1,
- avatar_hash=None
- )
-
- def test_accepts_valid_data(self):
- url = reverse('bot:infraction-list', host='api')
- data = {
- 'user': self.user.id,
- 'actor': self.user.id,
- 'type': 'ban',
- 'reason': 'He terk my jerb!',
- 'hidden': True,
- 'expires_at': '5018-11-20T15:52:00+00:00'
- }
-
- response = self.client.post(url, data=data)
- self.assertEqual(response.status_code, 201)
-
- infraction = Infraction.objects.get(id=response.json()['id'])
- self.assertAlmostEqual(
- infraction.inserted_at,
- dt.now(timezone.utc),
- delta=timedelta(seconds=2)
- )
- self.assertEqual(infraction.expires_at.isoformat(), data['expires_at'])
- self.assertEqual(infraction.user.id, data['user'])
- self.assertEqual(infraction.actor.id, data['actor'])
- self.assertEqual(infraction.type, data['type'])
- self.assertEqual(infraction.reason, data['reason'])
- self.assertEqual(infraction.hidden, data['hidden'])
- self.assertEqual(infraction.active, True)
-
- def test_returns_400_for_missing_user(self):
- url = reverse('bot:infraction-list', host='api')
- data = {
- 'actor': self.user.id,
- 'type': 'kick'
- }
-
- response = self.client.post(url, data=data)
- self.assertEqual(response.status_code, 400)
- self.assertEqual(response.json(), {
- 'user': ['This field is required.']
- })
-
- def test_returns_400_for_bad_user(self):
- url = reverse('bot:infraction-list', host='api')
- data = {
- 'user': 1337,
- 'actor': self.user.id,
- 'type': 'kick'
- }
-
- response = self.client.post(url, data=data)
- self.assertEqual(response.status_code, 400)
- self.assertEqual(response.json(), {
- 'user': ['Invalid pk "1337" - object does not exist.']
- })
-
- def test_returns_400_for_bad_type(self):
- url = reverse('bot:infraction-list', host='api')
- data = {
- 'user': self.user.id,
- 'actor': self.user.id,
- 'type': 'hug'
- }
-
- response = self.client.post(url, data=data)
- self.assertEqual(response.status_code, 400)
- self.assertEqual(response.json(), {
- 'type': ['"hug" is not a valid choice.']
- })
-
- def test_returns_400_for_bad_expired_at_format(self):
- url = reverse('bot:infraction-list', host='api')
- data = {
- 'user': self.user.id,
- 'actor': self.user.id,
- 'type': 'ban',
- 'expires_at': '20/11/5018 15:52:00'
- }
-
- response = self.client.post(url, data=data)
- self.assertEqual(response.status_code, 400)
- self.assertEqual(response.json(), {
- 'expires_at': [
- 'Datetime has wrong format. Use one of these formats instead: '
- 'YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z].'
- ]
- })
-
- def test_returns_400_for_expiring_non_expirable_type(self):
- url = reverse('bot:infraction-list', host='api')
- data = {
- 'user': self.user.id,
- 'actor': self.user.id,
- 'type': 'kick',
- 'expires_at': '5018-11-20T15:52:00+00:00'
- }
-
- response = self.client.post(url, data=data)
- self.assertEqual(response.status_code, 400)
- self.assertEqual(response.json(), {
- 'expires_at': [f'{data["type"]} infractions cannot expire.']
- })
-
- def test_returns_400_for_hidden_non_hideable_type(self):
- url = reverse('bot:infraction-list', host='api')
- data = {
- 'user': self.user.id,
- 'actor': self.user.id,
- 'type': 'superstar',
- 'hidden': True
- }
-
- response = self.client.post(url, data=data)
- self.assertEqual(response.status_code, 400)
- self.assertEqual(response.json(), {
- 'hidden': [f'{data["type"]} infractions cannot be hidden.']
- })
-
-
-class ExpandedTests(APISubdomainTestCase):
- @classmethod
- def setUpTestData(cls): # noqa
- cls.user = User.objects.create(
- id=5,
- name='james',
- discriminator=1,
- avatar_hash=None
- )
- cls.kick = Infraction.objects.create(
- user_id=cls.user.id,
- actor_id=cls.user.id,
- type='kick'
- )
- cls.warning = Infraction.objects.create(
- user_id=cls.user.id,
- actor_id=cls.user.id,
- type='warning'
- )
-
- 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'):
- self.assertTrue(field in obj, msg=f'field "{field}" missing from {key}')
-
- def test_list_expanded(self):
- url = reverse('bot:infraction-list-expanded', host='api')
-
- response = self.client.get(url)
- self.assertEqual(response.status_code, 200)
-
- response_data = response.json()
- self.assertEqual(len(response_data), 2)
-
- for infraction in response_data:
- self.check_expanded_fields(infraction)
-
- def test_create_expanded(self):
- url = reverse('bot:infraction-list-expanded', host='api')
- data = {
- 'user': self.user.id,
- 'actor': self.user.id,
- 'type': 'warning'
- }
-
- response = self.client.post(url, data=data)
- self.assertEqual(response.status_code, 201)
-
- self.assertEqual(len(Infraction.objects.all()), 3)
- self.check_expanded_fields(response.json())
-
- def test_retrieve_expanded(self):
- url = reverse('bot:infraction-detail-expanded', args=(self.warning.id,), host='api')
-
- response = self.client.get(url)
- self.assertEqual(response.status_code, 200)
-
- infraction = response.json()
- self.assertEqual(infraction['id'], self.warning.id)
- self.check_expanded_fields(infraction)
-
- def test_partial_update_expanded(self):
- url = reverse('bot:infraction-detail-expanded', args=(self.kick.id,), host='api')
- data = {'active': False}
-
- response = self.client.patch(url, data=data)
- self.assertEqual(response.status_code, 200)
-
- infraction = Infraction.objects.get(id=self.kick.id)
- self.assertEqual(infraction.active, data['active'])
- self.check_expanded_fields(response.json())
diff --git a/pysite/apps/api/tests/test_models.py b/pysite/apps/api/tests/test_models.py
deleted file mode 100644
index 43d1eb41..00000000
--- a/pysite/apps/api/tests/test_models.py
+++ /dev/null
@@ -1,113 +0,0 @@
-from datetime import datetime as dt, timezone
-
-from django.test import SimpleTestCase
-
-from ..models import (
- BotSetting, DeletedMessage,
- DocumentationLink, Infraction,
- Message, MessageDeletionContext,
- ModelReprMixin, OffTopicChannelName,
- Reminder, Role,
- SnakeFact, SnakeIdiom,
- SnakeName, SpecialSnake,
- Tag, User
-)
-
-
-class SimpleClass(ModelReprMixin):
- def __init__(self, is_what):
- self.the_cake = is_what
-
-
-class ReprMixinTests(SimpleTestCase):
- def setUp(self):
- self.klass = SimpleClass('is a lie')
-
- def test_shows_attributes(self):
- expected = "<SimpleClass(the_cake='is a lie')>"
- self.assertEqual(repr(self.klass), expected)
-
-
-class StringDunderMethodTests(SimpleTestCase):
- def setUp(self):
- self.objects = (
- DeletedMessage(
- id=45,
- author=User(
- id=444, name='bill',
- discriminator=5, avatar_hash=None
- ),
- channel_id=666,
- content="wooey",
- deletion_context=MessageDeletionContext(
- actor=User(
- id=5555, name='shawn',
- discriminator=555, avatar_hash=None
- ),
- creation=dt.utcnow()
- ),
- embeds=[]
- ),
- DocumentationLink(
- 'test', 'http://example.com', 'http://example.com'
- ),
- OffTopicChannelName(name='bob-the-builders-playground'),
- SnakeFact(fact='snakes are cute'),
- SnakeIdiom(idiom='snake snacks'),
- SnakeName(name='python', scientific='3'),
- SpecialSnake(
- name='Pythagoras Pythonista',
- info='The only python snake that is born a triangle'
- ),
- Role(
- id=5, name='test role',
- colour=0x5, permissions=0
- ),
- Message(
- id=45,
- author=User(
- id=444, name='bill',
- discriminator=5, avatar_hash=None
- ),
- channel_id=666,
- content="wooey",
- embeds=[]
- ),
- MessageDeletionContext(
- actor=User(
- id=5555, name='shawn',
- discriminator=555, avatar_hash=None
- ),
- creation=dt.utcnow()
- ),
- Tag(
- title='bob',
- embed={'content': "the builder"}
- ),
- User(
- id=5, name='bob',
- discriminator=1, avatar_hash=None
- ),
- Infraction(
- 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!',
- expires_at=dt(5018, 11, 20, 15, 52, tzinfo=timezone.utc)
- ),
- Reminder(
- author=User(
- id=452, name='billy',
- discriminator=5, avatar_hash=None
- ),
- channel_id=555,
- content="oh no",
- expiration=dt(5018, 11, 20, 15, 52, tzinfo=timezone.utc)
- )
- )
-
- def test_returns_string(self):
- for instance in self.objects:
- self.assertIsInstance(str(instance), str)
diff --git a/pysite/apps/api/tests/test_nominations.py b/pysite/apps/api/tests/test_nominations.py
deleted file mode 100644
index 1f03d1b0..00000000
--- a/pysite/apps/api/tests/test_nominations.py
+++ /dev/null
@@ -1,41 +0,0 @@
-from django_hosts.resolvers import reverse
-
-from .base import APISubdomainTestCase
-from ..models import Nomination, User
-
-
-class NominationTests(APISubdomainTestCase):
- @classmethod
- def setUpTestData(cls): # noqa
- cls.author = User.objects.create(
- id=5152,
- name='Ro Bert',
- discriminator=256,
- avatar_hash=None
- )
- cls.user = cls.author
-
- cls.nomination = Nomination.objects.create(
- author=cls.author,
- reason="he's good",
- user=cls.author
- )
-
- def test_returns_400_on_attempt_to_update_frozen_field(self):
- url = reverse('bot:nomination-detail', args=(self.user.id,), host='api')
- response = self.client.put(
- url,
- data={'inserted_at': 'something bad'}
- )
- self.assertEqual(response.status_code, 400)
- self.assertEqual(response.json(), {
- 'inserted_at': ['This field cannot be updated.']
- })
-
- def test_returns_200_on_successful_update(self):
- url = reverse('bot:nomination-detail', args=(self.user.id,), host='api')
- response = self.client.patch(
- url,
- data={'reason': 'there are many like it, but this test is mine'}
- )
- self.assertEqual(response.status_code, 200)
diff --git a/pysite/apps/api/tests/test_off_topic_channel_names.py b/pysite/apps/api/tests/test_off_topic_channel_names.py
deleted file mode 100644
index 60af1f62..00000000
--- a/pysite/apps/api/tests/test_off_topic_channel_names.py
+++ /dev/null
@@ -1,152 +0,0 @@
-from django_hosts.resolvers import reverse
-
-from .base import APISubdomainTestCase
-from ..models import OffTopicChannelName
-
-
-class UnauthenticatedTests(APISubdomainTestCase):
- def setUp(self):
- super().setUp()
- self.client.force_authenticate(user=None)
-
- def test_cannot_read_off_topic_channel_name_list(self):
- url = reverse('bot:offtopicchannelname-list', host='api')
- response = self.client.get(url)
-
- self.assertEqual(response.status_code, 401)
-
- def test_cannot_read_off_topic_channel_name_list_with_random_item_param(self):
- url = reverse('bot:offtopicchannelname-list', host='api')
- response = self.client.get(f'{url}?random_items=no')
-
- self.assertEqual(response.status_code, 401)
-
-
-class EmptyDatabaseTests(APISubdomainTestCase):
- def test_returns_empty_object(self):
- url = reverse('bot:offtopicchannelname-list', host='api')
- response = self.client.get(url)
-
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response.json(), [])
-
- def test_returns_empty_list_with_get_all_param(self):
- url = reverse('bot:offtopicchannelname-list', host='api')
- response = self.client.get(f'{url}?random_items=5')
-
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response.json(), [])
-
- def test_returns_400_for_bad_random_items_param(self):
- url = reverse('bot:offtopicchannelname-list', host='api')
- response = self.client.get(f'{url}?random_items=totally-a-valid-integer')
-
- self.assertEqual(response.status_code, 400)
- self.assertEqual(response.json(), {
- 'random_items': ["Must be a valid integer."]
- })
-
- def test_returns_400_for_negative_random_items_param(self):
- url = reverse('bot:offtopicchannelname-list', host='api')
- response = self.client.get(f'{url}?random_items=-5')
-
- self.assertEqual(response.status_code, 400)
- self.assertEqual(response.json(), {
- 'random_items': ["Must be a positive integer."]
- })
-
-
-class ListTests(APISubdomainTestCase):
- @classmethod
- def setUpTestData(cls): # noqa
- cls.test_name = OffTopicChannelName.objects.create(name='lemons-lemonade-stand')
- cls.test_name_2 = OffTopicChannelName.objects.create(name='bbq-with-bisk')
-
- def test_returns_name_in_list(self):
- url = reverse('bot:offtopicchannelname-list', host='api')
- response = self.client.get(url)
-
- self.assertEqual(response.status_code, 200)
- self.assertEqual(
- response.json(),
- [
- self.test_name.name,
- self.test_name_2.name
- ]
- )
-
- def test_returns_single_item_with_random_items_param_set_to_1(self):
- url = reverse('bot:offtopicchannelname-list', host='api')
- response = self.client.get(f'{url}?random_items=1')
-
- self.assertEqual(response.status_code, 200)
- self.assertEqual(len(response.json()), 1)
-
-
-class CreationTests(APISubdomainTestCase):
- def setUp(self):
- super().setUp()
-
- url = reverse('bot:offtopicchannelname-list', host='api')
- self.name = "lemonade-shop"
- response = self.client.post(f'{url}?name={self.name}')
- self.assertEqual(response.status_code, 201)
-
- def test_name_in_full_list(self):
- url = reverse('bot:offtopicchannelname-list', host='api')
- response = self.client.get(url)
-
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response.json(), [self.name])
-
- def test_returns_400_for_missing_name_param(self):
- url = reverse('bot:offtopicchannelname-list', host='api')
- response = self.client.post(url)
- self.assertEqual(response.status_code, 400)
- self.assertEqual(response.json(), {
- 'name': ["This query parameter is required."]
- })
-
- def test_returns_400_for_bad_name_param(self):
- url = reverse('bot:offtopicchannelname-list', host='api')
- invalid_names = (
- 'space between words',
- 'UPPERCASE',
- '$$$$$$$$'
- )
-
- for name in invalid_names:
- response = self.client.post(f'{url}?name={name}')
- self.assertEqual(response.status_code, 400)
- self.assertEqual(response.json(), {
- 'name': ["Enter a valid value."]
- })
-
-
-class DeletionTests(APISubdomainTestCase):
- @classmethod
- def setUpTestData(cls): # noqa
- cls.test_name = OffTopicChannelName.objects.create(name='lemons-lemonade-stand')
- cls.test_name_2 = OffTopicChannelName.objects.create(name='bbq-with-bisk')
-
- def test_deleting_unknown_name_returns_404(self):
- url = reverse('bot:offtopicchannelname-detail', args=('unknown-name',), host='api')
- response = self.client.delete(url)
-
- self.assertEqual(response.status_code, 404)
-
- def test_deleting_known_name_returns_204(self):
- url = reverse('bot:offtopicchannelname-detail', args=(self.test_name.name,), host='api')
- response = self.client.delete(url)
-
- self.assertEqual(response.status_code, 204)
-
- def test_name_gets_deleted(self):
- url = reverse('bot:offtopicchannelname-detail', args=(self.test_name_2.name,), host='api')
- response = self.client.delete(url)
-
- self.assertEqual(response.status_code, 204)
-
- url = reverse('bot:offtopicchannelname-list', host='api')
- response = self.client.get(url)
- self.assertNotIn(self.test_name_2.name, response.json())
diff --git a/pysite/apps/api/tests/test_rules.py b/pysite/apps/api/tests/test_rules.py
deleted file mode 100644
index c94f89cc..00000000
--- a/pysite/apps/api/tests/test_rules.py
+++ /dev/null
@@ -1,35 +0,0 @@
-from django_hosts.resolvers import reverse
-
-from .base import APISubdomainTestCase
-from ..views import RulesView
-
-
-class RuleAPITests(APISubdomainTestCase):
- def setUp(self):
- super().setUp()
- self.client.force_authenticate(user=None)
-
- def test_can_access_rules_view(self):
- url = reverse('rules', host='api')
- response = self.client.get(url)
-
- self.assertEqual(response.status_code, 200)
- self.assertIsInstance(response.json(), list)
-
- def test_link_format_query_param_produces_different_results(self):
- url = reverse('rules', host='api')
- markdown_links_response = self.client.get(url + '?link_format=md')
- html_links_response = self.client.get(url + '?link_format=html')
- self.assertNotEqual(
- markdown_links_response.json(),
- html_links_response.json()
- )
-
- def test_format_link_raises_value_error_for_invalid_target(self):
- with self.assertRaises(ValueError):
- RulesView._format_link("a", "b", "c")
-
- def test_get_returns_400_for_wrong_link_format(self):
- url = reverse('rules', host='api')
- response = self.client.get(url + '?link_format=unknown')
- self.assertEqual(response.status_code, 400)
diff --git a/pysite/apps/api/tests/test_snake_names.py b/pysite/apps/api/tests/test_snake_names.py
deleted file mode 100644
index 41dfae63..00000000
--- a/pysite/apps/api/tests/test_snake_names.py
+++ /dev/null
@@ -1,67 +0,0 @@
-from django_hosts.resolvers import reverse
-
-from .base import APISubdomainTestCase
-from ..models import SnakeName
-
-
-class StatusTests(APISubdomainTestCase):
- def setUp(self):
- super().setUp()
- self.client.force_authenticate(user=None)
-
- def test_cannot_read_snake_name_list(self):
- url = reverse('bot:snakename-list', host='api')
- response = self.client.get(url)
-
- self.assertEqual(response.status_code, 401)
-
- def test_cannot_read_snake_names_with_get_all_param(self):
- url = reverse('bot:snakename-list', host='api')
- response = self.client.get(f'{url}?get_all=True')
-
- self.assertEqual(response.status_code, 401)
-
-
-class EmptyDatabaseSnakeNameTests(APISubdomainTestCase):
- def test_endpoint_returns_empty_object(self):
- url = reverse('bot:snakename-list', host='api')
- response = self.client.get(url)
-
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response.json(), {})
-
- def test_endpoint_returns_empty_list_with_get_all_param(self):
- url = reverse('bot:snakename-list', host='api')
- response = self.client.get(f'{url}?get_all=True')
-
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response.json(), [])
-
-
-class SnakeNameListTests(APISubdomainTestCase):
- @classmethod
- def setUpTestData(cls): # noqa
- cls.snake_python = SnakeName.objects.create(name='Python', scientific='Totally.')
-
- def test_endpoint_returns_all_snakes_with_get_all_param(self):
- url = reverse('bot:snakename-list', host='api')
- response = self.client.get(f'{url}?get_all=True')
-
- self.assertEqual(response.status_code, 200)
- self.assertEqual(
- response.json(),
- [
- {
- 'name': self.snake_python.name,
- 'scientific': self.snake_python.scientific
- }
- ]
- )
-
- def test_endpoint_returns_single_snake_without_get_all_param(self):
- url = reverse('bot:snakename-list', host='api')
- response = self.client.get(url)
- self.assertEqual(response.json(), {
- 'name': self.snake_python.name,
- 'scientific': self.snake_python.scientific
- })
diff --git a/pysite/apps/api/tests/test_users.py b/pysite/apps/api/tests/test_users.py
deleted file mode 100644
index 90bc3d30..00000000
--- a/pysite/apps/api/tests/test_users.py
+++ /dev/null
@@ -1,121 +0,0 @@
-from django_hosts.resolvers import reverse
-
-from .base import APISubdomainTestCase
-from ..models import Role, User
-
-
-class UnauthedUserAPITests(APISubdomainTestCase):
- def setUp(self):
- super().setUp()
- self.client.force_authenticate(user=None)
-
- def test_detail_lookup_returns_401(self):
- url = reverse('bot:user-detail', args=('whatever',), host='api')
- response = self.client.get(url)
-
- self.assertEqual(response.status_code, 401)
-
- def test_list_returns_401(self):
- url = reverse('bot:user-list', host='api')
- response = self.client.get(url)
-
- self.assertEqual(response.status_code, 401)
-
- def test_create_returns_401(self):
- url = reverse('bot:user-list', host='api')
- response = self.client.post(url, data={'hi': 'there'})
-
- self.assertEqual(response.status_code, 401)
-
- def test_delete_returns_401(self):
- url = reverse('bot:user-detail', args=('whatever',), host='api')
- response = self.client.delete(url)
-
- self.assertEqual(response.status_code, 401)
-
-
-class CreationTests(APISubdomainTestCase):
- @classmethod
- def setUpTestData(cls): # noqa
- cls.role = Role.objects.create(
- id=5,
- name="Test role pls ignore",
- colour=2,
- permissions=0b01010010101
- )
-
- def test_accepts_valid_data(self):
- url = reverse('bot:user-list', host='api')
- data = {
- 'id': 42,
- 'avatar_hash': "validavatarhashiswear",
- 'name': "Test",
- 'discriminator': 42,
- 'roles': [
- self.role.id
- ],
- 'in_guild': True
- }
-
- response = self.client.post(url, data=data)
- self.assertEqual(response.status_code, 201)
- self.assertEqual(response.json(), data)
-
- user = User.objects.get(id=42)
- self.assertEqual(user.avatar_hash, data['avatar_hash'])
- self.assertEqual(user.name, data['name'])
- self.assertEqual(user.discriminator, data['discriminator'])
- self.assertEqual(user.in_guild, data['in_guild'])
-
- def test_supports_multi_creation(self):
- url = reverse('bot:user-list', host='api')
- data = [
- {
- 'id': 5,
- 'avatar_hash': "hahayes",
- 'name': "test man",
- 'discriminator': 42,
- 'roles': [
- self.role.id
- ],
- 'in_guild': True
- },
- {
- 'id': 8,
- 'avatar_hash': "maybenot",
- 'name': "another test man",
- 'discriminator': 555,
- 'roles': [],
- 'in_guild': False
- }
- ]
-
- response = self.client.post(url, data=data)
- self.assertEqual(response.status_code, 201)
- self.assertEqual(response.json(), data)
-
- def test_returns_400_for_unknown_role_id(self):
- url = reverse('bot:user-list', host='api')
- data = {
- 'id': 5,
- 'avatar_hash': "hahayes",
- 'name': "test man",
- 'discriminator': 42,
- 'roles': [
- 190810291
- ]
- }
-
- response = self.client.post(url, data=data)
- self.assertEqual(response.status_code, 400)
-
- def test_returns_400_for_bad_data(self):
- url = reverse('bot:user-list', host='api')
- data = {
- 'id': True,
- 'avatar_hash': 1902831,
- 'discriminator': "totally!"
- }
-
- response = self.client.post(url, data=data)
- self.assertEqual(response.status_code, 400)
diff --git a/pysite/apps/api/tests/test_validators.py b/pysite/apps/api/tests/test_validators.py
deleted file mode 100644
index d2c0a136..00000000
--- a/pysite/apps/api/tests/test_validators.py
+++ /dev/null
@@ -1,213 +0,0 @@
-from django.core.exceptions import ValidationError
-from django.test import TestCase
-
-from ..validators import (
- validate_bot_setting_name,
- validate_tag_embed
-)
-
-
-REQUIRED_KEYS = (
- 'content', 'fields', 'image', 'title', 'video'
-)
-
-
-class BotSettingValidatorTests(TestCase):
- def test_accepts_valid_names(self):
- validate_bot_setting_name('defcon')
-
- def test_rejects_bad_names(self):
- with self.assertRaises(ValidationError):
- validate_bot_setting_name('bad name')
-
-class TagEmbedValidatorTests(TestCase):
- def test_rejects_non_mapping(self):
- with self.assertRaises(ValidationError):
- validate_tag_embed('non-empty non-mapping')
-
- def test_rejects_missing_required_keys(self):
- with self.assertRaises(ValidationError):
- validate_tag_embed({
- 'unknown': "key"
- })
-
- def test_rejects_one_correct_one_incorrect(self):
- with self.assertRaises(ValidationError):
- validate_tag_embed({
- 'provider': "??",
- 'title': ""
- })
-
- def test_rejects_empty_required_key(self):
- with self.assertRaises(ValidationError):
- validate_tag_embed({
- 'title': ''
- })
-
- def test_rejects_list_as_embed(self):
- with self.assertRaises(ValidationError):
- validate_tag_embed([])
-
- def test_rejects_required_keys_and_unknown_keys(self):
- with self.assertRaises(ValidationError):
- validate_tag_embed({
- 'title': "the duck walked up to the lemonade stand",
- 'and': "he said to the man running the stand"
- })
-
- def test_rejects_too_long_title(self):
- with self.assertRaises(ValidationError):
- validate_tag_embed({
- 'title': 'a' * 257
- })
-
- def test_rejects_too_many_fields(self):
- with self.assertRaises(ValidationError):
- validate_tag_embed({
- 'fields': [{} for _ in range(26)]
- })
-
- def test_rejects_too_long_description(self):
- with self.assertRaises(ValidationError):
- validate_tag_embed({
- 'description': 'd' * 2049
- })
-
- def test_allows_valid_embed(self):
- validate_tag_embed({
- 'title': "My embed",
- 'description': "look at my embed, my embed is amazing"
- })
-
- def test_allows_unvalidated_fields(self):
- validate_tag_embed({
- 'title': "My embed",
- 'provider': "what am I??"
- })
-
- def test_rejects_fields_as_list_of_non_mappings(self):
- with self.assertRaises(ValidationError):
- validate_tag_embed({
- 'fields': ['abc']
- })
-
- def test_rejects_fields_with_unknown_fields(self):
- with self.assertRaises(ValidationError):
- validate_tag_embed({
- 'fields': [
- {
- 'what': "is this field"
- }
- ]
- })
-
- def test_rejects_fields_with_too_long_name(self):
- with self.assertRaises(ValidationError):
- validate_tag_embed({
- 'fields': [
- {
- 'name': "a" * 257
- }
- ]
- })
-
- def test_rejects_one_correct_one_incorrect_field(self):
- with self.assertRaises(ValidationError):
- validate_tag_embed({
- 'fields': [
- {
- 'name': "Totally valid",
- 'value': "LOOK AT ME"
- },
- {
- 'oh': "what is this key?"
- }
- ]
- })
-
- def test_allows_valid_fields(self):
- validate_tag_embed({
- 'fields': [
- {
- 'name': "valid",
- 'value': "field"
- }
- ]
- })
-
- def test_rejects_footer_as_non_mapping(self):
- with self.assertRaises(ValidationError):
- validate_tag_embed({
- 'title': "whatever",
- 'footer': []
- })
-
- def test_rejects_footer_with_unknown_fields(self):
- with self.assertRaises(ValidationError):
- validate_tag_embed({
- 'title': "whatever",
- 'footer': {
- 'duck': "quack"
- }
- })
-
- def test_rejects_footer_with_empty_text(self):
- with self.assertRaises(ValidationError):
- validate_tag_embed({
- 'title': "whatever",
- 'footer': {
- 'text': ""
- }
- })
-
- def test_allows_footer_with_proper_values(self):
- validate_tag_embed({
- 'title': "whatever",
- 'footer': {
- 'text': "django good"
- }
- })
-
- def test_rejects_author_as_non_mapping(self):
- with self.assertRaises(ValidationError):
- validate_tag_embed({
- 'title': "whatever",
- 'author': []
- })
-
- def test_rejects_author_with_unknown_field(self):
- with self.assertRaises(ValidationError):
- validate_tag_embed({
- 'title': "whatever",
- 'author': {
- 'field': "that is unknown"
- }
- })
-
- def test_rejects_author_with_empty_name(self):
- with self.assertRaises(ValidationError):
- validate_tag_embed({
- 'title': "whatever",
- 'author': {
- 'name': ""
- }
- })
-
- def test_rejects_author_with_one_correct_one_incorrect(self):
- with self.assertRaises(ValidationError):
- validate_tag_embed({
- 'title': "whatever",
- 'author': {
- # Relies on "dictionary insertion order remembering" (D.I.O.R.) behaviour
- 'url': "bobswebsite.com",
- 'name': ""
- }
- })
-
- def test_allows_author_with_proper_values(self):
- validate_tag_embed({
- 'title': "whatever",
- 'author': {
- 'name': "Bob"
- }
- })
diff --git a/pysite/apps/api/urls.py b/pysite/apps/api/urls.py
deleted file mode 100644
index 6c89a52e..00000000
--- a/pysite/apps/api/urls.py
+++ /dev/null
@@ -1,86 +0,0 @@
-from django.urls import include, path
-from rest_framework.routers import DefaultRouter
-
-from .views import HealthcheckView, RulesView
-from .viewsets import (
- BotSettingViewSet, DeletedMessageViewSet,
- DocumentationLinkViewSet, InfractionViewSet,
- NominationViewSet, OffTopicChannelNameViewSet,
- ReminderViewSet, RoleViewSet,
- SnakeFactViewSet, SnakeIdiomViewSet,
- SnakeNameViewSet, SpecialSnakeViewSet,
- TagViewSet, UserViewSet
-)
-
-
-# http://www.django-rest-framework.org/api-guide/routers/#defaultrouter
-bot_router = DefaultRouter(trailing_slash=False)
-bot_router.register(
- 'bot-settings',
- BotSettingViewSet
-)
-bot_router.register(
- 'deleted-messages',
- DeletedMessageViewSet
-)
-bot_router.register(
- 'documentation-links',
- DocumentationLinkViewSet
-)
-bot_router.register(
- 'infractions',
- InfractionViewSet
-)
-bot_router.register(
- 'nominations',
- NominationViewSet
-)
-bot_router.register(
- 'off-topic-channel-names',
- OffTopicChannelNameViewSet,
- base_name='offtopicchannelname'
-)
-bot_router.register(
- 'reminders',
- ReminderViewSet
-)
-bot_router.register(
- 'roles',
- RoleViewSet
-)
-bot_router.register(
- 'snake-facts',
- SnakeFactViewSet
-)
-bot_router.register(
- 'snake-idioms',
- SnakeIdiomViewSet
-)
-bot_router.register(
- 'snake-names',
- SnakeNameViewSet,
- base_name='snakename'
-)
-bot_router.register(
- 'special-snakes',
- SpecialSnakeViewSet
-)
-bot_router.register(
- 'tags',
- TagViewSet
-)
-bot_router.register(
- 'users',
- UserViewSet
-)
-
-app_name = 'api'
-urlpatterns = (
- # Build URLs using something like...
- #
- # from django_hosts.resolvers import reverse
- # snake_name_endpoint = reverse('bot:snakename-list', host='api') # `bot/` endpoints
- path('bot/', include((bot_router.urls, 'api'), namespace='bot')),
- path('healthcheck', HealthcheckView.as_view(), name='healthcheck'),
- path('rules', RulesView.as_view(), name='rules')
-)
diff --git a/pysite/apps/api/validators.py b/pysite/apps/api/validators.py
deleted file mode 100644
index 5159cdb3..00000000
--- a/pysite/apps/api/validators.py
+++ /dev/null
@@ -1,164 +0,0 @@
-from collections.abc import Mapping
-
-from django.core.exceptions import ValidationError
-from django.core.validators import MaxLengthValidator, MinLengthValidator
-
-
-def validate_tag_embed_fields(fields):
- field_validators = {
- 'name': (MaxLengthValidator(limit_value=256),),
- 'value': (MaxLengthValidator(limit_value=1024),)
- }
-
- for field in fields:
- if not isinstance(field, Mapping):
- raise ValidationError("Embed fields must be a mapping.")
-
- for field_name, value in field.items():
- if field_name not in field_validators:
- raise ValidationError(f"Unknown embed field field: {field_name!r}.")
-
- for validator in field_validators[field_name]:
- validator(value)
-
-
-def validate_tag_embed_footer(footer):
- field_validators = {
- 'text': (
- MinLengthValidator(
- limit_value=1,
- message="Footer text must not be empty."
- ),
- MaxLengthValidator(limit_value=2048)
- ),
- 'icon_url': (),
- 'proxy_icon_url': ()
- }
-
- if not isinstance(footer, Mapping):
- raise ValidationError("Embed footer must be a mapping.")
-
- for field_name, value in footer.items():
- if field_name not in field_validators:
- raise ValidationError(f"Unknown embed footer field: {field_name!r}.")
-
- for validator in field_validators[field_name]:
- validator(value)
-
-
-def validate_tag_embed_author(author):
- field_validators = {
- 'name': (
- MinLengthValidator(
- limit_value=1,
- message="Embed author name must not be empty."
- ),
- MaxLengthValidator(limit_value=256)
- ),
- 'url': (),
- 'icon_url': (),
- 'proxy_icon_url': ()
- }
-
- if not isinstance(author, Mapping):
- raise ValidationError("Embed author must be a mapping.")
-
- for field_name, value in author.items():
- if field_name not in field_validators:
- raise ValidationError(f"Unknown embed author field: {field_name!r}.")
-
- for validator in field_validators[field_name]:
- validator(value)
-
-
-def validate_tag_embed(embed):
- """
- Validate a JSON document containing an embed as possible to send
- on Discord. This attempts to rebuild the validation used by Discord
- as well as possible by checking for various embed limits so we can
- ensure that any embed we store here will also be accepted as a
- valid embed by the Discord API.
-
- Using this directly is possible, although not intended - you usually
- stick this onto the `validators` keyword argument of model fields.
-
- Example:
-
- >>> from django.contrib.postgres import fields as pgfields
- >>> from django.db import models
- >>> from pysite.apps.api import validate_tag_embed
- >>> class MyMessage(models.Model):
- ... embed = pgfields.JSONField(
- ... validators=(
- ... validate_tag_embed,
- ... )
- ... )
- ... # ...
- ...
-
- Args:
- embed (Dict[str, Union[str, List[dict], dict]]):
- A dictionary describing the contents of this embed.
- See the official documentation for a full reference
- of accepted keys by this dictionary:
- https://discordapp.com/developers/docs/resources/channel#embed-object
-
- Raises:
- ValidationError:
- In case the given embed is deemed invalid, a `ValidationError`
- is raised which in turn will allow Django to display errors
- as appropriate.
- """
-
- all_keys = {
- 'title', 'type', 'description', 'url', 'timestamp',
- 'color', 'footer', 'image', 'thumbnail', 'video',
- 'provider', 'author', 'fields'
- }
- one_required_of = {'description', 'fields', 'image', 'title', 'video'}
- field_validators = {
- 'title': (
- MinLengthValidator(
- limit_value=1,
- message="Embed title must not be empty."
- ),
- MaxLengthValidator(limit_value=256)
- ),
- 'description': (MaxLengthValidator(limit_value=2048),),
- 'fields': (
- MaxLengthValidator(limit_value=25),
- validate_tag_embed_fields
- ),
- 'footer': (validate_tag_embed_footer,),
- 'author': (validate_tag_embed_author,)
- }
-
- if not embed:
- raise ValidationError("Tag embed must not be empty.")
-
- elif not isinstance(embed, Mapping):
- raise ValidationError("Tag embed must be a mapping.")
-
- elif not any(field in embed for field in one_required_of):
- raise ValidationError(f"Tag embed must contain one of the fields {one_required_of}.")
-
- for required_key in one_required_of:
- if required_key in embed and not embed[required_key]:
- raise ValidationError(f"Key {required_key!r} must not be empty.")
-
- for field_name, value in embed.items():
- if field_name not in all_keys:
- raise ValidationError(f"Unknown field name: {field_name!r}")
-
- if field_name in field_validators:
- for validator in field_validators[field_name]:
- validator(value)
-
-
-def validate_bot_setting_name(name):
- KNOWN_SETTINGS = (
- 'defcon',
- )
-
- if name not in KNOWN_SETTINGS:
- raise ValidationError(f"`{name}` is not a known setting name.")
diff --git a/pysite/apps/api/views.py b/pysite/apps/api/views.py
deleted file mode 100644
index f88e1039..00000000
--- a/pysite/apps/api/views.py
+++ /dev/null
@@ -1,161 +0,0 @@
-from rest_framework.exceptions import ParseError
-from rest_framework.response import Response
-from rest_framework.views import APIView
-
-
-class HealthcheckView(APIView):
- """
- Provides a simple view to check that the website is alive and well.
-
- ## Routes
- ### GET /healthcheck
- Returns a simple JSON document showcasing whether the system is working:
-
- >>> {
- ... 'status': 'ok'
- ... }
-
- Seems to be.
-
- ## Authentication
- Does not require any authentication nor permissions.
- """
-
- authentication_classes = ()
- permission_classes = ()
-
- def get(self, request, format=None): # noqa
- return Response({'status': 'ok'})
-
-
-class RulesView(APIView):
- """
- Return a list of the server's rules.
-
- ## Routes
- ### GET /rules
- Returns a JSON array containing the server's rules:
-
- >>> [
- ... "Eat candy.",
- ... "Wake up at 4 AM.",
- ... "Take your medicine."
- ... ]
-
- Since some of the the rules require links, this view
- gives you the option to return rules in either Markdown
- or HTML format by specifying the `link_format` query parameter
- as either `md` or `html`. Specifying a different value than
- `md` or `html` will return 400.
-
- ## Authentication
- Does not require any authentication nor permissions.
- """
-
- authentication_classes = ()
- permission_classes = ()
-
- @staticmethod
- def _format_link(description, link, target):
- """
- Build the markup necessary to render `link` with `description`
- as its description in the given `target` language.
-
- Arguments:
- description (str):
- A textual description of the string. Represents the content
- between the `<a>` tags in HTML, or the content between the
- array brackets in Markdown.
-
- link (str):
- The resulting link that a user should be redirected to
- upon clicking the generated element.
-
- target (str):
- One of `{'md', 'html'}`, denoting the target format that the
- link should be rendered in.
-
- Returns:
- str:
- The link, rendered appropriately for the given `target` format
- using `description` as its textual description.
-
- Raises:
- ValueError:
- If `target` is not `'md'` or `'html'`.
- """
-
- if target == 'html':
- return f'<a href="{link}">{description}</a>'
- elif target == 'md':
- return f'[{description}]({link})'
- else:
- raise ValueError(
- f"Can only template links to `html` or `md`, got `{target}`"
- )
-
- # `format` here is the result format, we have a link format here instead.
- def get(self, request, format=None): # noqa
- link_format = request.query_params.get('link_format', 'md')
- if link_format not in ('html', 'md'):
- raise ParseError(
- f"`format` must be `html` or `md`, got `{format}`."
- )
-
- discord_community_guidelines_link = self._format_link(
- 'Discord Community Guidelines',
- 'https://discordapp.com/guidelines',
- link_format
- )
- channels_page_link = self._format_link(
- 'channels page',
- 'https://pythondiscord.com/about/channels',
- link_format
- )
- google_translate_link = self._format_link(
- 'Google Translate',
- 'https://translate.google.com/',
- link_format
- )
-
- return Response([
- "Be polite, and do not spam.",
- f"Follow the {discord_community_guidelines_link}.",
- (
- "Don't intentionally make other people uncomfortable - if "
- "someone asks you to stop discussing something, you should stop."
- ),
- (
- "Be patient both with users asking "
- "questions, and the users answering them."
- ),
- (
- "We will not help you with anything that might break a law or the "
- "terms of service of any other community, pysite, service, or "
- "otherwise - No piracy, brute-forcing, captcha circumvention, "
- "sneaker bots, or anything else of that nature."
- ),
- (
- "Listen to and respect the staff members - we're "
- "here to help, but we're all human beings."
- ),
- (
- "All discussion should be kept within the relevant "
- "channels for the subject - See the "
- f"{channels_page_link} for more information."
- ),
- (
- "This is an English-speaking server, so please speak English "
- f"to the best of your ability - {google_translate_link} "
- "should be fine if you're not sure."
- ),
- (
- "Keep all discussions safe for work - No gore, nudity, sexual "
- "soliciting, references to suicide, or anything else of that nature"
- ),
- (
- "We do not allow advertisements for communities (including "
- "other Discord servers) or commercial projects - Contact "
- "us directly if you want to discuss a partnership!"
- )
- ])
diff --git a/pysite/apps/api/viewsets.py b/pysite/apps/api/viewsets.py
deleted file mode 100644
index 0bd6149f..00000000
--- a/pysite/apps/api/viewsets.py
+++ /dev/null
@@ -1,890 +0,0 @@
-from django.shortcuts import get_object_or_404
-from django_filters.rest_framework import DjangoFilterBackend
-from rest_framework.decorators import action
-from rest_framework.exceptions import ParseError, ValidationError
-from rest_framework.filters import SearchFilter
-from rest_framework.mixins import (
- CreateModelMixin, DestroyModelMixin,
- ListModelMixin, RetrieveModelMixin,
- UpdateModelMixin
-)
-from rest_framework.response import Response
-from rest_framework.status import HTTP_201_CREATED
-from rest_framework.viewsets import GenericViewSet, ModelViewSet, ViewSet
-from rest_framework_bulk import BulkCreateModelMixin
-
-from .models import (
- BotSetting, DocumentationLink,
- Infraction, MessageDeletionContext,
- Nomination, OffTopicChannelName,
- Reminder, Role,
- SnakeFact, SnakeIdiom,
- SnakeName, SpecialSnake,
- Tag, User
-)
-from .serializers import (
- BotSettingSerializer, DocumentationLinkSerializer,
- ExpandedInfractionSerializer, InfractionSerializer,
- MessageDeletionContextSerializer, NominationSerializer,
- OffTopicChannelNameSerializer, ReminderSerializer,
- RoleSerializer, SnakeFactSerializer,
- SnakeIdiomSerializer, SnakeNameSerializer,
- SpecialSnakeSerializer, TagSerializer,
- UserSerializer
-)
-
-
-class BotSettingViewSet(RetrieveModelMixin, UpdateModelMixin, GenericViewSet):
- """
- View providing update operations on bot setting routes.
- """
-
- serializer_class = BotSettingSerializer
- queryset = BotSetting.objects.all()
-
-
-class DeletedMessageViewSet(CreateModelMixin, GenericViewSet):
- """
- View providing support for posting bulk deletion logs generated by the bot.
-
- ## Routes
- ### POST /bot/deleted-messages
- Post messages from bulk deletion logs.
-
- #### Body schema
- >>> {
- ... # The member ID of the original actor, if applicable.
- ... # If a member ID is given, it must be present on the pysite.
- ... 'actor': Optional[int]
- ... 'creation': datetime,
- ... 'messages': [
- ... {
- ... 'id': int,
- ... 'author': int,
- ... 'channel_id': int,
- ... 'content': str,
- ... 'embeds': [
- ... # Discord embed objects
- ... ]
- ... }
- ... ]
- ... }
-
- #### Status codes
- - 204: returned on success
- """
-
- queryset = MessageDeletionContext.objects.all()
- serializer_class = MessageDeletionContextSerializer
-
-
-class DocumentationLinkViewSet(
- CreateModelMixin, DestroyModelMixin, ListModelMixin, RetrieveModelMixin, GenericViewSet
-):
- """
- View providing management of documentation links used in the bot's `Doc` cog.
-
- ## Routes
- ### GET /bot/documentation-links
- Retrieve all currently stored entries from the database.
-
- #### Response format
- >>> [
- ... {
- ... 'package': 'flask',
- ... 'base_url': 'https://flask.pocoo.org/docs/dev',
- ... 'inventory_url': 'https://flask.pocoo.org/docs/objects.inv'
- ... },
- ... # ...
- ... ]
-
- #### Status codes
- - 200: returned on success
-
- ### GET /bot/documentation-links/<package:str>
- Look up the documentation object for the given `package`.
-
- #### Response format
- >>> {
- ... 'package': 'flask',
- ... 'base_url': 'https://flask.pocoo.org/docs/dev',
- ... 'inventory_url': 'https://flask.pocoo.org/docs/objects.inv'
- ... }
-
- #### Status codes
- - 200: returned on success
- - 404: if no entry for the given `package` exists
-
- ### POST /bot/documentation-links
- Create a new documentation link object.
-
- #### Body schema
- >>> {
- ... 'package': str,
- ... 'base_url': URL,
- ... 'inventory_url': URL
- ... }
-
- #### Status codes
- - 201: returned on success
- - 400: if the request body has invalid fields, see the response for details
-
- ### DELETE /bot/documentation-links/<package:str>
- Delete the entry for the given `package`.
-
- #### Status codes
- - 204: returned on success
- - 404: if the given `package` could not be found
- """
-
- queryset = DocumentationLink.objects.all()
- serializer_class = DocumentationLinkSerializer
- lookup_field = 'package'
-
-
-class InfractionViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, GenericViewSet):
- """
- View providing CRUD operations on infractions for Discord users.
-
- ## Routes
- ### GET /bot/infractions
- Retrieve all infractions.
- May be filtered by the query parameters.
-
- #### Query parameters
- - **active** `bool`: whether the infraction is still active
- - **actor** `int`: snowflake of the user which applied the infraction
- - **hidden** `bool`: whether the infraction is a shadow infraction
- - **search** `str`: regular expression applied to the infraction's reason
- - **type** `str`: the type of the infraction
- - **user** `int`: snowflake of the user to which the infraction was applied
-
- Invalid query parameters are ignored.
-
- #### Response format
- >>> [
- ... {
- ... 'id': 5,
- ... 'inserted_at': '2018-11-22T07:24:06.132307Z',
- ... 'expires_at': '5018-11-20T15:52:00Z',
- ... 'active': False,
- ... 'user': 172395097705414656,
- ... 'actor': 125435062127820800,
- ... 'type': 'ban',
- ... 'reason': 'He terk my jerb!',
- ... 'hidden': True
- ... }
- ... ]
-
- #### Status codes
- - 200: returned on success
-
- ### GET /bot/infractions/<id:int>
- Retrieve a single infraction by ID.
-
- #### Response format
- See `GET /bot/infractions`.
-
- #### Status codes
- - 200: returned on success
- - 404: if an infraction with the given `id` could not be found
-
- ### POST /bot/infractions
- Create a new infraction and return the created infraction.
- Only `actor`, `type`, and `user` are required.
- The `actor` and `user` must be users known by the pysite.
-
- #### Request body
- >>> {
- ... 'active': False,
- ... 'actor': 125435062127820800,
- ... 'expires_at': '5018-11-20T15:52:00+00:00',
- ... 'hidden': True,
- ... 'type': 'ban',
- ... 'reason': 'He terk my jerb!',
- ... 'user': 172395097705414656
- ... }
-
- #### Response format
- See `GET /bot/infractions`.
-
- #### Status codes
- - 201: returned on success
- - 400: if a given user is unknown or a field in the request body is invalid
-
- ### PATCH /bot/infractions/<id:int>
- Update the infraction with the given `id` and return the updated infraction.
- Only `active`, `reason`, and `expires_at` may be updated.
-
- #### Request body
- >>> {
- ... 'active': True,
- ... 'expires_at': '4143-02-15T21:04:31+00:00',
- ... 'reason': 'durka derr'
- ... }
-
- #### Response format
- See `GET /bot/infractions`.
-
- #### Status codes
- - 200: returned on success
- - 400: if a field in the request body is invalid or disallowed
- - 404: if an infraction with the given `id` could not be found
-
- ### Expanded routes
- All routes support expansion of `user` and `actor` in responses. To use an expanded route,
- append `/expanded` to the end of the route e.g. `GET /bot/infractions/expanded`.
-
- #### Response format
- See `GET /bot/users/<snowflake:int>` for the expanded formats of `user` and `actor`. Responses
- are otherwise identical to their non-expanded counterparts.
- """
-
- serializer_class = InfractionSerializer
- queryset = Infraction.objects.all()
- filter_backends = (DjangoFilterBackend, SearchFilter)
- filter_fields = ('user__id', 'actor__id', 'active', 'hidden', 'type')
- search_fields = ('$reason',)
- frozen_fields = ('id', 'inserted_at', 'type', 'user', 'actor', 'hidden')
-
- def partial_update(self, request, *args, **kwargs):
- for field in request.data:
- if field in self.frozen_fields:
- raise ValidationError({field: ['This field cannot be updated.']})
-
- instance = self.get_object()
- serializer = self.get_serializer(instance, data=request.data, partial=True)
- serializer.is_valid(raise_exception=True)
- serializer.save()
-
- return Response(serializer.data)
-
- @action(url_path='expanded', detail=False)
- def list_expanded(self, *args, **kwargs):
- self.serializer_class = ExpandedInfractionSerializer
- return self.list(*args, **kwargs)
-
- @list_expanded.mapping.post
- def create_expanded(self, *args, **kwargs):
- self.serializer_class = ExpandedInfractionSerializer
- return self.create(*args, **kwargs)
-
- @action(url_path='expanded', url_name='detail-expanded', detail=True)
- def retrieve_expanded(self, *args, **kwargs):
- self.serializer_class = ExpandedInfractionSerializer
- return self.retrieve(*args, **kwargs)
-
- @retrieve_expanded.mapping.patch
- def partial_update_expanded(self, *args, **kwargs):
- self.serializer_class = ExpandedInfractionSerializer
- return self.partial_update(*args, **kwargs)
-
-
-class OffTopicChannelNameViewSet(DestroyModelMixin, ViewSet):
- """
- View of off-topic channel names used by the bot
- to rotate our off-topic names on a daily basis.
-
- ## Routes
- ### GET /bot/off-topic-channel-names
- Return all known off-topic channel names from the database.
- If the `random_items` query parameter is given, for example using...
- $ curl api.pythondiscord.local:8000/bot/off-topic-channel-names?random_items=5
- ... then the API will return `5` random items from the database.
-
- #### Response format
- Return a list of off-topic-channel names:
- >>> [
- ... "lemons-lemonade-stand",
- ... "bbq-with-bisk"
- ... ]
-
- #### Status codes
- - 200: returned on success
- - 400: returned when `random_items` is not a positive integer
-
- ### POST /bot/off-topic-channel-names
- Create a new off-topic-channel name in the database.
- The name must be given as a query parameter, for example:
- $ curl api.pythondiscord.local:8000/bot/off-topic-channel-names?name=lemons-lemonade-shop
-
- #### Status codes
- - 201: returned on success
- - 400: if the request body has invalid fields, see the response for details
-
- ### DELETE /bot/off-topic-channel-names/<name:str>
- Delete the off-topic-channel name with the given `name`.
-
- #### Status codes
- - 204: returned on success
- - 404: returned when the given `name` was not found
-
- ## Authentication
- Requires a API token.
- """
-
- lookup_field = 'name'
- serializer_class = OffTopicChannelNameSerializer
-
- def get_object(self):
- queryset = self.get_queryset()
- name = self.kwargs[self.lookup_field]
- return get_object_or_404(queryset, name=name)
-
- def get_queryset(self):
- return OffTopicChannelName.objects.all()
-
- def create(self, request):
- if 'name' in request.query_params:
- create_data = {'name': request.query_params['name']}
- serializer = OffTopicChannelNameSerializer(data=create_data)
- serializer.is_valid(raise_exception=True)
- serializer.save()
- return Response(create_data, status=HTTP_201_CREATED)
-
- else:
- raise ParseError(detail={
- 'name': ["This query parameter is required."]
- })
-
- def list(self, request): # noqa
- if 'random_items' in request.query_params:
- param = request.query_params['random_items']
- try:
- random_count = int(param)
- except ValueError:
- raise ParseError(detail={'random_items': ["Must be a valid integer."]})
-
- if random_count <= 0:
- raise ParseError(detail={
- 'random_items': ["Must be a positive integer."]
- })
-
- queryset = self.get_queryset().order_by('?')[:random_count]
- serialized = self.serializer_class(queryset, many=True)
- return Response(serialized.data)
-
- queryset = self.get_queryset()
- serialized = self.serializer_class(queryset, many=True)
- return Response(serialized.data)
-
-
-class ReminderViewSet(CreateModelMixin, ListModelMixin, DestroyModelMixin, GenericViewSet):
- """
- View providing CRUD access to reminders.
-
- ## Routes
- ### GET /bot/reminders
- Returns all reminders in the database.
-
- #### Response format
- >>> [
- ... {
- ... 'active': True,
- ... 'author': 1020103901030,
- ... 'content': "Make dinner",
- ... 'expiration': '5018-11-20T15:52:00Z'
- ... 'id': 11
- ... },
- ... ...
- ... ]
-
- #### Status codes
- - 200: returned on success
-
- ### POST /bot/reminders
- Create a new reminder.
-
- #### Request body
- >>> {
- ... 'author': int,
- ... 'content': str,
- ... 'expiration': str # ISO-formatted datetime
- ... }
-
- #### Status codes
- - 201: returned on success
- - 400: if the body format is invalid
- - 404: if no user with the given ID could be found
-
- ### DELETE /bot/reminders/<id:int>
- Delete the reminder with the given `id`.
-
- #### Status codes
- - 204: returned on success
- - 404: if a reminder with the given `id` does not exist
-
- ## Authentication
- Requires an API token.
- """
-
- serializer_class = ReminderSerializer
- queryset = Reminder.objects.prefetch_related('author')
- filter_backends = (DjangoFilterBackend, SearchFilter)
- filter_fields = ('active', 'author__id')
-
-
-class RoleViewSet(ModelViewSet):
- """
- View providing CRUD access to the roles on our server, used
- by the bot to keep a mirror of our server's roles on the pysite.
-
- ## Routes
- ### GET /bot/roles
- Returns all roles in the database.
-
- #### Response format
- >>> [
- ... {
- ... 'id': 267628507062992896,
- ... 'name': "Admins",
- ... 'colour': 1337,
- ... 'permissions': 8
- ... }
- ... ]
-
- #### Status codes
- - 200: returned on success
-
- ### GET /bot/roles/<snowflake:int>
- Gets a single role by ID.
-
- #### Response format
- >>> {
- ... 'id': 267628507062992896,
- ... 'name': "Admins",
- ... 'colour': 1337,
- ... 'permissions': 8
- ... }
-
- #### Status codes
- - 200: returned on success
- - 404: if a role with the given `snowflake` could not be found
-
- ### POST /bot/roles
- Adds a single new role.
-
- #### Request body
- >>> {
- ... 'id': int,
- ... 'name': str,
- ... 'colour': int,
- ... 'permissions': int,
- ... }
-
- #### Status codes
- - 201: returned on success
- - 400: if the body format is invalid
-
- ### PUT /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
- ... }
-
- #### Status codes
- - 200: returned on success
- - 400: if the request body was invalid
-
- ### PATCH /bot/roles/<snowflake:int>
- Update the role with the given `snowflake`.
- All fields in the request body are required.
-
- >>> {
- ... 'id': int,
- ... 'name': str,
- ... 'colour': int,
- ... 'permissions': int
- ... }
-
- ### DELETE /bot/roles/<snowflake:int>
- Deletes the role with the given `snowflake`.
-
- #### Status codes
- - 204: returned on success
- - 404: if a role with the given `snowflake` does not exist
- """
-
- queryset = Role.objects.all()
- serializer_class = RoleSerializer
-
-
-class SnakeFactViewSet(ListModelMixin, GenericViewSet):
- """
- View providing snake facts created by the Pydis community in the first code jam.
-
- ## Routes
- ### GET /bot/snake-facts
- Returns snake facts from the database.
-
- #### Response format
- >>> [
- ... {'fact': 'Snakes are dangerous'},
- ... {'fact': 'Except for Python, we all love it'}
- ... ]
-
- #### Status codes
- - 200: returned on success
-
- ## Authentication
- Requires an API token.
- """
-
- serializer_class = SnakeFactSerializer
- queryset = SnakeFact.objects.all()
-
-
-class SnakeIdiomViewSet(ListModelMixin, GenericViewSet):
- """
- View providing snake idioms for the snake cog.
-
- ## Routes
- ### GET /bot/snake-idioms
- Returns snake idioms from the database.
-
- #### Response format
- >>> [
- ... {'idiom': 'Sneky snek'},
- ... {'idiom': 'Snooky Snake'}
- ... ]
-
- #### Status codes
- - 200: returned on success
-
- ## Authentication
- Requires an API token
- """
-
- serializer_class = SnakeIdiomSerializer
- queryset = SnakeIdiom.objects.all()
-
-
-class SnakeNameViewSet(ViewSet):
- """
- View providing snake names for the bot's snake cog from our first code jam's winners.
-
- ## Routes
- ### GET /bot/snake-names
- By default, return a single random snake name along with its name and scientific name.
- If the `get_all` query parameter is given, for example using...
- $ curl api.pythondiscord.local:8000/bot/snake-names?get_all=yes
- ... then the API will return all snake names and scientific names in the database.
-
- #### Response format
- Without `get_all` query parameter:
- >>> {
- ... 'name': "Python",
- ... 'scientific': "Langus greatus"
- ... }
-
- If the database is empty for whatever reason, this will return an empty dictionary.
-
- With `get_all` query parameter:
- >>> [
- ... {'name': "Python 3", 'scientific': "Langus greatus"},
- ... {'name': "Python 2", 'scientific': "Langus decentus"}
- ... ]
-
- #### Status codes
- - 200: returned on success
-
- ## Authentication
- Requires a API token.
- """
-
- serializer_class = SnakeNameSerializer
-
- def get_queryset(self):
- return SnakeName.objects.all()
-
- def list(self, request): # noqa
- if request.query_params.get('get_all'):
- queryset = self.get_queryset()
- serialized = self.serializer_class(queryset, many=True)
- return Response(serialized.data)
-
- single_snake = SnakeName.objects.order_by('?').first()
- if single_snake is not None:
- body = {
- 'name': single_snake.name,
- 'scientific': single_snake.scientific
- }
-
- return Response(body)
-
- return Response({})
-
-
-class SpecialSnakeViewSet(ListModelMixin, GenericViewSet):
- """
- View providing special snake names for our bot's snake cog.
-
- ## Routes
- ### GET /bot/special-snakes
- Returns a list of special snake names.
-
- #### Response Format
- >>> [
- ... {
- ... 'name': 'Snakky sneakatus',
- ... 'info': 'Scary snek',
- ... 'image': 'https://discordapp.com/assets/53ef346458017da2062aca5c7955946b.svg'
- ... }
- ... ]
-
- #### Status codes
- - 200: returned on success
-
- ## Authentication
- Requires an API token.
- """
-
- serializer_class = SpecialSnakeSerializer
- queryset = SpecialSnake.objects.all()
-
-
-class TagViewSet(ModelViewSet):
- """
- View providing CRUD operations on tags shown by our bot.
-
- ## Routes
- ### GET /bot/tags
- Returns all tags in the database.
-
- #### Response format
- >>> [
- ... {
- ... 'title': "resources",
- ... 'embed': {
- ... 'content': "Did you really think I'd put something useful here?"
- ... }
- ... }
- ... ]
-
- #### Status codes
- - 200: returned on success
-
- ### GET /bot/tags/<title:str>
- Gets a single tag by its title.
-
- #### Response format
- >>> {
- ... 'title': "My awesome tag",
- ... 'embed': {
- ... 'content': "totally not filler words"
- ... }
- ... }
-
- #### Status codes
- - 200: returned on success
- - 404: if a tag with the given `title` could not be found
-
- ### POST /bot/tags
- Adds a single tag to the database.
-
- #### Request body
- >>> {
- ... 'title': str,
- ... 'embed': dict
- ... }
-
- The embed structure is the same as the embed structure that the Discord API
- expects. You can view the documentation for it here:
- https://discordapp.com/developers/docs/resources/channel#embed-object
-
- #### Status codes
- - 201: returned on success
- - 400: if one of the given fields is invalid
-
- ### PUT /bot/tags/<title:str>
- Update the tag with the given `title`.
-
- #### Request body
- >>> {
- ... 'title': str,
- ... 'embed': dict
- ... }
-
- The embed structure is the same as the embed structure that the Discord API
- expects. You can view the documentation for it here:
- https://discordapp.com/developers/docs/resources/channel#embed-object
-
- #### Status codes
- - 200: returned on success
- - 400: if the request body was invalid, see response body for details
- - 404: if the tag with the given `title` could not be found
-
- ### PATCH /bot/tags/<title:str>
- Update the tag with the given `title`.
-
- #### Request body
- >>> {
- ... 'title': str,
- ... 'embed': dict
- ... }
-
- The embed structure is the same as the embed structure that the Discord API
- expects. You can view the documentation for it here:
- https://discordapp.com/developers/docs/resources/channel#embed-object
-
- #### Status codes
- - 200: returned on success
- - 400: if the request body was invalid, see response body for details
- - 404: if the tag with the given `title` could not be found
-
- ### DELETE /bot/tags/<title:str>
- Deletes the tag with the given `title`.
-
- #### Status codes
- - 204: returned on success
- - 404: if a tag with the given `title` does not exist
- """
-
- serializer_class = TagSerializer
- queryset = Tag.objects.all()
-
-
-class UserViewSet(BulkCreateModelMixin, ModelViewSet):
- """
- View providing CRUD operations on Discord users through the bot.
-
- ## Routes
- ### GET /bot/users
- Returns all users currently known.
-
- #### Response format
- >>> [
- ... {
- ... 'id': 409107086526644234,
- ... 'avatar': "3ba3c1acce584c20b1e96fc04bbe80eb",
- ... 'name': "Python",
- ... 'discriminator': 4329,
- ... 'roles': [
- ... 352427296948486144,
- ... 270988689419665409,
- ... 277546923144249364,
- ... 458226699344019457
- ... ],
- ... 'in_guild': True
- ... }
- ... ]
-
- #### Status codes
- - 200: returned on success
-
- ### GET /bot/users/<snowflake:int>
- Gets a single user by ID.
-
- #### Response format
- >>> {
- ... 'id': 409107086526644234,
- ... 'avatar': "3ba3c1acce584c20b1e96fc04bbe80eb",
- ... 'name': "Python",
- ... 'discriminator': 4329,
- ... 'roles': [
- ... 352427296948486144,
- ... 270988689419665409,
- ... 277546923144249364,
- ... 458226699344019457
- ... ],
- ... 'in_guild': True
- ... }
-
- #### Status codes
- - 200: returned on success
- - 404: if a user with the given `snowflake` could not be found
-
- ### POST /bot/users
- Adds a single or multiple new users.
- The roles attached to the user(s) must be roles known by the pysite.
-
- #### Request body
- >>> {
- ... 'id': int,
- ... 'avatar': str,
- ... 'name': str,
- ... 'discriminator': int,
- ... 'roles': List[int],
- ... 'in_guild': bool
- ... }
-
- Alternatively, request users can be POSTed as a list of above objects,
- in which case multiple users will be created at once.
-
- #### Status codes
- - 201: returned on success
- - 400: if one of the given roles does not exist, or one of the given fields is invalid
-
- ### PUT /bot/users/<snowflake:int>
- Update the user with the given `snowflake`.
- All fields in the request body are required.
-
- #### Request body
- >>> {
- ... 'id': int,
- ... 'avatar': str,
- ... 'name': str,
- ... 'discriminator': int,
- ... 'roles': List[int],
- ... 'in_guild': bool
- ... }
-
- #### Status codes
- - 200: returned on success
- - 400: if the request body was invalid, see response body for details
- - 404: if the user with the given `snowflake` could not be found
-
- ### PATCH /bot/users/<snowflake:int>
- Update the user with the given `snowflake`.
- All fields in the request body are optional.
-
- #### Request body
- >>> {
- ... 'id': int,
- ... 'avatar': str,
- ... 'name': str,
- ... 'discriminator': int,
- ... 'roles': List[int],
- ... 'in_guild': bool
- ... }
-
- #### Status codes
- - 200: returned on success
- - 400: if the request body was invalid, see response body for details
- - 404: if the user with the given `snowflake` could not be found
-
- ### DELETE /bot/users/<snowflake:int>
- Deletes the user with the given `snowflake`.
-
- #### Status codes
- - 204: returned on success
- - 404: if a user with the given `snowflake` does not exist
- """
-
- serializer_class = UserSerializer
- queryset = User.objects.prefetch_related('roles')
-
-
-class NominationViewSet(ModelViewSet):
- # TODO: doc me
- serializer_class = NominationSerializer
- queryset = Nomination.objects.prefetch_related('author', 'user')
- frozen_fields = ('author', 'inserted_at', 'user')
-
- def update(self, request, *args, **kwargs):
- for field in request.data:
- if field in self.frozen_fields:
- raise ValidationError({field: ['This field cannot be updated.']})
-
- instance = self.get_object()
- serializer = self.get_serializer(instance, data=request.data, partial=True)
- serializer.is_valid(raise_exception=True)
- serializer.save()
-
- return Response(serializer.data)
diff --git a/pysite/apps/home/__init__.py b/pysite/apps/home/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/pysite/apps/home/__init__.py
+++ /dev/null
diff --git a/pysite/apps/home/admin.py b/pysite/apps/home/admin.py
deleted file mode 100644
index 4185d360..00000000
--- a/pysite/apps/home/admin.py
+++ /dev/null
@@ -1,3 +0,0 @@
-# from django.contrib import admin
-
-# Register your models here.
diff --git a/pysite/apps/home/apps.py b/pysite/apps/home/apps.py
deleted file mode 100644
index 90dc7137..00000000
--- a/pysite/apps/home/apps.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from django.apps import AppConfig
-
-
-class HomeConfig(AppConfig):
- name = 'home'
diff --git a/pysite/apps/home/migrations/__init__.py b/pysite/apps/home/migrations/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/pysite/apps/home/migrations/__init__.py
+++ /dev/null
diff --git a/pysite/apps/home/models.py b/pysite/apps/home/models.py
deleted file mode 100644
index 0b4331b3..00000000
--- a/pysite/apps/home/models.py
+++ /dev/null
@@ -1,3 +0,0 @@
-# from django.db import models
-
-# Create your models here.
diff --git a/pysite/apps/home/tests.py b/pysite/apps/home/tests.py
deleted file mode 100644
index 54fac6e8..00000000
--- a/pysite/apps/home/tests.py
+++ /dev/null
@@ -1,9 +0,0 @@
-from django.test import TestCase
-from django_hosts.resolvers import reverse
-
-
-class TestIndexReturns200(TestCase):
- def test_index_returns_200(self):
- url = reverse('index')
- resp = self.client.get(url)
- self.assertEqual(resp.status_code, 200)
diff --git a/pysite/apps/home/urls.py b/pysite/apps/home/urls.py
deleted file mode 100644
index a01e019e..00000000
--- a/pysite/apps/home/urls.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from django.contrib import admin
-from django.urls import path
-from django.views.generic import TemplateView
-
-
-app_name = 'home'
-urlpatterns = [
- path('', TemplateView.as_view(template_name='home/index.html'), name='index'),
- path('admin/', admin.site.urls)
-]
diff --git a/pysite/apps/home/views.py b/pysite/apps/home/views.py
deleted file mode 100644
index fd0e0449..00000000
--- a/pysite/apps/home/views.py
+++ /dev/null
@@ -1,3 +0,0 @@
-# from django.shortcuts import render
-
-# Create your views here.
diff --git a/pysite/apps/wiki/__init__.py b/pysite/apps/wiki/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/pysite/apps/wiki/__init__.py
+++ /dev/null
diff --git a/pysite/apps/wiki/admin.py b/pysite/apps/wiki/admin.py
deleted file mode 100644
index 4185d360..00000000
--- a/pysite/apps/wiki/admin.py
+++ /dev/null
@@ -1,3 +0,0 @@
-# from django.contrib import admin
-
-# Register your models here.
diff --git a/pysite/apps/wiki/apps.py b/pysite/apps/wiki/apps.py
deleted file mode 100644
index fce4708e..00000000
--- a/pysite/apps/wiki/apps.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from django.apps import AppConfig
-
-
-class WikiConfig(AppConfig):
- name = 'wiki'
diff --git a/pysite/apps/wiki/migrations/__init__.py b/pysite/apps/wiki/migrations/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/pysite/apps/wiki/migrations/__init__.py
+++ /dev/null
diff --git a/pysite/apps/wiki/models.py b/pysite/apps/wiki/models.py
deleted file mode 100644
index 0b4331b3..00000000
--- a/pysite/apps/wiki/models.py
+++ /dev/null
@@ -1,3 +0,0 @@
-# from django.db import models
-
-# Create your models here.
diff --git a/pysite/apps/wiki/tests.py b/pysite/apps/wiki/tests.py
deleted file mode 100644
index a79ca8be..00000000
--- a/pysite/apps/wiki/tests.py
+++ /dev/null
@@ -1,3 +0,0 @@
-# from django.test import TestCase
-
-# Create your tests here.
diff --git a/pysite/apps/wiki/views.py b/pysite/apps/wiki/views.py
deleted file mode 100644
index fd0e0449..00000000
--- a/pysite/apps/wiki/views.py
+++ /dev/null
@@ -1,3 +0,0 @@
-# from django.shortcuts import render
-
-# Create your views here.