From 6516eff584065ed121109b7874cb080f72c5e3cc Mon Sep 17 00:00:00 2001 From: Numerlor <25886452+Numerlor@users.noreply.github.com> Date: Mon, 3 Aug 2020 22:16:27 +0200 Subject: Add a validator for package names. Package names are used for stats in the bot and are restricted to the a-z_ char set, a validator is added to accommodate this restriction at the site admin side. --- pydis_site/apps/api/models/bot/documentation_link.py | 7 +++++++ pydis_site/apps/api/tests/test_documentation_links.py | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/pydis_site/apps/api/models/bot/documentation_link.py b/pydis_site/apps/api/models/bot/documentation_link.py index 5a46460b..f77d6f38 100644 --- a/pydis_site/apps/api/models/bot/documentation_link.py +++ b/pydis_site/apps/api/models/bot/documentation_link.py @@ -1,3 +1,4 @@ +from django.core.validators import RegexValidator from django.db import models from pydis_site.apps.api.models.mixins import ModelReprMixin @@ -9,6 +10,12 @@ class DocumentationLink(ModelReprMixin, models.Model): package = models.CharField( primary_key=True, max_length=50, + validators=( + RegexValidator( + regex=r"^[a-z_]+$", + message="Package names can only consist of lowercase a-z letters and underscores." + ), + ), help_text="The Python package name that this documentation link belongs to." ) base_url = models.URLField( diff --git a/pydis_site/apps/api/tests/test_documentation_links.py b/pydis_site/apps/api/tests/test_documentation_links.py index e560a2fd..72e5cadb 100644 --- a/pydis_site/apps/api/tests/test_documentation_links.py +++ b/pydis_site/apps/api/tests/test_documentation_links.py @@ -108,6 +108,17 @@ class DetailLookupDocumentationLinkAPITests(APISubdomainTestCase): self.assertEqual(response.status_code, 400) + def test_create_invalid_package_name_returns_400(self): + test_cases = ("InvalidPackage", "invalid package", "i\u0150valid") + for case in test_cases: + with self.subTest(package_name=case): + body = self.doc_json.copy() + body['package'] = case + 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): -- cgit v1.2.3 From b7f302e4e7afefb16a652d3b0524f4cf4ee835e9 Mon Sep 17 00:00:00 2001 From: Numerlor <25886452+Numerlor@users.noreply.github.com> Date: Tue, 4 Aug 2020 11:29:26 +0200 Subject: Add ascii digits to the validator. Some packages can contain them and are good for stats. --- pydis_site/apps/api/models/bot/documentation_link.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pydis_site/apps/api/models/bot/documentation_link.py b/pydis_site/apps/api/models/bot/documentation_link.py index f77d6f38..e093af59 100644 --- a/pydis_site/apps/api/models/bot/documentation_link.py +++ b/pydis_site/apps/api/models/bot/documentation_link.py @@ -12,8 +12,8 @@ class DocumentationLink(ModelReprMixin, models.Model): max_length=50, validators=( RegexValidator( - regex=r"^[a-z_]+$", - message="Package names can only consist of lowercase a-z letters and underscores." + regex=r"^[a-z0-9_]+$", + message="Package names can only consist of lowercase a-z letters, digits, and underscores." ), ), help_text="The Python package name that this documentation link belongs to." -- cgit v1.2.3 From b242f6d4893dd47c8f07df7dd7bf97f8f1c6631c Mon Sep 17 00:00:00 2001 From: Numerlor <25886452+Numerlor@users.noreply.github.com> Date: Tue, 4 Aug 2020 12:20:52 +0200 Subject: Move package name validator definition. The move prevents it going through the line limit and deeper nesting of parentheses from splitting up the string. --- pydis_site/apps/api/models/bot/documentation_link.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pydis_site/apps/api/models/bot/documentation_link.py b/pydis_site/apps/api/models/bot/documentation_link.py index e093af59..56b47ae6 100644 --- a/pydis_site/apps/api/models/bot/documentation_link.py +++ b/pydis_site/apps/api/models/bot/documentation_link.py @@ -3,6 +3,11 @@ from django.db import models from pydis_site.apps.api.models.mixins import ModelReprMixin +package_name_validator = RegexValidator( + regex=r"^[a-z0-9_]+$", + message="Package names can only consist of lowercase a-z letters, digits, and underscores." +) + class DocumentationLink(ModelReprMixin, models.Model): """A documentation link used by the `!docs` command of the bot.""" @@ -10,12 +15,7 @@ class DocumentationLink(ModelReprMixin, models.Model): package = models.CharField( primary_key=True, max_length=50, - validators=( - RegexValidator( - regex=r"^[a-z0-9_]+$", - message="Package names can only consist of lowercase a-z letters, digits, and underscores." - ), - ), + validators=package_name_validator, help_text="The Python package name that this documentation link belongs to." ) base_url = models.URLField( -- cgit v1.2.3 From 62cf63427e51f2a03eb37d726ca9a6a12fed7374 Mon Sep 17 00:00:00 2001 From: Numerlor <25886452+Numerlor@users.noreply.github.com> Date: Tue, 4 Aug 2020 16:00:43 +0200 Subject: Fix package name validator definition. The validators kwarg expects an iterable of validators, while a validator directly was being supplied. --- pydis_site/apps/api/models/bot/documentation_link.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pydis_site/apps/api/models/bot/documentation_link.py b/pydis_site/apps/api/models/bot/documentation_link.py index 56b47ae6..4f2bd2ab 100644 --- a/pydis_site/apps/api/models/bot/documentation_link.py +++ b/pydis_site/apps/api/models/bot/documentation_link.py @@ -3,9 +3,11 @@ from django.db import models from pydis_site.apps.api.models.mixins import ModelReprMixin -package_name_validator = RegexValidator( - regex=r"^[a-z0-9_]+$", - message="Package names can only consist of lowercase a-z letters, digits, and underscores." +package_name_validator = ( + RegexValidator( + regex=r"^[a-z0-9_]+$", + message="Package names can only consist of lowercase a-z letters, digits, and underscores." + ), ) -- cgit v1.2.3 From 29929b9771fde2179296b653109795da9857d281 Mon Sep 17 00:00:00 2001 From: Numerlor <25886452+Numerlor@users.noreply.github.com> Date: Tue, 4 Aug 2020 16:04:07 +0200 Subject: Create migration for doc package name validator. --- .../0061_documentationlink_packagename_validator.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 pydis_site/apps/api/migrations/0061_documentationlink_packagename_validator.py diff --git a/pydis_site/apps/api/migrations/0061_documentationlink_packagename_validator.py b/pydis_site/apps/api/migrations/0061_documentationlink_packagename_validator.py new file mode 100644 index 00000000..cc734c8a --- /dev/null +++ b/pydis_site/apps/api/migrations/0061_documentationlink_packagename_validator.py @@ -0,0 +1,19 @@ +# Generated by Django 3.0.8 on 2020-08-04 12:25 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0060_populate_filterlists_fix'), + ] + + operations = [ + 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, validators=[django.core.validators.RegexValidator(message='Package names can only consist of lowercase a-z letters, digits, and underscores.', regex='^[a-z0-9_]+$')]), + ), + ] -- cgit v1.2.3 From c00aeb96e1566b7f71ef15cd840895f4e44d5181 Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Wed, 10 Feb 2021 00:36:43 +0000 Subject: Update ALLOWED_HOSTS Add internal domain for API routing and remove legacy routes for pydis.com domains (which are now redirected at the edge) --- pydis_site/settings.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pydis_site/settings.py b/pydis_site/settings.py index 50caab80..300452fa 100644 --- a/pydis_site/settings.py +++ b/pydis_site/settings.py @@ -62,11 +62,7 @@ else: 'admin.pythondiscord.com', 'api.pythondiscord.com', 'staff.pythondiscord.com', - 'pydis.com', - 'api.pydis.com', - 'admin.pydis.com', - 'staff.pydis.com', - 'api.site', + 'pydis-api.default.svc.cluster.local', ] ) SECRET_KEY = env('SECRET_KEY') -- cgit v1.2.3 From 0515475c453769c75e5724b6b47caf1b630b00c1 Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Wed, 10 Feb 2021 00:51:44 +0000 Subject: Update hosts.py --- pydis_site/hosts.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pydis_site/hosts.py b/pydis_site/hosts.py index 898e8cdc..4e55d838 100644 --- a/pydis_site/hosts.py +++ b/pydis_site/hosts.py @@ -4,7 +4,10 @@ from django_hosts import host, patterns host_patterns = patterns( '', host(r'admin', 'pydis_site.apps.admin.urls', name="admin"), - host(r'api', 'pydis_site.apps.api.urls', name='api'), + # External API ingress (over the net) + host(r'api', 'pydis_site.apps.api.urls', name='external_api'), + # Internal API ingress (cluster local) + host(r'pydis-api', 'pydis_site.apps.api.urls', name='internal_api'), host(r'staff', 'pydis_site.apps.staff.urls', name='staff'), host(r'.*', 'pydis_site.apps.home.urls', name=settings.DEFAULT_HOST) ) -- cgit v1.2.3 From e5a0bba5bda972439f2cda87f3966b19e4cd10a5 Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Wed, 10 Feb 2021 00:54:58 +0000 Subject: Don't change the name of the host for subdomains The host is used to build reverse URLs during tests, changing it causes it to fail. --- pydis_site/hosts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydis_site/hosts.py b/pydis_site/hosts.py index 4e55d838..5a837a8b 100644 --- a/pydis_site/hosts.py +++ b/pydis_site/hosts.py @@ -5,7 +5,7 @@ host_patterns = patterns( '', host(r'admin', 'pydis_site.apps.admin.urls', name="admin"), # External API ingress (over the net) - host(r'api', 'pydis_site.apps.api.urls', name='external_api'), + host(r'api', 'pydis_site.apps.api.urls', name='api'), # Internal API ingress (cluster local) host(r'pydis-api', 'pydis_site.apps.api.urls', name='internal_api'), host(r'staff', 'pydis_site.apps.staff.urls', name='staff'), -- cgit v1.2.3 From bceaef8a1921a4013fb525c3b68f41810bec5c14 Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Thu, 18 Feb 2021 09:58:02 +0100 Subject: Update helper count --- pydis_site/templates/home/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydis_site/templates/home/index.html b/pydis_site/templates/home/index.html index a98613a3..04815b7f 100644 --- a/pydis_site/templates/home/index.html +++ b/pydis_site/templates/home/index.html @@ -88,7 +88,7 @@

You can find help with most Python-related problems in one of our help channels. - Our staff of over 50 dedicated expert Helpers are available around the clock + Our staff of over 90 dedicated expert Helpers are available around the clock in every timezone. Whether you're looking to learn the language or working on a complex project, we've got someone who can help you if you get stuck.

-- cgit v1.2.3 From 9ac477385d5ed26d2d8e4f711b2c927cfaf35461 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Mon, 22 Feb 2021 08:21:18 +0200 Subject: Split nomination model to 2 tables and create migrations --- .../api/migrations/0068_split_nomination_tables.py | 60 ++++++++++++++++++++++ .../0069_change_nomination_entry_plural.py | 17 ++++++ pydis_site/apps/api/models/bot/nomination.py | 47 ++++++++++++----- 3 files changed, 112 insertions(+), 12 deletions(-) create mode 100644 pydis_site/apps/api/migrations/0068_split_nomination_tables.py create mode 100644 pydis_site/apps/api/migrations/0069_change_nomination_entry_plural.py diff --git a/pydis_site/apps/api/migrations/0068_split_nomination_tables.py b/pydis_site/apps/api/migrations/0068_split_nomination_tables.py new file mode 100644 index 00000000..2e2313ee --- /dev/null +++ b/pydis_site/apps/api/migrations/0068_split_nomination_tables.py @@ -0,0 +1,60 @@ +# Generated by Django 3.0.11 on 2021-02-21 15:32 + +from django.apps.registry import Apps +from django.db import backends, migrations, models +from django.db.backends.base.schema import BaseDatabaseSchemaEditor +import django.db.models.deletion +import pydis_site.apps.api.models.mixins + + +def migrate_nominations(apps: Apps, schema_editor: BaseDatabaseSchemaEditor) -> None: + Nomination = apps.get_model("api", "Nomination") + NominationEntry = apps.get_model("api", "NominationEntry") + + for nomination in Nomination.objects.all(): + nomination_entry = NominationEntry( + nomination=nomination, + actor=nomination.actor, + reason=nomination.reason, + inserted_at=nomination.inserted_at + ) + nomination_entry.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0067_add_voice_ban_infraction_type'), + ] + + operations = [ + migrations.CreateModel( + name='NominationEntry', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('reason', models.TextField(blank=True, help_text='Why the actor nominated this user.', null=True)), + ('inserted_at', + models.DateTimeField(auto_now_add=True, help_text='The creation date of this nomination entry.')), + ('actor', 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')), + ('nomination', models.ForeignKey(help_text='Nomination to what this entry belongs.', + on_delete=django.db.models.deletion.CASCADE, to='api.Nomination')), + ], + bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), + ), + migrations.RunPython(migrate_nominations), + migrations.RemoveField( + model_name='nomination', + name='actor', + ), + migrations.RemoveField( + model_name='nomination', + name='reason', + ), + migrations.AddField( + model_name='nomination', + name='reviewed', + field=models.BooleanField(default=False, help_text='Whether voting message have been made.'), + ), + ] diff --git a/pydis_site/apps/api/migrations/0069_change_nomination_entry_plural.py b/pydis_site/apps/api/migrations/0069_change_nomination_entry_plural.py new file mode 100644 index 00000000..6bf4ac8c --- /dev/null +++ b/pydis_site/apps/api/migrations/0069_change_nomination_entry_plural.py @@ -0,0 +1,17 @@ +# Generated by Django 3.0.11 on 2021-02-21 16:44 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0068_split_nomination_tables'), + ] + + operations = [ + migrations.AlterModelOptions( + name='nominationentry', + options={'verbose_name_plural': 'nomination entries'}, + ), + ] diff --git a/pydis_site/apps/api/models/bot/nomination.py b/pydis_site/apps/api/models/bot/nomination.py index 11b9e36e..ed6f7d81 100644 --- a/pydis_site/apps/api/models/bot/nomination.py +++ b/pydis_site/apps/api/models/bot/nomination.py @@ -5,23 +5,12 @@ from pydis_site.apps.api.models.mixins import ModelReprMixin class Nomination(ModelReprMixin, models.Model): - """A helper nomination created by staff.""" + """A general helper nomination information created by staff.""" active = models.BooleanField( default=True, help_text="Whether this nomination is still relevant." ) - actor = 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.", - null=True, - blank=True - ) user = models.ForeignKey( User, on_delete=models.CASCADE, @@ -42,6 +31,10 @@ class Nomination(ModelReprMixin, models.Model): help_text="When the nomination was ended.", null=True ) + reviewed = models.BooleanField( + default=False, + help_text="Whether voting message have been made." + ) def __str__(self): """Representation that makes the target and state of the nomination immediately evident.""" @@ -52,3 +45,33 @@ class Nomination(ModelReprMixin, models.Model): """Set the ordering of nominations to most recent first.""" ordering = ("-inserted_at",) + + +class NominationEntry(ModelReprMixin, models.Model): + """A nomination entry created by single staff.""" + + nomination = models.ForeignKey( + Nomination, + on_delete=models.CASCADE, + help_text="Nomination to what this entry belongs." + ) + actor = 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 the actor nominated this user.", + null=True, + blank=True + ) + inserted_at = models.DateTimeField( + auto_now_add=True, + help_text="The creation date of this nomination entry." + ) + + class Meta: + """Meta options for NominationEntry model.""" + + verbose_name_plural = "nomination entries" -- cgit v1.2.3 From ff46d9f59b5da3aff5b8efbca3214ee0d3c064c1 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Mon, 22 Feb 2021 08:21:54 +0200 Subject: Change nominations admin interface and add nomination entries interface --- pydis_site/apps/api/admin.py | 80 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 67 insertions(+), 13 deletions(-) diff --git a/pydis_site/apps/api/admin.py b/pydis_site/apps/api/admin.py index b6fee9d1..449e660e 100644 --- a/pydis_site/apps/api/admin.py +++ b/pydis_site/apps/api/admin.py @@ -21,6 +21,7 @@ from .models import ( Role, User ) +from .models.bot.nomination import NominationEntry admin.site.site_header = "Python Discord | Administration" admin.site.site_title = "Python Discord" @@ -218,7 +219,7 @@ class NominationActorFilter(admin.SimpleListFilter): def lookups(self, request: HttpRequest, model: NominationAdmin) -> Iterable[Tuple[int, str]]: """Selectable values for viewer to filter by.""" - actor_ids = Nomination.objects.order_by().values_list("actor").distinct() + actor_ids = NominationEntry.objects.order_by().values_list("actor").distinct() actors = User.objects.filter(id__in=actor_ids) return ((a.id, a.username) for a in actors) @@ -226,7 +227,10 @@ class NominationActorFilter(admin.SimpleListFilter): """Query to filter the list of Users against.""" if not self.value(): return - return queryset.filter(actor__id=self.value()) + nomination_ids = NominationEntry.objects.filter( + actor__id=self.value() + ).values_list("nomination_id").distinct() + return queryset.filter(id__in=nomination_ids) @admin.register(Nomination) @@ -236,9 +240,6 @@ class NominationAdmin(admin.ModelAdmin): search_fields = ( "user__name", "user__id", - "actor__name", - "actor__id", - "reason", "end_reason" ) @@ -247,27 +248,25 @@ class NominationAdmin(admin.ModelAdmin): list_display = ( "user", "active", - "reason", - "actor", + "reviewed" ) fields = ( "user", "active", - "actor", - "reason", "inserted_at", "ended_at", - "end_reason" + "end_reason", + "reviewed" ) - # only allow reason fields to be edited. + # only allow end reason field to be edited. readonly_fields = ( "user", "active", - "actor", "inserted_at", - "ended_at" + "ended_at", + "reviewed" ) def has_add_permission(self, *args) -> bool: @@ -275,6 +274,61 @@ class NominationAdmin(admin.ModelAdmin): return False +class NominationEntryActorFilter(admin.SimpleListFilter): + """Actor Filter for NominationEntry Admin list page.""" + + title = "Actor" + parameter_name = "actor" + + def lookups(self, request: HttpRequest, model: NominationAdmin) -> Iterable[Tuple[int, str]]: + """Selectable values for viewer to filter by.""" + actor_ids = NominationEntry.objects.order_by().values_list("actor").distinct() + actors = User.objects.filter(id__in=actor_ids) + return ((a.id, a.username) for a in actors) + + def queryset(self, request: HttpRequest, queryset: QuerySet) -> Optional[QuerySet]: + """Query to filter the list of Users against.""" + if not self.value(): + return + return queryset.filter(actor__id=self.value()) + + +@admin.register(NominationEntry) +class NominationEntryAdmin(admin.ModelAdmin): + """Admin formatting for the NominationEntry model.""" + + search_fields = ( + "actor__name", + "actor__id", + "reason", + ) + + list_filter = (NominationEntryActorFilter,) + + list_display = ( + "nomination", + "actor", + ) + + fields = ( + "nomination", + "actor", + "reason", + "inserted_at", + ) + + # only allow reason field to be edited + readonly_fields = ( + "nomination", + "actor", + "inserted_at", + ) + + def has_add_permission(self, request: HttpRequest) -> bool: + """Disable adding new nomination entry from admin.""" + return False + + @admin.register(OffTopicChannelName) class OffTopicChannelNameAdmin(admin.ModelAdmin): """Admin formatting for the OffTopicChannelName model.""" -- cgit v1.2.3 From 4bc55a5e606de8037d52e992dd308f52c86a8e5f Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Mon, 22 Feb 2021 08:22:15 +0200 Subject: Change nominations serializer and add nomination entry serializer --- pydis_site/apps/api/serializers.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/pydis_site/apps/api/serializers.py b/pydis_site/apps/api/serializers.py index 10eb3839..65c69849 100644 --- a/pydis_site/apps/api/serializers.py +++ b/pydis_site/apps/api/serializers.py @@ -26,6 +26,7 @@ from .models import ( Role, User ) +from .models.bot.nomination import NominationEntry class BotSettingSerializer(ModelSerializer): @@ -338,6 +339,21 @@ class UserSerializer(ModelSerializer): raise ValidationError({"id": ["User with ID already present."]}) +class NominationEntrySerializer(ModelSerializer): + """A class providing (de-)serialization of `NominationEntry` instances.""" + + nomination = PrimaryKeyRelatedField( + queryset=Nomination.objects.all(), + write_only=True + ) + + class Meta: + """Metadata defined for the Django REST framework.""" + + model = NominationEntry + fields = ('nomination', 'actor', 'reason', 'inserted_at') + + class NominationSerializer(ModelSerializer): """A class providing (de-)serialization of `Nomination` instances.""" @@ -346,8 +362,8 @@ class NominationSerializer(ModelSerializer): model = Nomination fields = ( - 'id', 'active', 'actor', 'reason', 'user', - 'inserted_at', 'end_reason', 'ended_at') + 'id', 'active', 'user', 'inserted_at', 'end_reason', 'ended_at' + ) class OffensiveMessageSerializer(ModelSerializer): -- cgit v1.2.3 From 51c57d3707c684bb908195f419e53f4ed164ba3b Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Mon, 22 Feb 2021 08:23:04 +0200 Subject: Update nominations viewset GET and POST to make this working with 2-table system --- pydis_site/apps/api/viewsets/bot/nomination.py | 119 ++++++++++++++++++++----- 1 file changed, 96 insertions(+), 23 deletions(-) diff --git a/pydis_site/apps/api/viewsets/bot/nomination.py b/pydis_site/apps/api/viewsets/bot/nomination.py index cf6e262f..8775515c 100644 --- a/pydis_site/apps/api/viewsets/bot/nomination.py +++ b/pydis_site/apps/api/viewsets/bot/nomination.py @@ -15,7 +15,8 @@ from rest_framework.response import Response from rest_framework.viewsets import GenericViewSet from pydis_site.apps.api.models.bot import Nomination -from pydis_site.apps.api.serializers import NominationSerializer +from pydis_site.apps.api.models.bot.nomination import NominationEntry +from pydis_site.apps.api.serializers import NominationEntrySerializer, NominationSerializer class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, GenericViewSet): @@ -29,7 +30,6 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge #### Query parameters - **active** `bool`: whether the nomination is still active - - **actor__id** `int`: snowflake of the user who nominated the user - **user__id** `int`: snowflake of the user who received the nomination - **ordering** `str`: comma-separated sequence of fields to order the returned results @@ -40,12 +40,18 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge ... { ... 'id': 1, ... 'active': false, - ... 'actor': 336843820513755157, - ... 'reason': 'They know how to explain difficult concepts', ... 'user': 336843820513755157, ... 'inserted_at': '2019-04-25T14:02:37.775587Z', ... 'end_reason': 'They were helpered after a staff-vote', - ... 'ended_at': '2019-04-26T15:12:22.123587Z' + ... 'ended_at': '2019-04-26T15:12:22.123587Z', + ... 'entries': [ + ... { + ... 'actor': 336843820513755157, + ... 'reason': 'They know how to explain difficult concepts', + ... 'inserted_at': '2019-04-25T14:02:37.775587Z' + ... } + ... ], + ... 'reviewed': true ... } ... ] @@ -59,12 +65,18 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge >>> { ... 'id': 1, ... 'active': true, - ... 'actor': 336843820513755157, - ... 'reason': 'They know how to explain difficult concepts', ... 'user': 336843820513755157, ... 'inserted_at': '2019-04-25T14:02:37.775587Z', ... 'end_reason': 'They were helpered after a staff-vote', - ... 'ended_at': '2019-04-26T15:12:22.123587Z' + ... 'ended_at': '2019-04-26T15:12:22.123587Z', + ... 'entries': [ + ... { + ... 'actor': 336843820513755157, + ... 'reason': 'They know how to explain difficult concepts', + ... 'inserted_at': '2019-04-25T14:02:37.775587Z' + ... } + ... ], + ... 'reviewed': false ... } ### Status codes @@ -75,8 +87,9 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge Create a new, active nomination returns the created nominations. The `user`, `reason` and `actor` fields are required and the `user` and `actor` need to know by the site. Providing other valid fields - is not allowed and invalid fields are ignored. A `user` is only - allowed one active nomination at a time. + is not allowed and invalid fields are ignored. If `user` already have + active nomination, new nomination entry will be created assigned to + active nomination. #### Request body >>> { @@ -91,7 +104,6 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge #### Status codes - 201: returned on success - 400: returned on failure for one of the following reasons: - - A user already has an active nomination; - The `user` or `actor` are unknown to the site; - The request contained a field that cannot be set at creation. @@ -148,10 +160,44 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge serializer_class = NominationSerializer queryset = Nomination.objects.all() filter_backends = (DjangoFilterBackend, SearchFilter, OrderingFilter) - filter_fields = ('user__id', 'actor__id', 'active') - frozen_fields = ('id', 'actor', 'inserted_at', 'user', 'ended_at') + filter_fields = ('user__id', 'active') + frozen_fields = ('id', 'inserted_at', 'user', 'ended_at') frozen_on_create = ('ended_at', 'end_reason', 'active', 'inserted_at') + def list(self, request: HttpRequest, *args, **kwargs) -> Response: + """ + DRF method for listing Nominations. + + Called by the Django Rest Framework in response to the corresponding HTTP request. + """ + queryset = self.filter_queryset(self.get_queryset()) + data = NominationSerializer(queryset, many=True).data + + for i, nomination in enumerate(data): + entries = NominationEntrySerializer( + NominationEntry.objects.filter(nomination_id=nomination["id"]), + many=True + ).data + data[i]["entries"] = entries + + return Response(data) + + def retrieve(self, request: HttpRequest, *args, **kwargs) -> Response: + """ + DRF method for retrieving a Nomination. + + Called by the Django Rest Framework in response to the corresponding HTTP request. + """ + nomination = self.get_object() + + data = NominationSerializer(nomination).data + data["entries"] = NominationEntrySerializer( + NominationEntry.objects.filter(nomination_id=nomination.id), + many=True + ).data + + return Response(data) + def create(self, request: HttpRequest, *args, **kwargs) -> Response: """ DRF method for creating a Nomination. @@ -163,19 +209,46 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge raise ValidationError({field: ['This field cannot be set at creation.']}) user_id = request.data.get("user") - if Nomination.objects.filter(active=True, user__id=user_id).exists(): - raise ValidationError({'active': ['There can only be one active nomination.']}) + nomination_filter = Nomination.objects.filter(active=True, user__id=user_id) + + if not nomination_filter.exists(): + serializer = NominationSerializer( + data=ChainMap( + request.data, + {"active": True} + ) + ) + serializer.is_valid(raise_exception=True) + nomination = Nomination.objects.create(**serializer.validated_data) - serializer = self.get_serializer( - data=ChainMap( - request.data, - {"active": True} + # Serializer truncate unnecessary data away + entry_serializer = NominationEntrySerializer( + data=ChainMap(request.data, {"nomination": nomination.id}) ) + entry_serializer.is_valid(raise_exception=True) + + entry = NominationEntry.objects.create(**entry_serializer.validated_data) + + data = NominationSerializer(nomination).data + data["entries"] = NominationEntrySerializer([entry], many=True).data + + headers = self.get_success_headers(data) + return Response(data, status=status.HTTP_201_CREATED, headers=headers) + + entry_serializer = NominationEntrySerializer( + data=ChainMap(request.data, {"nomination": nomination_filter[0].id}) ) - serializer.is_valid(raise_exception=True) - self.perform_create(serializer) - headers = self.get_success_headers(serializer.data) - return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) + entry_serializer.is_valid(raise_exception=True) + NominationEntry.objects.create(**entry_serializer.validated_data) + + data = NominationSerializer(nomination_filter[0]).data + data["entries"] = NominationEntrySerializer( + NominationEntry.objects.filter(nomination_id=nomination_filter[0].id), + many=True + ).data + + headers = self.get_success_headers(data) + return Response(data, status=status.HTTP_201_CREATED, headers=headers) def partial_update(self, request: HttpRequest, *args, **kwargs) -> Response: """ -- cgit v1.2.3 From 71388fff77dcb2b074e02dfe33133d779c9afa2d Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Mon, 22 Feb 2021 09:00:54 +0200 Subject: Add reviewed field to nomination serializer --- pydis_site/apps/api/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydis_site/apps/api/serializers.py b/pydis_site/apps/api/serializers.py index 65c69849..96f28aee 100644 --- a/pydis_site/apps/api/serializers.py +++ b/pydis_site/apps/api/serializers.py @@ -362,7 +362,7 @@ class NominationSerializer(ModelSerializer): model = Nomination fields = ( - 'id', 'active', 'user', 'inserted_at', 'end_reason', 'ended_at' + 'id', 'active', 'user', 'inserted_at', 'end_reason', 'ended_at', 'reviewed' ) -- cgit v1.2.3 From e17da1f2e991710beaabd37701ce06a57f7b4a77 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Mon, 22 Feb 2021 09:01:18 +0200 Subject: Migrate PATCH request for 2-table nominations system --- pydis_site/apps/api/viewsets/bot/nomination.py | 70 ++++++++++++++++++++++---- 1 file changed, 60 insertions(+), 10 deletions(-) diff --git a/pydis_site/apps/api/viewsets/bot/nomination.py b/pydis_site/apps/api/viewsets/bot/nomination.py index 8775515c..14dee9bc 100644 --- a/pydis_site/apps/api/viewsets/bot/nomination.py +++ b/pydis_site/apps/api/viewsets/bot/nomination.py @@ -112,18 +112,20 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge The PATCH route can be used for three distinct operations: 1. Updating the `reason` of `active` nomination; - 2. Ending an `active` nomination; - 3. Updating the `end_reason` or `reason` field of an `inactive` nomination. + 2. Updating `reviewed` field of `active` nomination. + 3. Ending an `active` nomination; + 4. Updating the `end_reason` or `reason` field of an `inactive` nomination. While the response format and status codes are the same for all three operations (see below), the request bodies vary depending on the operation. For all operations it holds that providing other valid fields is not allowed and invalid fields are ignored. - ### 1. Updating the `reason` of `active` nomination + ### 1. Updating the `reason` of `active` nomination. Actor field is required. #### Request body >>> { ... 'reason': 'He would make a great helper', + ... 'actor': 409107086526644234 ... } #### Response format @@ -134,7 +136,16 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge - 400: if a field in the request body is invalid or disallowed - 404: if an infraction with the given `id` could not be found - ### 2. Ending an `active` nomination + ### 2. Setting nomination `reviewed` + + #### Request body + >>> { + ... 'reviewed': True + ... } + + See operation 1 for the response format and status codes. + + ### 3. Ending an `active` nomination #### Request body >>> { @@ -144,11 +155,13 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge See operation 1 for the response format and status codes. - ### 3. Updating the `end_reason` or `reason` field of an `inactive` nomination. + ### 4. Updating the `end_reason` or `reason` field of an `inactive` nomination. + Actor field is required when updating reason. #### Request body >>> { ... 'reason': 'Updated reason for this nomination', + ... 'actor': 409107086526644234, ... 'end_reason': 'Updated end_reason for this nomination', ... } @@ -162,7 +175,7 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge filter_backends = (DjangoFilterBackend, SearchFilter, OrderingFilter) filter_fields = ('user__id', 'active') frozen_fields = ('id', 'inserted_at', 'user', 'ended_at') - frozen_on_create = ('ended_at', 'end_reason', 'active', 'inserted_at') + frozen_on_create = ('ended_at', 'end_reason', 'active', 'inserted_at', 'reviewed') def list(self, request: HttpRequest, *args, **kwargs) -> Response: """ @@ -274,8 +287,20 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge {'end_reason': ["An active nomination can't have an end reason."]} ) + elif 'reviewed' in data: + # 2. We're setting nomination reviewed + if not instance.active: + raise ValidationError( + {'reviewed': 'This field cannot be set if nomination is inactive.'} + ) + + if 'active' in data: + raise ValidationError( + {'active': 'This field cannot be set same time than ending nomination.'} + ) + elif instance.active and not data['active']: - # 2. We're ending an active nomination. + # 3. We're ending an active nomination. if 'reason' in data: raise ValidationError( {'reason': ['This field cannot be set when ending a nomination.']} @@ -289,11 +314,36 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge instance.ended_at = timezone.now() elif 'active' in data: - # 3. The `active` field is only allowed when ending a nomination. + # 4. The `active` field is only allowed when ending a nomination. raise ValidationError( {'active': ['This field can only be used to end a nomination']} ) - serializer.save() + if 'reason' in request.data: + if 'actor' not in request.data: + raise ValidationError( + {'actor': 'This field is required when editing reason.'} + ) + + entry_filter = NominationEntry.objects.filter( + nomination_id=instance.id, + actor__id=request.data['actor'] + ) + + if not entry_filter.exists(): + raise ValidationError( + {'actor': "Actor don't exist or have not nominated user."} + ) + + entry = entry_filter[0] + entry.reason = request.data['reason'] + entry.save() + + nomination = serializer.save() + return_data = NominationSerializer(nomination).data + return_data["entries"] = NominationEntrySerializer( + NominationEntry.objects.filter(nomination_id=nomination.id), + many=True + ).data - return Response(serializer.data) + return Response(return_data) -- cgit v1.2.3 From 0ae69d9892db73029336e7b9ca95164dd80f828f Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Mon, 22 Feb 2021 09:05:54 +0200 Subject: Disable creating multiple nomination entries of one nomination for one actor --- pydis_site/apps/api/viewsets/bot/nomination.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pydis_site/apps/api/viewsets/bot/nomination.py b/pydis_site/apps/api/viewsets/bot/nomination.py index 14dee9bc..81fb43f7 100644 --- a/pydis_site/apps/api/viewsets/bot/nomination.py +++ b/pydis_site/apps/api/viewsets/bot/nomination.py @@ -252,6 +252,16 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge data=ChainMap(request.data, {"nomination": nomination_filter[0].id}) ) entry_serializer.is_valid(raise_exception=True) + + # Don't allow user creating many nomination entries for one nomination + if NominationEntry.objects.filter( + nomination_id=nomination_filter[0].id, + actor__id=entry_serializer.validated_data["actor"].id + ).exists(): + raise ValidationError( + {'actor': 'This actor have already created nomination entry for this nomination.'} + ) + NominationEntry.objects.create(**entry_serializer.validated_data) data = NominationSerializer(nomination_filter[0]).data -- cgit v1.2.3 From 2fb5bf5dbdc6cdfe0a62d54bc7b726eab0199a59 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Mon, 22 Feb 2021 09:36:55 +0200 Subject: Add rollback to nominations table split migration --- .../apps/api/migrations/0068_split_nomination_tables.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/pydis_site/apps/api/migrations/0068_split_nomination_tables.py b/pydis_site/apps/api/migrations/0068_split_nomination_tables.py index 2e2313ee..1c392365 100644 --- a/pydis_site/apps/api/migrations/0068_split_nomination_tables.py +++ b/pydis_site/apps/api/migrations/0068_split_nomination_tables.py @@ -21,6 +21,19 @@ def migrate_nominations(apps: Apps, schema_editor: BaseDatabaseSchemaEditor) -> nomination_entry.save() +def unmigrate_nominations(apps: Apps, schema_editor: BaseDatabaseSchemaEditor) -> None: + Nomination = apps.get_model("api", "Nomination") + NominationEntry = apps.get_model("api", "NominationEntry") + + for entry in NominationEntry.objects.all(): + nomination = Nomination.objects.get(pk=entry.nomination.id) + nomination.actor = entry.actor + nomination.reason = entry.reason + nomination.inserted_at = entry.inserted_at + + nomination.save() + + class Migration(migrations.Migration): dependencies = [ @@ -43,7 +56,7 @@ class Migration(migrations.Migration): ], bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), ), - migrations.RunPython(migrate_nominations), + migrations.RunPython(migrate_nominations, unmigrate_nominations), migrations.RemoveField( model_name='nomination', name='actor', -- cgit v1.2.3 From 698cbc17405a49fe42669fedd6054ab6c9008a4a Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Mon, 22 Feb 2021 09:37:12 +0200 Subject: Wrap validation errors to [] --- pydis_site/apps/api/viewsets/bot/nomination.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pydis_site/apps/api/viewsets/bot/nomination.py b/pydis_site/apps/api/viewsets/bot/nomination.py index 81fb43f7..c4600425 100644 --- a/pydis_site/apps/api/viewsets/bot/nomination.py +++ b/pydis_site/apps/api/viewsets/bot/nomination.py @@ -259,7 +259,7 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge actor__id=entry_serializer.validated_data["actor"].id ).exists(): raise ValidationError( - {'actor': 'This actor have already created nomination entry for this nomination.'} + {'actor': ['This actor have already created nomination entry for this nomination.']} ) NominationEntry.objects.create(**entry_serializer.validated_data) @@ -301,12 +301,12 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge # 2. We're setting nomination reviewed if not instance.active: raise ValidationError( - {'reviewed': 'This field cannot be set if nomination is inactive.'} + {'reviewed': ['This field cannot be set if nomination is inactive.']} ) if 'active' in data: raise ValidationError( - {'active': 'This field cannot be set same time than ending nomination.'} + {'active': ['This field cannot be set same time than ending nomination.']} ) elif instance.active and not data['active']: @@ -332,7 +332,7 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge if 'reason' in request.data: if 'actor' not in request.data: raise ValidationError( - {'actor': 'This field is required when editing reason.'} + {'actor': ['This field is required when editing reason.']} ) entry_filter = NominationEntry.objects.filter( @@ -342,7 +342,7 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge if not entry_filter.exists(): raise ValidationError( - {'actor': "Actor don't exist or have not nominated user."} + {'actor': ["Actor don't exist or have not nominated user."]} ) entry = entry_filter[0] -- cgit v1.2.3 From 6591270f832e93a3eaf941aa1aeeddb6af7bce36 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Mon, 22 Feb 2021 11:36:25 +0200 Subject: Import NominationEntry to models __init__.py --- pydis_site/apps/api/models/__init__.py | 1 + pydis_site/apps/api/models/bot/__init__.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pydis_site/apps/api/models/__init__.py b/pydis_site/apps/api/models/__init__.py index 0a8c90f6..fd5bf220 100644 --- a/pydis_site/apps/api/models/__init__.py +++ b/pydis_site/apps/api/models/__init__.py @@ -8,6 +8,7 @@ from .bot import ( Message, MessageDeletionContext, Nomination, + NominationEntry, OffensiveMessage, OffTopicChannelName, Reminder, diff --git a/pydis_site/apps/api/models/bot/__init__.py b/pydis_site/apps/api/models/bot/__init__.py index 1673b434..ac864de3 100644 --- a/pydis_site/apps/api/models/bot/__init__.py +++ b/pydis_site/apps/api/models/bot/__init__.py @@ -6,7 +6,7 @@ from .documentation_link import DocumentationLink from .infraction import Infraction from .message import Message from .message_deletion_context import MessageDeletionContext -from .nomination import Nomination +from .nomination import Nomination, NominationEntry from .off_topic_channel_name import OffTopicChannelName from .offensive_message import OffensiveMessage from .reminder import Reminder -- cgit v1.2.3 From 126b5084f1f583790bcd1a386474230012d7cadb Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Mon, 22 Feb 2021 11:36:47 +0200 Subject: Change NominationEntry import location in serializers --- pydis_site/apps/api/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydis_site/apps/api/serializers.py b/pydis_site/apps/api/serializers.py index 96f28aee..49c247b5 100644 --- a/pydis_site/apps/api/serializers.py +++ b/pydis_site/apps/api/serializers.py @@ -20,13 +20,13 @@ from .models import ( Infraction, MessageDeletionContext, Nomination, + NominationEntry, OffTopicChannelName, OffensiveMessage, Reminder, Role, User ) -from .models.bot.nomination import NominationEntry class BotSettingSerializer(ModelSerializer): -- cgit v1.2.3 From 3907122c9e76f78bd7bfd07028e9eb79b43d65b3 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Mon, 22 Feb 2021 11:37:33 +0200 Subject: Small improvements in nomination viewset --- pydis_site/apps/api/viewsets/bot/nomination.py | 62 +++++++++++++------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/pydis_site/apps/api/viewsets/bot/nomination.py b/pydis_site/apps/api/viewsets/bot/nomination.py index c4600425..7820ca0d 100644 --- a/pydis_site/apps/api/viewsets/bot/nomination.py +++ b/pydis_site/apps/api/viewsets/bot/nomination.py @@ -14,8 +14,7 @@ from rest_framework.mixins import ( from rest_framework.response import Response from rest_framework.viewsets import GenericViewSet -from pydis_site.apps.api.models.bot import Nomination -from pydis_site.apps.api.models.bot.nomination import NominationEntry +from pydis_site.apps.api.models.bot import Nomination, NominationEntry from pydis_site.apps.api.serializers import NominationEntrySerializer, NominationSerializer @@ -112,9 +111,9 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge The PATCH route can be used for three distinct operations: 1. Updating the `reason` of `active` nomination; - 2. Updating `reviewed` field of `active` nomination. - 3. Ending an `active` nomination; - 4. Updating the `end_reason` or `reason` field of an `inactive` nomination. + 2. Ending an `active` nomination; + 3. Updating the `end_reason` or `reason` field of an `inactive` nomination. + 4. Updating `reviewed` field of `active` nomination. While the response format and status codes are the same for all three operations (see below), the request bodies vary depending on the operation. For all operations it holds @@ -136,16 +135,7 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge - 400: if a field in the request body is invalid or disallowed - 404: if an infraction with the given `id` could not be found - ### 2. Setting nomination `reviewed` - - #### Request body - >>> { - ... 'reviewed': True - ... } - - See operation 1 for the response format and status codes. - - ### 3. Ending an `active` nomination + ### 2. Ending an `active` nomination #### Request body >>> { @@ -155,7 +145,7 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge See operation 1 for the response format and status codes. - ### 4. Updating the `end_reason` or `reason` field of an `inactive` nomination. + ### 3. Updating the `end_reason` or `reason` field of an `inactive` nomination. Actor field is required when updating reason. #### Request body @@ -167,6 +157,15 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge Note: The request body may contain either or both fields. + See operation 1 for the response format and status codes. + + ### 4. Setting nomination `reviewed` + + #### Request body + >>> { + ... 'reviewed': True + ... } + See operation 1 for the response format and status codes. """ @@ -297,21 +296,9 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge {'end_reason': ["An active nomination can't have an end reason."]} ) - elif 'reviewed' in data: - # 2. We're setting nomination reviewed - if not instance.active: - raise ValidationError( - {'reviewed': ['This field cannot be set if nomination is inactive.']} - ) - - if 'active' in data: - raise ValidationError( - {'active': ['This field cannot be set same time than ending nomination.']} - ) - elif instance.active and not data['active']: - # 3. We're ending an active nomination. - if 'reason' in data: + # 2. We're ending an active nomination. + if 'reason' in request.data: raise ValidationError( {'reason': ['This field cannot be set when ending a nomination.']} ) @@ -321,14 +308,27 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge {'end_reason': ['This field is required when ending a nomination.']} ) + if 'reviewed' in request.data: + raise ValidationError( + {'reviewed': ['This field cannot be set same time than ending nomination.']} + ) + instance.ended_at = timezone.now() elif 'active' in data: - # 4. The `active` field is only allowed when ending a nomination. + # 3. The `active` field is only allowed when ending a nomination. raise ValidationError( {'active': ['This field can only be used to end a nomination']} ) + # This is actually covered, but for some reason coverage don't think so. + elif 'reviewed' in request.data: # pragma: no cover + # 4. We're setting nomination reviewed + if not instance.active: + raise ValidationError( + {'reviewed': ['This field cannot be set if nomination is inactive.']} + ) + if 'reason' in request.data: if 'actor' not in request.data: raise ValidationError( -- cgit v1.2.3 From 9af0579297e8f1b535099f24f41b4c7f85e2a0cc Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Mon, 22 Feb 2021 11:37:53 +0200 Subject: Update nomination-related tests to cover recent table-splitting changes --- pydis_site/apps/api/tests/test_models.py | 16 +-- pydis_site/apps/api/tests/test_nominations.py | 144 +++++++++++++++++++++++--- 2 files changed, 139 insertions(+), 21 deletions(-) diff --git a/pydis_site/apps/api/tests/test_models.py b/pydis_site/apps/api/tests/test_models.py index 853e6621..66052e01 100644 --- a/pydis_site/apps/api/tests/test_models.py +++ b/pydis_site/apps/api/tests/test_models.py @@ -10,6 +10,7 @@ from pydis_site.apps.api.models import ( Message, MessageDeletionContext, Nomination, + NominationEntry, OffTopicChannelName, OffensiveMessage, Reminder, @@ -37,17 +38,11 @@ class StringDunderMethodTests(SimpleTestCase): def setUp(self): self.nomination = Nomination( id=123, - actor=User( - id=9876, - name='Mr. Hemlock', - discriminator=6666, - ), user=User( id=9876, name="Hemlock's Cat", discriminator=7777, ), - reason="He purrrrs like the best!", ) self.objects = ( @@ -135,6 +130,15 @@ class StringDunderMethodTests(SimpleTestCase): ), content="oh no", expiration=dt(5018, 11, 20, 15, 52, tzinfo=timezone.utc) + ), + NominationEntry( + nomination_id=self.nomination.id, + actor=User( + id=9876, + name='Mr. Hemlock', + discriminator=6666, + ), + reason="He purrrrs like the best!", ) ) diff --git a/pydis_site/apps/api/tests/test_nominations.py b/pydis_site/apps/api/tests/test_nominations.py index b37135f8..3892ec6e 100644 --- a/pydis_site/apps/api/tests/test_nominations.py +++ b/pydis_site/apps/api/tests/test_nominations.py @@ -3,7 +3,7 @@ from datetime import datetime as dt, timedelta, timezone from django_hosts.resolvers import reverse from .base import APISubdomainTestCase -from ..models import Nomination, User +from ..models import Nomination, NominationEntry, User class CreationTests(APISubdomainTestCase): @@ -14,6 +14,11 @@ class CreationTests(APISubdomainTestCase): name='joe dart', discriminator=1111, ) + cls.user2 = User.objects.create( + id=9876, + name='Who?', + discriminator=1234 + ) def test_accepts_valid_data(self): url = reverse('bot:nomination-list', host='api') @@ -27,17 +32,39 @@ class CreationTests(APISubdomainTestCase): self.assertEqual(response.status_code, 201) nomination = Nomination.objects.get(id=response.json()['id']) + nomination_entry = NominationEntry.objects.get( + nomination_id=nomination.id, + actor_id=self.user.id + ) self.assertAlmostEqual( nomination.inserted_at, dt.now(timezone.utc), delta=timedelta(seconds=2) ) self.assertEqual(nomination.user.id, data['user']) - self.assertEqual(nomination.actor.id, data['actor']) - self.assertEqual(nomination.reason, data['reason']) + self.assertEqual(nomination_entry.reason, data['reason']) self.assertEqual(nomination.active, True) - def test_returns_400_on_second_active_nomination(self): + def test_returns_200_on_second_active_nomination_by_different_user(self): + url = reverse('bot:nomination-list', host='api') + first_data = { + 'actor': self.user.id, + 'reason': 'Joe Dart on Fender Bass', + 'user': self.user.id, + } + second_data = { + 'actor': self.user2.id, + 'reason': 'Great user', + 'user': self.user.id + } + + response1 = self.client.post(url, data=first_data) + self.assertEqual(response1.status_code, 201) + + response2 = self.client.post(url, data=second_data) + self.assertEqual(response2.status_code, 201) + + def test_returns_400_on_second_active_nomination_by_existing_nominator(self): url = reverse('bot:nomination-list', host='api') data = { 'actor': self.user.id, @@ -51,7 +78,7 @@ class CreationTests(APISubdomainTestCase): response2 = self.client.post(url, data=data) self.assertEqual(response2.status_code, 400) self.assertEqual(response2.json(), { - 'active': ['There can only be one active nomination.'] + 'actor': ['This actor have already created nomination entry for this nomination.'] }) def test_returns_400_for_missing_user(self): @@ -189,30 +216,40 @@ class NominationTests(APISubdomainTestCase): ) cls.active_nomination = Nomination.objects.create( - user=cls.user, + user=cls.user + ) + cls.active_nomination_entry = NominationEntry.objects.create( + nomination=cls.active_nomination, actor=cls.user, reason="He's pretty funky" ) cls.inactive_nomination = Nomination.objects.create( user=cls.user, - actor=cls.user, - reason="He's pretty funky", active=False, end_reason="His neck couldn't hold the funk", ended_at="5018-11-20T15:52:00+00:00" ) + cls.inactive_nomination_entry = NominationEntry.objects.create( + nomination=cls.inactive_nomination, + actor=cls.user, + reason="He's pretty funky" + ) - def test_returns_200_update_reason_on_active(self): + def test_returns_200_update_reason_on_active_with_actor(self): url = reverse('bot:nomination-detail', args=(self.active_nomination.id,), host='api') data = { - 'reason': "He's one funky duck" + 'reason': "He's one funky duck", + 'actor': self.user.id } response = self.client.patch(url, data=data) self.assertEqual(response.status_code, 200) - nomination = Nomination.objects.get(id=response.json()['id']) - self.assertEqual(nomination.reason, data['reason']) + nomination_entry = NominationEntry.objects.get( + nomination_id=response.json()['id'], + actor_id=self.user.id + ) + self.assertEqual(nomination_entry.reason, data['reason']) def test_returns_400_on_frozen_field_update(self): url = reverse('bot:nomination-detail', args=(self.active_nomination.id,), host='api') @@ -241,14 +278,18 @@ class NominationTests(APISubdomainTestCase): def test_returns_200_update_reason_on_inactive(self): url = reverse('bot:nomination-detail', args=(self.inactive_nomination.id,), host='api') data = { - 'reason': "He's one funky duck" + 'reason': "He's one funky duck", + 'actor': self.user.id } response = self.client.patch(url, data=data) self.assertEqual(response.status_code, 200) - nomination = Nomination.objects.get(id=response.json()['id']) - self.assertEqual(nomination.reason, data['reason']) + nomination_entry = NominationEntry.objects.get( + nomination_id=response.json()['id'], + actor_id=self.user.id + ) + self.assertEqual(nomination_entry.reason, data['reason']) def test_returns_200_update_end_reason_on_inactive(self): url = reverse('bot:nomination-detail', args=(self.inactive_nomination.id,), host='api') @@ -442,3 +483,76 @@ class NominationTests(APISubdomainTestCase): infractions = response.json() self.assertEqual(len(infractions), 2) + + def test_return_nomination_entries_get_single_nomination(self): + url = reverse('bot:nomination-detail', args=(self.active_nomination.id,), host='api') + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + data = response.json() + + self.assertEqual(len(data['entries']), 1) + self.assertEqual(data['entries'][0], { + "actor": self.user.id, + "reason": "He's pretty funky", + "inserted_at": self.active_nomination_entry.inserted_at.isoformat().replace( + "+00:00", "Z" + ) + }) + + def test_return_nomination_entries_get_all_nominations(self): + url = reverse('api:nomination-list', host='api') + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + + data = response.json() + + self.assertEqual(len(data), 2) + self.assertEqual(len(data[0]["entries"]), 1) + self.assertEqual(len(data[1]["entries"]), 1) + + def test_patch_nomination_set_reviewed_of_active_nomination(self): + url = reverse('api:nomination-detail', args=(self.active_nomination.id,), host='api') + data = {'reviewed': True} + + response = self.client.patch(url, data=data) + self.assertEqual(response.status_code, 200) + + def test_patch_nomination_set_reviewed_of_inactive_nomination(self): + url = reverse('api:nomination-detail', args=(self.inactive_nomination.id,), host='api') + data = {'reviewed': True} + + response = self.client.patch(url, data=data) + self.assertEqual(response.status_code, 400) + self.assertEqual(response.json(), { + 'reviewed': ['This field cannot be set if nomination is inactive.'] + }) + + def test_patch_nomination_set_reviewed_and_end(self): + url = reverse('api:nomination-detail', args=(self.active_nomination.id,), host='api') + data = {'reviewed': True, 'active': False, 'end_reason': "What?"} + + response = self.client.patch(url, data=data) + self.assertEqual(response.status_code, 400) + self.assertEqual(response.json(), { + 'reviewed': ['This field cannot be set same time than ending nomination.'] + }) + + def test_modifying_reason_without_actor(self): + url = reverse('api:nomination-detail', args=(self.active_nomination.id,), host='api') + data = {'reason': 'That is my reason!'} + + response = self.client.patch(url, data=data) + self.assertEqual(response.status_code, 400) + self.assertEqual(response.json(), { + 'actor': ['This field is required when editing reason.'] + }) + + def test_modifying_reason_with_unknown_actor(self): + url = reverse('api:nomination-detail', args=(self.active_nomination.id,), host='api') + data = {'reason': 'That is my reason!', 'actor': 90909090909090} + + response = self.client.patch(url, data=data) + self.assertEqual(response.status_code, 400) + self.assertEqual(response.json(), { + 'actor': ["Actor don't exist or have not nominated user."] + }) -- cgit v1.2.3 From 3f99f184bab1375b49fa521a145922f518ae74a8 Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Tue, 23 Feb 2021 10:19:03 +0100 Subject: Add powered by linode to the footer --- pydis_site/static/css/base/base.css | 8 ++++++++ pydis_site/templates/base/footer.html | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/pydis_site/static/css/base/base.css b/pydis_site/static/css/base/base.css index b53ff5d4..a1d325f9 100644 --- a/pydis_site/static/css/base/base.css +++ b/pydis_site/static/css/base/base.css @@ -69,6 +69,14 @@ main.site-content { background-color: transparent; } +#linode-logo { + padding-left: 15px; + background: url(https://www.linode.com/wp-content/uploads/2021/01/Linode-Logo-Black.svg) no-repeat center; + filter: invert(1) grayscale(1); + background-size: 60px; + color: #00000000; +} + #django-logo { padding-bottom: 2px; background: url(https://static.djangoproject.com/img/logos/django-logo-negative.png) no-repeat center; diff --git a/pydis_site/templates/base/footer.html b/pydis_site/templates/base/footer.html index 90f06f3c..bca43b5d 100644 --- a/pydis_site/templates/base/footer.html +++ b/pydis_site/templates/base/footer.html @@ -1,7 +1,7 @@

- Built with and
© {% now "Y" %} Python Discord + Powered by
Built with and
© {% now "Y" %} Python Discord

-- cgit v1.2.3 From f731e4dd31f9eba8e8c9916329a5c08578055077 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Wed, 24 Feb 2021 08:27:36 +0200 Subject: Set related_name option of nomination field of NominationEntry In order to use entries in serializer without manually setting entries key we have to use related_name option to automatically fetch all related entries. --- pydis_site/apps/api/models/bot/nomination.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pydis_site/apps/api/models/bot/nomination.py b/pydis_site/apps/api/models/bot/nomination.py index ed6f7d81..a813855d 100644 --- a/pydis_site/apps/api/models/bot/nomination.py +++ b/pydis_site/apps/api/models/bot/nomination.py @@ -53,7 +53,8 @@ class NominationEntry(ModelReprMixin, models.Model): nomination = models.ForeignKey( Nomination, on_delete=models.CASCADE, - help_text="Nomination to what this entry belongs." + help_text="Nomination to what this entry belongs.", + related_name="entries" ) actor = models.ForeignKey( User, -- cgit v1.2.3 From 1fc5470a0af18aab427a48faf132bea2712330aa Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Wed, 24 Feb 2021 08:28:54 +0200 Subject: Set default ordering of NominationEntry to inserted_at decreasing Set it here so we don't have to set it every place where we fetch entries. --- pydis_site/apps/api/models/bot/nomination.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pydis_site/apps/api/models/bot/nomination.py b/pydis_site/apps/api/models/bot/nomination.py index a813855d..e72a18ca 100644 --- a/pydis_site/apps/api/models/bot/nomination.py +++ b/pydis_site/apps/api/models/bot/nomination.py @@ -76,3 +76,7 @@ class NominationEntry(ModelReprMixin, models.Model): """Meta options for NominationEntry model.""" verbose_name_plural = "nomination entries" + + # Set default ordering here to latest first + # so we don't need to define it everywhere + ordering = ("-inserted_at",) -- cgit v1.2.3 From bb88dc66eb4692eaf5a8ef276944ec13aebb97cb Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Wed, 24 Feb 2021 08:29:53 +0200 Subject: Merge nomination migrations This doesn't make sense to have 3 small migrations for one PR, so I merged 2 existing migrations and 1 new, ordering and related_name adding migrations to one. --- .../apps/api/migrations/0068_split_nomination_tables.py | 4 +++- .../migrations/0069_change_nomination_entry_plural.py | 17 ----------------- 2 files changed, 3 insertions(+), 18 deletions(-) delete mode 100644 pydis_site/apps/api/migrations/0069_change_nomination_entry_plural.py diff --git a/pydis_site/apps/api/migrations/0068_split_nomination_tables.py b/pydis_site/apps/api/migrations/0068_split_nomination_tables.py index 1c392365..107e3a56 100644 --- a/pydis_site/apps/api/migrations/0068_split_nomination_tables.py +++ b/pydis_site/apps/api/migrations/0068_split_nomination_tables.py @@ -52,9 +52,11 @@ class Migration(migrations.Migration): on_delete=django.db.models.deletion.CASCADE, related_name='nomination_set', to='api.User')), ('nomination', models.ForeignKey(help_text='Nomination to what this entry belongs.', - on_delete=django.db.models.deletion.CASCADE, to='api.Nomination')), + on_delete=django.db.models.deletion.CASCADE, to='api.Nomination', + related_name='entries')), ], bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), + options={'ordering': ('-inserted_at',), 'verbose_name_plural': 'nomination entries'} ), migrations.RunPython(migrate_nominations, unmigrate_nominations), migrations.RemoveField( diff --git a/pydis_site/apps/api/migrations/0069_change_nomination_entry_plural.py b/pydis_site/apps/api/migrations/0069_change_nomination_entry_plural.py deleted file mode 100644 index 6bf4ac8c..00000000 --- a/pydis_site/apps/api/migrations/0069_change_nomination_entry_plural.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 3.0.11 on 2021-02-21 16:44 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0068_split_nomination_tables'), - ] - - operations = [ - migrations.AlterModelOptions( - name='nominationentry', - options={'verbose_name_plural': 'nomination entries'}, - ), - ] -- cgit v1.2.3 From 7682b56fa881f809797e299bd829dfda6480b8d9 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Wed, 24 Feb 2021 08:31:06 +0200 Subject: Add entries field to Nomination serializer After setting related_name in NominationEntry model nomination field, we can just provide serializer and DRF automatically fetch all related entries. --- pydis_site/apps/api/serializers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pydis_site/apps/api/serializers.py b/pydis_site/apps/api/serializers.py index 49c247b5..17dd4b3d 100644 --- a/pydis_site/apps/api/serializers.py +++ b/pydis_site/apps/api/serializers.py @@ -357,12 +357,14 @@ class NominationEntrySerializer(ModelSerializer): class NominationSerializer(ModelSerializer): """A class providing (de-)serialization of `Nomination` instances.""" + entries = NominationEntrySerializer(many=True, read_only=True) + class Meta: """Metadata defined for the Django REST Framework.""" model = Nomination fields = ( - 'id', 'active', 'user', 'inserted_at', 'end_reason', 'ended_at', 'reviewed' + 'id', 'active', 'user', 'inserted_at', 'end_reason', 'ended_at', 'reviewed', 'entries' ) -- cgit v1.2.3 From 3edb416ba86c62d62c0258d4c69f967e47765186 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Wed, 24 Feb 2021 08:38:13 +0200 Subject: Simplify nominations viewset After moving entries to nomination serializer we can get rid from GET request handlers and let DRF handle this. Also PATCH and POST handlers got some simplification by removing manual entries setting. --- pydis_site/apps/api/viewsets/bot/nomination.py | 51 ++------------------------ 1 file changed, 3 insertions(+), 48 deletions(-) diff --git a/pydis_site/apps/api/viewsets/bot/nomination.py b/pydis_site/apps/api/viewsets/bot/nomination.py index 7820ca0d..e3e71ca2 100644 --- a/pydis_site/apps/api/viewsets/bot/nomination.py +++ b/pydis_site/apps/api/viewsets/bot/nomination.py @@ -176,40 +176,6 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge frozen_fields = ('id', 'inserted_at', 'user', 'ended_at') frozen_on_create = ('ended_at', 'end_reason', 'active', 'inserted_at', 'reviewed') - def list(self, request: HttpRequest, *args, **kwargs) -> Response: - """ - DRF method for listing Nominations. - - Called by the Django Rest Framework in response to the corresponding HTTP request. - """ - queryset = self.filter_queryset(self.get_queryset()) - data = NominationSerializer(queryset, many=True).data - - for i, nomination in enumerate(data): - entries = NominationEntrySerializer( - NominationEntry.objects.filter(nomination_id=nomination["id"]), - many=True - ).data - data[i]["entries"] = entries - - return Response(data) - - def retrieve(self, request: HttpRequest, *args, **kwargs) -> Response: - """ - DRF method for retrieving a Nomination. - - Called by the Django Rest Framework in response to the corresponding HTTP request. - """ - nomination = self.get_object() - - data = NominationSerializer(nomination).data - data["entries"] = NominationEntrySerializer( - NominationEntry.objects.filter(nomination_id=nomination.id), - many=True - ).data - - return Response(data) - def create(self, request: HttpRequest, *args, **kwargs) -> Response: """ DRF method for creating a Nomination. @@ -238,11 +204,9 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge data=ChainMap(request.data, {"nomination": nomination.id}) ) entry_serializer.is_valid(raise_exception=True) - - entry = NominationEntry.objects.create(**entry_serializer.validated_data) + NominationEntry.objects.create(**entry_serializer.validated_data) data = NominationSerializer(nomination).data - data["entries"] = NominationEntrySerializer([entry], many=True).data headers = self.get_success_headers(data) return Response(data, status=status.HTTP_201_CREATED, headers=headers) @@ -264,10 +228,6 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge NominationEntry.objects.create(**entry_serializer.validated_data) data = NominationSerializer(nomination_filter[0]).data - data["entries"] = NominationEntrySerializer( - NominationEntry.objects.filter(nomination_id=nomination_filter[0].id), - many=True - ).data headers = self.get_success_headers(data) return Response(data, status=status.HTTP_201_CREATED, headers=headers) @@ -349,11 +309,6 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge entry.reason = request.data['reason'] entry.save() - nomination = serializer.save() - return_data = NominationSerializer(nomination).data - return_data["entries"] = NominationEntrySerializer( - NominationEntry.objects.filter(nomination_id=nomination.id), - many=True - ).data + serializer.save() - return Response(return_data) + return Response(serializer.data) -- cgit v1.2.3 From 774e7c09d6950bb74e83af456d20b8f72fdef8ed Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Wed, 24 Feb 2021 08:41:40 +0200 Subject: Remove GET nomination test for entries Entries isn't handled manually anymore so these tests have no point. --- pydis_site/apps/api/tests/test_nominations.py | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/pydis_site/apps/api/tests/test_nominations.py b/pydis_site/apps/api/tests/test_nominations.py index 3892ec6e..1daa6f3f 100644 --- a/pydis_site/apps/api/tests/test_nominations.py +++ b/pydis_site/apps/api/tests/test_nominations.py @@ -484,32 +484,6 @@ class NominationTests(APISubdomainTestCase): self.assertEqual(len(infractions), 2) - def test_return_nomination_entries_get_single_nomination(self): - url = reverse('bot:nomination-detail', args=(self.active_nomination.id,), host='api') - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - data = response.json() - - self.assertEqual(len(data['entries']), 1) - self.assertEqual(data['entries'][0], { - "actor": self.user.id, - "reason": "He's pretty funky", - "inserted_at": self.active_nomination_entry.inserted_at.isoformat().replace( - "+00:00", "Z" - ) - }) - - def test_return_nomination_entries_get_all_nominations(self): - url = reverse('api:nomination-list', host='api') - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - - data = response.json() - - self.assertEqual(len(data), 2) - self.assertEqual(len(data[0]["entries"]), 1) - self.assertEqual(len(data[1]["entries"]), 1) - def test_patch_nomination_set_reviewed_of_active_nomination(self): url = reverse('api:nomination-detail', args=(self.active_nomination.id,), host='api') data = {'reviewed': True} -- cgit v1.2.3 From cd03f7448ae4c96c23a80c4c46344bc35b8c603a Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Wed, 24 Feb 2021 08:44:22 +0200 Subject: Make default value of nomination entry reason to empty string For string fields NULL as default is not suggested, so use empty string instead. --- pydis_site/apps/api/migrations/0068_split_nomination_tables.py | 2 +- pydis_site/apps/api/models/bot/nomination.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pydis_site/apps/api/migrations/0068_split_nomination_tables.py b/pydis_site/apps/api/migrations/0068_split_nomination_tables.py index 107e3a56..27c29017 100644 --- a/pydis_site/apps/api/migrations/0068_split_nomination_tables.py +++ b/pydis_site/apps/api/migrations/0068_split_nomination_tables.py @@ -45,7 +45,7 @@ class Migration(migrations.Migration): name='NominationEntry', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('reason', models.TextField(blank=True, help_text='Why the actor nominated this user.', null=True)), + ('reason', models.TextField(blank=True, help_text='Why the actor nominated this user.', default="")), ('inserted_at', models.DateTimeField(auto_now_add=True, help_text='The creation date of this nomination entry.')), ('actor', models.ForeignKey(help_text='The staff member that nominated this user.', diff --git a/pydis_site/apps/api/models/bot/nomination.py b/pydis_site/apps/api/models/bot/nomination.py index e72a18ca..443200ff 100644 --- a/pydis_site/apps/api/models/bot/nomination.py +++ b/pydis_site/apps/api/models/bot/nomination.py @@ -64,7 +64,7 @@ class NominationEntry(ModelReprMixin, models.Model): ) reason = models.TextField( help_text="Why the actor nominated this user.", - null=True, + default="", blank=True ) inserted_at = models.DateTimeField( -- cgit v1.2.3 From 98c60cf90f511f828d3e7bc3d7891b2809d27db6 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Wed, 24 Feb 2021 08:47:25 +0200 Subject: Add comment about manually defining nomination field of NominationEntry serializer --- pydis_site/apps/api/serializers.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pydis_site/apps/api/serializers.py b/pydis_site/apps/api/serializers.py index 17dd4b3d..f47bedca 100644 --- a/pydis_site/apps/api/serializers.py +++ b/pydis_site/apps/api/serializers.py @@ -342,6 +342,9 @@ class UserSerializer(ModelSerializer): class NominationEntrySerializer(ModelSerializer): """A class providing (de-)serialization of `NominationEntry` instances.""" + # We need to define it here, because we don't want that nomination ID + # return inside nomination response entry, because ID is already available + # as top-level field. Queryset is required if field is not read only. nomination = PrimaryKeyRelatedField( queryset=Nomination.objects.all(), write_only=True -- cgit v1.2.3 From 97d0ece403870a5a2184350608cda448808d70ac Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Thu, 25 Feb 2021 16:57:32 +0000 Subject: Reduce worker count from 4 to 2 We'll be creating a second site instance to allow for rolling restarts, so it makes sense to half this so when we double the requests we'll effectively have 4 workers again. --- manage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manage.py b/manage.py index a025e7b1..fb5ee40c 100755 --- a/manage.py +++ b/manage.py @@ -163,7 +163,7 @@ class SiteManager: "-b", "0.0.0.0:8000", "pydis_site.wsgi:application", "--threads", "8", - "-w", "4", + "-w", "2", "--max-requests", "1000", "--max-requests-jitter", "50", "--statsd-host", "graphite.default.svc.cluster.local:8125", -- cgit v1.2.3 From ebf128e6d52cfd574e7a055804aa1abc54949699 Mon Sep 17 00:00:00 2001 From: Gustav Odinger <65498475+gustavwilliam@users.noreply.github.com> Date: Mon, 1 Mar 2021 17:10:09 +0100 Subject: Add 404 page This does not include the styling required for the page to display properly --- pydis_site/templates/404.html | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 pydis_site/templates/404.html diff --git a/pydis_site/templates/404.html b/pydis_site/templates/404.html new file mode 100644 index 00000000..909e0a3d --- /dev/null +++ b/pydis_site/templates/404.html @@ -0,0 +1,34 @@ +{% load static %} + + + + + + Python Discord | 404 + + + + + + + + + +
+
+ Python Discord banner +
+
+

404 — not found

+

We couldn't find the page you're looking for. Here are a few things to try out:

+
    +
  • Double check the URL. Are you sure you typed it out correctly? +
  • Come join our Discord Server. Maybe we can help you out over + there +
+
+
+ + + -- cgit v1.2.3 From 3020e2e1e9a44356da9031e8cd278dfe6bf6bba1 Mon Sep 17 00:00:00 2001 From: Gustav Odinger <65498475+gustavwilliam@users.noreply.github.com> Date: Mon, 1 Mar 2021 17:11:08 +0100 Subject: Add error page styling This css sheet applies to the 404 and 500 pages --- pydis_site/static/css/error_pages.css | 66 +++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 pydis_site/static/css/error_pages.css diff --git a/pydis_site/static/css/error_pages.css b/pydis_site/static/css/error_pages.css new file mode 100644 index 00000000..77bb7e2b --- /dev/null +++ b/pydis_site/static/css/error_pages.css @@ -0,0 +1,66 @@ +html { + height: 100%; +} + +body { + background-color: #7289DA; + background-image: url("https://raw.githubusercontent.com/python-discord/branding/master/logos/banner_pattern/banner_pattern.svg"); + background-size: 128px; + font-family: "Hind", "Helvetica", "Arial", sans-serif; + display: flex; + align-items: center; + justify-content: center; + height: 100%; +} + +h1, +p { + color: black; + padding: 0; + margin: 0; + margin-bottom: 10px; +} + +h1 { + margin-bottom: 15px; + font-size: 26px; +} + +p, +li { + line-height: 125%; +} + +a { + color: #7289DA; +} + +ul { + margin-bottom: 0; +} + +li { + margin-top: 10px; +} + +.error-box { + display: flex; + flex-direction: column; + max-width: 512px; + background-color: white; + border-radius: 20px; + overflow: hidden; + box-shadow: 5px 7px 40px rgba(0, 0, 0, 0.432); +} + +.logo-box { + display: flex; + justify-content: center; + height: 80px; + padding: 15px; + background-color: #758ad4; +} + +.content-box { + padding: 25px; +} -- cgit v1.2.3 From df9f5907248792c701139e5e05dc59af69cb88a1 Mon Sep 17 00:00:00 2001 From: Gustav Odinger <65498475+gustavwilliam@users.noreply.github.com> Date: Mon, 1 Mar 2021 17:13:35 +0100 Subject: Add 500 page --- pydis_site/templates/500.html | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 pydis_site/templates/500.html diff --git a/pydis_site/templates/500.html b/pydis_site/templates/500.html new file mode 100644 index 00000000..831ed285 --- /dev/null +++ b/pydis_site/templates/500.html @@ -0,0 +1,29 @@ +{% load static %} + + + + + + Python Discord | 500 + + + + + + + + + +
+
+ Python Discord banner +
+
+

500 — internal server error

+

Sorry, but something went wrong on our side of things.

+
+
+ + + -- cgit v1.2.3 From 1ed129fd0a2fec924e0d6057ac3b18a86be007a5 Mon Sep 17 00:00:00 2001 From: Gustav Odinger <65498475+gustavwilliam@users.noreply.github.com> Date: Mon, 1 Mar 2021 20:28:57 +0100 Subject: Capitalise error codes in 404 and 500 pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Leon Sandøy --- pydis_site/templates/404.html | 2 +- pydis_site/templates/500.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pydis_site/templates/404.html b/pydis_site/templates/404.html index 909e0a3d..42e317d2 100644 --- a/pydis_site/templates/404.html +++ b/pydis_site/templates/404.html @@ -20,7 +20,7 @@ alt="Python Discord banner" />
-

404 — not found

+

404 — Not Found

We couldn't find the page you're looking for. Here are a few things to try out:

  • Double check the URL. Are you sure you typed it out correctly? diff --git a/pydis_site/templates/500.html b/pydis_site/templates/500.html index 831ed285..46373654 100644 --- a/pydis_site/templates/500.html +++ b/pydis_site/templates/500.html @@ -20,7 +20,7 @@ alt="Python Discord banner" />
-

500 — internal server error

+

500 — Internal Server Error

Sorry, but something went wrong on our side of things.

-- cgit v1.2.3 From 9243fef24b953d6ac83f80d7ccc91948adac9a60 Mon Sep 17 00:00:00 2001 From: Gustav Odinger <65498475+gustavwilliam@users.noreply.github.com> Date: Mon, 1 Mar 2021 20:34:55 +0100 Subject: Update error message for 500 page Now includes a link to our Discord server and concrete actions for the user, if the problem persists --- pydis_site/templates/500.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydis_site/templates/500.html b/pydis_site/templates/500.html index 46373654..869892ec 100644 --- a/pydis_site/templates/500.html +++ b/pydis_site/templates/500.html @@ -21,7 +21,7 @@

500 — Internal Server Error

-

Sorry, but something went wrong on our side of things.

+

Something went wrong at our end. Please try again shortly, or if the problem persists, please let us know on Discord.

-- cgit v1.2.3 From f17075f4f187de1b458e4209265002c68baa3e70 Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Mon, 1 Mar 2021 20:22:42 +0000 Subject: Update Dockerfile --- Dockerfile | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3bb0a457..44567b8a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,14 +3,10 @@ FROM python:3.8-slim-buster # Allow service to handle stops gracefully STOPSIGNAL SIGQUIT -# Set Git SHA build argument -ARG git_sha="development" - # Set pip to have cleaner logs and no saved cache ENV PIP_NO_CACHE_DIR=false \ PIPENV_HIDE_EMOJIS=1 \ - PIPENV_NOSPIN=1 \ - GIT_SHA=$git_sha + PIPENV_NOSPIN=1 # Install git RUN apt-get -y update \ @@ -31,6 +27,11 @@ COPY . . # Install project dependencies RUN pipenv install --system --deploy + +# Set Git SHA environment variable +ARG git_sha="development" +ENV GIT_SHA=$git_sha + # Run web server through custom manager ENTRYPOINT ["python", "manage.py"] CMD ["run"] -- cgit v1.2.3 From 54eca6da46d83d8872746b89b95a22f7cf0c2b52 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Fri, 5 Mar 2021 07:48:32 +0200 Subject: Fix grammar of nomination endpoints documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Leon Sandøy Co-authored-by: Joe Banks --- pydis_site/apps/api/viewsets/bot/nomination.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pydis_site/apps/api/viewsets/bot/nomination.py b/pydis_site/apps/api/viewsets/bot/nomination.py index e3e71ca2..c208df46 100644 --- a/pydis_site/apps/api/viewsets/bot/nomination.py +++ b/pydis_site/apps/api/viewsets/bot/nomination.py @@ -86,8 +86,8 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge Create a new, active nomination returns the created nominations. The `user`, `reason` and `actor` fields are required and the `user` and `actor` need to know by the site. Providing other valid fields - is not allowed and invalid fields are ignored. If `user` already have - active nomination, new nomination entry will be created assigned to + is not allowed and invalid fields are ignored. If `user` already has an + active nomination, a new nomination entry will be created and assigned as the active nomination. #### Request body @@ -119,7 +119,7 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge below), the request bodies vary depending on the operation. For all operations it holds that providing other valid fields is not allowed and invalid fields are ignored. - ### 1. Updating the `reason` of `active` nomination. Actor field is required. + ### 1. Updating the `reason` of `active` nomination. The `actor` field is required. #### Request body >>> { @@ -199,7 +199,7 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge serializer.is_valid(raise_exception=True) nomination = Nomination.objects.create(**serializer.validated_data) - # Serializer truncate unnecessary data away + # The serializer will truncate and get rid of excessive data entry_serializer = NominationEntrySerializer( data=ChainMap(request.data, {"nomination": nomination.id}) ) @@ -283,7 +283,7 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge # This is actually covered, but for some reason coverage don't think so. elif 'reviewed' in request.data: # pragma: no cover - # 4. We're setting nomination reviewed + # 4. We are altering the reviewed state of the nomination. if not instance.active: raise ValidationError( {'reviewed': ['This field cannot be set if nomination is inactive.']} -- cgit v1.2.3 From 72693a6b72332c1ca9411f73ae14d395c5dd8933 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Fri, 5 Mar 2021 07:49:46 +0200 Subject: Replace double quotes with single quotes Co-authored-by: Joe Banks --- pydis_site/apps/api/viewsets/bot/nomination.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydis_site/apps/api/viewsets/bot/nomination.py b/pydis_site/apps/api/viewsets/bot/nomination.py index c208df46..ef7d1dab 100644 --- a/pydis_site/apps/api/viewsets/bot/nomination.py +++ b/pydis_site/apps/api/viewsets/bot/nomination.py @@ -302,7 +302,7 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge if not entry_filter.exists(): raise ValidationError( - {'actor': ["Actor don't exist or have not nominated user."]} + {'actor': ['Actor don't exist or have not nominated user.']} ) entry = entry_filter[0] -- cgit v1.2.3 From 9075231a5fd68986b0845327d6a2bffe70447fc8 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Fri, 5 Mar 2021 08:18:17 +0200 Subject: Use double quotes instead apostrophe because string contain "don't" --- pydis_site/apps/api/viewsets/bot/nomination.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydis_site/apps/api/viewsets/bot/nomination.py b/pydis_site/apps/api/viewsets/bot/nomination.py index ef7d1dab..c208df46 100644 --- a/pydis_site/apps/api/viewsets/bot/nomination.py +++ b/pydis_site/apps/api/viewsets/bot/nomination.py @@ -302,7 +302,7 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge if not entry_filter.exists(): raise ValidationError( - {'actor': ['Actor don't exist or have not nominated user.']} + {'actor': ["Actor don't exist or have not nominated user."]} ) entry = entry_filter[0] -- cgit v1.2.3 From d8751ad37ed5a493554575ea3adb264def342664 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Fri, 5 Mar 2021 08:23:28 +0200 Subject: Fix grammar of error messages and change tests to match with changes --- pydis_site/apps/api/tests/test_nominations.py | 8 ++++---- pydis_site/apps/api/viewsets/bot/nomination.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pydis_site/apps/api/tests/test_nominations.py b/pydis_site/apps/api/tests/test_nominations.py index 1daa6f3f..c07679c5 100644 --- a/pydis_site/apps/api/tests/test_nominations.py +++ b/pydis_site/apps/api/tests/test_nominations.py @@ -78,7 +78,7 @@ class CreationTests(APISubdomainTestCase): response2 = self.client.post(url, data=data) self.assertEqual(response2.status_code, 400) self.assertEqual(response2.json(), { - 'actor': ['This actor have already created nomination entry for this nomination.'] + 'actor': ['This actor has already endorsed this nomination.'] }) def test_returns_400_for_missing_user(self): @@ -498,7 +498,7 @@ class NominationTests(APISubdomainTestCase): response = self.client.patch(url, data=data) self.assertEqual(response.status_code, 400) self.assertEqual(response.json(), { - 'reviewed': ['This field cannot be set if nomination is inactive.'] + 'reviewed': ['This field cannot be set if the nomination is inactive.'] }) def test_patch_nomination_set_reviewed_and_end(self): @@ -508,7 +508,7 @@ class NominationTests(APISubdomainTestCase): response = self.client.patch(url, data=data) self.assertEqual(response.status_code, 400) self.assertEqual(response.json(), { - 'reviewed': ['This field cannot be set same time than ending nomination.'] + 'reviewed': ['This field cannot be set while you are ending a nomination.'] }) def test_modifying_reason_without_actor(self): @@ -518,7 +518,7 @@ class NominationTests(APISubdomainTestCase): response = self.client.patch(url, data=data) self.assertEqual(response.status_code, 400) self.assertEqual(response.json(), { - 'actor': ['This field is required when editing reason.'] + 'actor': ['This field is required when editing the reason.'] }) def test_modifying_reason_with_unknown_actor(self): diff --git a/pydis_site/apps/api/viewsets/bot/nomination.py b/pydis_site/apps/api/viewsets/bot/nomination.py index c208df46..451264f4 100644 --- a/pydis_site/apps/api/viewsets/bot/nomination.py +++ b/pydis_site/apps/api/viewsets/bot/nomination.py @@ -222,7 +222,7 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge actor__id=entry_serializer.validated_data["actor"].id ).exists(): raise ValidationError( - {'actor': ['This actor have already created nomination entry for this nomination.']} + {'actor': ['This actor has already endorsed this nomination.']} ) NominationEntry.objects.create(**entry_serializer.validated_data) @@ -270,7 +270,7 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge if 'reviewed' in request.data: raise ValidationError( - {'reviewed': ['This field cannot be set same time than ending nomination.']} + {'reviewed': ['This field cannot be set while you are ending a nomination.']} ) instance.ended_at = timezone.now() @@ -286,13 +286,13 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge # 4. We are altering the reviewed state of the nomination. if not instance.active: raise ValidationError( - {'reviewed': ['This field cannot be set if nomination is inactive.']} + {'reviewed': ['This field cannot be set if the nomination is inactive.']} ) if 'reason' in request.data: if 'actor' not in request.data: raise ValidationError( - {'actor': ['This field is required when editing reason.']} + {'actor': ['This field is required when editing the reason.']} ) entry_filter = NominationEntry.objects.filter( -- cgit v1.2.3 From 53a9f5282adfdd3a2a4ebc819fcc9cd568a632ea Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sat, 6 Mar 2021 14:09:38 +0200 Subject: Fix grammar of nomination models --- pydis_site/apps/api/migrations/0068_split_nomination_tables.py | 4 ++-- pydis_site/apps/api/models/bot/nomination.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pydis_site/apps/api/migrations/0068_split_nomination_tables.py b/pydis_site/apps/api/migrations/0068_split_nomination_tables.py index 27c29017..79825ed7 100644 --- a/pydis_site/apps/api/migrations/0068_split_nomination_tables.py +++ b/pydis_site/apps/api/migrations/0068_split_nomination_tables.py @@ -51,7 +51,7 @@ class Migration(migrations.Migration): ('actor', 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')), - ('nomination', models.ForeignKey(help_text='Nomination to what this entry belongs.', + ('nomination', models.ForeignKey(help_text='The nomination this entry belongs to.', on_delete=django.db.models.deletion.CASCADE, to='api.Nomination', related_name='entries')), ], @@ -70,6 +70,6 @@ class Migration(migrations.Migration): migrations.AddField( model_name='nomination', name='reviewed', - field=models.BooleanField(default=False, help_text='Whether voting message have been made.'), + field=models.BooleanField(default=False, help_text='Whether a review was made.'), ), ] diff --git a/pydis_site/apps/api/models/bot/nomination.py b/pydis_site/apps/api/models/bot/nomination.py index 443200ff..221d8534 100644 --- a/pydis_site/apps/api/models/bot/nomination.py +++ b/pydis_site/apps/api/models/bot/nomination.py @@ -33,7 +33,7 @@ class Nomination(ModelReprMixin, models.Model): ) reviewed = models.BooleanField( default=False, - help_text="Whether voting message have been made." + help_text="Whether a review was made." ) def __str__(self): @@ -48,12 +48,12 @@ class Nomination(ModelReprMixin, models.Model): class NominationEntry(ModelReprMixin, models.Model): - """A nomination entry created by single staff.""" + """A nomination entry created by a single staff member.""" nomination = models.ForeignKey( Nomination, on_delete=models.CASCADE, - help_text="Nomination to what this entry belongs.", + help_text="The nomination this entry belongs to.", related_name="entries" ) actor = models.ForeignKey( -- cgit v1.2.3 From ce5a082458d8cc1433ce623d3b666fb6e0f82672 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sat, 6 Mar 2021 14:11:56 +0200 Subject: Change as -> to in nomination viewset docs --- pydis_site/apps/api/viewsets/bot/nomination.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydis_site/apps/api/viewsets/bot/nomination.py b/pydis_site/apps/api/viewsets/bot/nomination.py index 451264f4..9b9aa280 100644 --- a/pydis_site/apps/api/viewsets/bot/nomination.py +++ b/pydis_site/apps/api/viewsets/bot/nomination.py @@ -87,7 +87,7 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge The `user`, `reason` and `actor` fields are required and the `user` and `actor` need to know by the site. Providing other valid fields is not allowed and invalid fields are ignored. If `user` already has an - active nomination, a new nomination entry will be created and assigned as the + active nomination, a new nomination entry will be created and assigned to the active nomination. #### Request body -- cgit v1.2.3 From 9202d92d3cb27111eaf5d3cc1aee859f9cbe8918 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sat, 6 Mar 2021 14:12:56 +0200 Subject: Fix grammar of nomination viewset command about single entry for user --- pydis_site/apps/api/viewsets/bot/nomination.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydis_site/apps/api/viewsets/bot/nomination.py b/pydis_site/apps/api/viewsets/bot/nomination.py index 9b9aa280..0792a2a6 100644 --- a/pydis_site/apps/api/viewsets/bot/nomination.py +++ b/pydis_site/apps/api/viewsets/bot/nomination.py @@ -216,7 +216,7 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge ) entry_serializer.is_valid(raise_exception=True) - # Don't allow user creating many nomination entries for one nomination + # Don't allow a user to create many nomination entries in a single nomination if NominationEntry.objects.filter( nomination_id=nomination_filter[0].id, actor__id=entry_serializer.validated_data["actor"].id -- cgit v1.2.3 From 48eff5d27715acd658f6284f4d4e1cdbbf71bee6 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sat, 6 Mar 2021 14:13:50 +0200 Subject: Fix grammar of unknown actor error and change tests --- pydis_site/apps/api/tests/test_nominations.py | 2 +- pydis_site/apps/api/viewsets/bot/nomination.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pydis_site/apps/api/tests/test_nominations.py b/pydis_site/apps/api/tests/test_nominations.py index c07679c5..9cefbd8f 100644 --- a/pydis_site/apps/api/tests/test_nominations.py +++ b/pydis_site/apps/api/tests/test_nominations.py @@ -528,5 +528,5 @@ class NominationTests(APISubdomainTestCase): response = self.client.patch(url, data=data) self.assertEqual(response.status_code, 400) self.assertEqual(response.json(), { - 'actor': ["Actor don't exist or have not nominated user."] + 'actor': ["The actor doesn't exist or has not nominated the user."] }) diff --git a/pydis_site/apps/api/viewsets/bot/nomination.py b/pydis_site/apps/api/viewsets/bot/nomination.py index 0792a2a6..144daab0 100644 --- a/pydis_site/apps/api/viewsets/bot/nomination.py +++ b/pydis_site/apps/api/viewsets/bot/nomination.py @@ -302,7 +302,7 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge if not entry_filter.exists(): raise ValidationError( - {'actor': ["Actor don't exist or have not nominated user."]} + {'actor': ["The actor doesn't exist or has not nominated the user."]} ) entry = entry_filter[0] -- cgit v1.2.3 From 4f9c088f6b0458eb0ebb52ef899cdfdc57f2c43c Mon Sep 17 00:00:00 2001 From: Boris Muratov <8bee278@gmail.com> Date: Sun, 7 Mar 2021 00:59:41 +0200 Subject: Add route to get a member's data for helper review Added route for getting a user's join date, total messages, and top 3 channels by activity. This information will be used to auto-review nominees. --- postgres/init.sql | 18 ++++++++++++- pydis_site/apps/api/models/bot/metricity.py | 39 +++++++++++++++++++++++++++++ pydis_site/apps/api/viewsets/bot/user.py | 15 +++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/postgres/init.sql b/postgres/init.sql index 740063e7..ae86fca0 100644 --- a/postgres/init.sql +++ b/postgres/init.sql @@ -13,12 +13,28 @@ INSERT INTO users VALUES ( current_timestamp ); +CREATE TABLE channels ( + id varchar, + name varchar, + primary key(id) +); + +INSERT INTO channels VALUES( + '267659945086812160', + 'python-general' +); + +INSERT INTO channels VALUES( + '1234', + 'zebra' +); + CREATE TABLE messages ( id varchar, author_id varchar references users(id), is_deleted boolean, created_at timestamp, - channel_id varchar, + channel_id varchar references channels(id), primary key(id) ); diff --git a/pydis_site/apps/api/models/bot/metricity.py b/pydis_site/apps/api/models/bot/metricity.py index cae630f1..af5e1f3b 100644 --- a/pydis_site/apps/api/models/bot/metricity.py +++ b/pydis_site/apps/api/models/bot/metricity.py @@ -89,3 +89,42 @@ class Metricity: raise NotFound() return values[0] + + def top_channel_activity(self, user_id: str) -> int: + """ + Query the top three channels in which the user is most active. + + Help channels are grouped under "the help channels", + and off-topic channels are grouped under "off-topic". + """ + self.cursor.execute( + """ + SELECT + CASE + WHEN channels.name ILIKE 'help-%%' THEN 'the help channels' + WHEN channels.name ILIKE 'ot%%' THEN 'off-topic' + ELSE channels.name + END, + COUNT(1) + FROM + messages + LEFT JOIN channels ON channels.id = messages.channel_id + WHERE + author_id = '%s' + GROUP BY + 1 + ORDER BY + 2 DESC + LIMIT + 3; + """, + [user_id] + ) + + values = self.cursor.fetchall() + print(values) + + if not values: + raise NotFound() + + return values diff --git a/pydis_site/apps/api/viewsets/bot/user.py b/pydis_site/apps/api/viewsets/bot/user.py index 829e2694..5e1f8775 100644 --- a/pydis_site/apps/api/viewsets/bot/user.py +++ b/pydis_site/apps/api/viewsets/bot/user.py @@ -262,3 +262,18 @@ class UserViewSet(ModelViewSet): except NotFound: return Response(dict(detail="User not found in metricity"), status=status.HTTP_404_NOT_FOUND) + + @action(detail=True) + def metricity_review_data(self, request: Request, pk: str = None) -> Response: + """Request handler for metricity_review_data endpoint.""" + user = self.get_object() + + with Metricity() as metricity: + try: + data = metricity.user(user.id) + data["total_messages"] = metricity.total_messages(user.id) + data["top_channel_activity"] = metricity.top_channel_activity(user.id) + return Response(data, status=status.HTTP_200_OK) + except NotFound: + return Response(dict(detail="User not found in metricity"), + status=status.HTTP_404_NOT_FOUND) -- cgit v1.2.3 From d2690bbedb5f5ef221cbfaa42ad78ff8fcc263f2 Mon Sep 17 00:00:00 2001 From: Boris Muratov <8bee278@gmail.com> Date: Sun, 7 Mar 2021 01:05:09 +0200 Subject: Amend top_channel_activity return type --- pydis_site/apps/api/models/bot/metricity.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pydis_site/apps/api/models/bot/metricity.py b/pydis_site/apps/api/models/bot/metricity.py index af5e1f3b..29a43513 100644 --- a/pydis_site/apps/api/models/bot/metricity.py +++ b/pydis_site/apps/api/models/bot/metricity.py @@ -1,3 +1,5 @@ +from typing import List, Tuple + from django.db import connections BLOCK_INTERVAL = 10 * 60 # 10 minute blocks @@ -90,7 +92,7 @@ class Metricity: return values[0] - def top_channel_activity(self, user_id: str) -> int: + def top_channel_activity(self, user_id: str) -> List[Tuple[str, int]]: """ Query the top three channels in which the user is most active. -- cgit v1.2.3 From 3468d12b0c893bfa951e7baa85ada6a8716e5aef Mon Sep 17 00:00:00 2001 From: Boris Muratov <8bee278@gmail.com> Date: Sun, 7 Mar 2021 01:39:12 +0200 Subject: Added test for metricity-review-data --- pydis_site/apps/api/tests/test_users.py | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/pydis_site/apps/api/tests/test_users.py b/pydis_site/apps/api/tests/test_users.py index 69bbfefc..5851ac11 100644 --- a/pydis_site/apps/api/tests/test_users.py +++ b/pydis_site/apps/api/tests/test_users.py @@ -410,7 +410,7 @@ class UserMetricityTests(APISubdomainTestCase): joined_at = "foo" total_messages = 1 total_blocks = 1 - self.mock_metricity_user(joined_at, total_messages, total_blocks) + self.mock_metricity_user(joined_at, total_messages, total_blocks, []) # When url = reverse('bot:user-metricity-data', args=[0], host='api') @@ -442,7 +442,7 @@ class UserMetricityTests(APISubdomainTestCase): {'exception': ObjectDoesNotExist, 'voice_banned': False}, ] - self.mock_metricity_user("foo", 1, 1) + self.mock_metricity_user("foo", 1, 1, [["bar", 1]]) for case in cases: with self.subTest(exception=case['exception'], voice_banned=case['voice_banned']): @@ -455,7 +455,27 @@ class UserMetricityTests(APISubdomainTestCase): self.assertEqual(response.status_code, 200) self.assertEqual(response.json()["voice_banned"], case["voice_banned"]) - def mock_metricity_user(self, joined_at, total_messages, total_blocks): + def test_metricity_review_data(self): + # Given + joined_at = "foo" + total_messages = 10 + total_blocks = 1 + channel_activity = [["bar", 4], ["buzz", 6]] + self.mock_metricity_user(joined_at, total_messages, total_blocks, channel_activity) + + # When + url = reverse('bot:user-metricity-review-data', args=[0], host='api') + response = self.client.get(url) + + # Then + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json(), { + "joined_at": joined_at, + "top_channel_activity": channel_activity, + "total_messages": total_messages + }) + + def mock_metricity_user(self, joined_at, total_messages, total_blocks, top_channel_activity): patcher = patch("pydis_site.apps.api.viewsets.bot.user.Metricity") self.metricity = patcher.start() self.addCleanup(patcher.stop) @@ -463,6 +483,7 @@ class UserMetricityTests(APISubdomainTestCase): self.metricity.user.return_value = dict(joined_at=joined_at) self.metricity.total_messages.return_value = total_messages self.metricity.total_message_blocks.return_value = total_blocks + self.metricity.top_channel_activity.return_value = top_channel_activity def mock_no_metricity_user(self): patcher = patch("pydis_site.apps.api.viewsets.bot.user.Metricity") @@ -472,3 +493,4 @@ class UserMetricityTests(APISubdomainTestCase): self.metricity.user.side_effect = NotFound() self.metricity.total_messages.side_effect = NotFound() self.metricity.total_message_blocks.side_effect = NotFound() + self.metricity.top_channel_activity.side_effect = NotFound() -- cgit v1.2.3 From 66047f06e5ce060e79e49ef2dfab06475298b070 Mon Sep 17 00:00:00 2001 From: Boris Muratov <8bee278@gmail.com> Date: Sun, 7 Mar 2021 01:46:43 +0200 Subject: Test metricity-review-data when user doesn't exist --- pydis_site/apps/api/tests/test_users.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pydis_site/apps/api/tests/test_users.py b/pydis_site/apps/api/tests/test_users.py index 5851ac11..2ac06f6b 100644 --- a/pydis_site/apps/api/tests/test_users.py +++ b/pydis_site/apps/api/tests/test_users.py @@ -431,10 +431,13 @@ class UserMetricityTests(APISubdomainTestCase): # When url = reverse('bot:user-metricity-data', args=[0], host='api') - response = self.client.get(url) + response1 = self.client.get(url) + url = reverse('bot:user-metricity-review-data', args=[0], host='api') + response2 = self.client.get(url) # Then - self.assertEqual(response.status_code, 404) + self.assertEqual(response1.status_code, 404) + self.assertEqual(response2.status_code, 404) def test_metricity_voice_banned(self): cases = [ -- cgit v1.2.3 From 07847e959c9b2ac6a79ab38b900abc2d179d0478 Mon Sep 17 00:00:00 2001 From: Boris Muratov <8bee278@gmail.com> Date: Sun, 7 Mar 2021 11:58:30 +0200 Subject: Get rid of stray print Oops. --- pydis_site/apps/api/models/bot/metricity.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pydis_site/apps/api/models/bot/metricity.py b/pydis_site/apps/api/models/bot/metricity.py index 29a43513..7e2a68f2 100644 --- a/pydis_site/apps/api/models/bot/metricity.py +++ b/pydis_site/apps/api/models/bot/metricity.py @@ -124,7 +124,6 @@ class Metricity: ) values = self.cursor.fetchall() - print(values) if not values: raise NotFound() -- cgit v1.2.3 From fdc1be68a90d6ebd9dfe29369bc8a974bcaa8214 Mon Sep 17 00:00:00 2001 From: Boris Muratov <8bee278@gmail.com> Date: Thu, 11 Mar 2021 02:37:44 +0200 Subject: Ignore deleted messaages in message counts Co-authored-by: Joe Banks --- pydis_site/apps/api/models/bot/metricity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydis_site/apps/api/models/bot/metricity.py b/pydis_site/apps/api/models/bot/metricity.py index 7e2a68f2..db975d4e 100644 --- a/pydis_site/apps/api/models/bot/metricity.py +++ b/pydis_site/apps/api/models/bot/metricity.py @@ -112,7 +112,7 @@ class Metricity: messages LEFT JOIN channels ON channels.id = messages.channel_id WHERE - author_id = '%s' + author_id = '%s' AND NOT messages.is_deleted GROUP BY 1 ORDER BY -- cgit v1.2.3 From f4a67489b81f95978912582189cb23afb2169e8e Mon Sep 17 00:00:00 2001 From: Boris Muratov <8bee278@gmail.com> Date: Fri, 12 Mar 2021 15:35:47 +0200 Subject: Document endpoint in viewset docstring --- pydis_site/apps/api/viewsets/bot/user.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pydis_site/apps/api/viewsets/bot/user.py b/pydis_site/apps/api/viewsets/bot/user.py index 5e1f8775..25722f5a 100644 --- a/pydis_site/apps/api/viewsets/bot/user.py +++ b/pydis_site/apps/api/viewsets/bot/user.py @@ -119,6 +119,22 @@ class UserViewSet(ModelViewSet): - 200: returned on success - 404: if a user with the given `snowflake` could not be found + ### GET /bot/users//metricity_review_data + Gets metricity data for a single user's review by ID. + + #### Response format + >>> { + ... 'joined_at': '2020-08-26T08:09:43.507000', + ... 'top_channel_activity': [['off-topic', 15], + ... ['talent-pool', 4], + ... ['defcon', 2]], + ... 'total_messages': 22 + ... } + + #### 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 site. -- cgit v1.2.3 From 9b1b42a4899c0c3f47845def5d152e2bed6c8dd0 Mon Sep 17 00:00:00 2001 From: Boris Muratov <8bee278@gmail.com> Date: Fri, 12 Mar 2021 15:59:11 +0200 Subject: Split test_no_metricity_user to two tests by endpoints --- pydis_site/apps/api/tests/test_users.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/pydis_site/apps/api/tests/test_users.py b/pydis_site/apps/api/tests/test_users.py index 2ac06f6b..c43b916a 100644 --- a/pydis_site/apps/api/tests/test_users.py +++ b/pydis_site/apps/api/tests/test_users.py @@ -431,13 +431,21 @@ class UserMetricityTests(APISubdomainTestCase): # When url = reverse('bot:user-metricity-data', args=[0], host='api') - response1 = self.client.get(url) + response = self.client.get(url) + + # Then + self.assertEqual(response.status_code, 404) + + def test_no_metricity_user_for_review(self): + # Given + self.mock_no_metricity_user() + + # When url = reverse('bot:user-metricity-review-data', args=[0], host='api') - response2 = self.client.get(url) + response = self.client.get(url) # Then - self.assertEqual(response1.status_code, 404) - self.assertEqual(response2.status_code, 404) + self.assertEqual(response.status_code, 404) def test_metricity_voice_banned(self): cases = [ -- cgit v1.2.3 From cd797f801abaff8874e75b1ed093e17f67572a37 Mon Sep 17 00:00:00 2001 From: Boris Muratov <8bee278@gmail.com> Date: Fri, 12 Mar 2021 16:00:14 +0200 Subject: Add case in query for voice chat activity --- pydis_site/apps/api/models/bot/metricity.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pydis_site/apps/api/models/bot/metricity.py b/pydis_site/apps/api/models/bot/metricity.py index db975d4e..5daa5c66 100644 --- a/pydis_site/apps/api/models/bot/metricity.py +++ b/pydis_site/apps/api/models/bot/metricity.py @@ -105,6 +105,7 @@ class Metricity: CASE WHEN channels.name ILIKE 'help-%%' THEN 'the help channels' WHEN channels.name ILIKE 'ot%%' THEN 'off-topic' + WHEN channels.name ILIKE '%%voice%%' THEN 'voice chats' ELSE channels.name END, COUNT(1) -- cgit v1.2.3 From e4e5268122650679f6106b6241109ebc2d930d52 Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Sat, 13 Mar 2021 19:59:50 +0000 Subject: master => main --- .github/workflows/build.yaml | 2 +- .github/workflows/deploy.yaml | 2 +- .github/workflows/lint-test.yaml | 2 +- .github/workflows/sentry-release.yml | 4 ++-- CONTRIBUTING.md | 6 +++--- README.md | 14 +++++++------- pydis_site/static/css/error_pages.css | 2 +- pydis_site/templates/home/timeline.html | 2 +- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index d113cff7..873bcda4 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -4,7 +4,7 @@ on: workflow_run: workflows: ["Lint & Test"] branches: - - master + - main types: - completed diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index ff2652fd..8abf2bfb 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -4,7 +4,7 @@ on: workflow_run: workflows: ["Build"] branches: - - master + - main types: - completed diff --git a/.github/workflows/lint-test.yaml b/.github/workflows/lint-test.yaml index 397c2085..9e3d331d 100644 --- a/.github/workflows/lint-test.yaml +++ b/.github/workflows/lint-test.yaml @@ -3,7 +3,7 @@ name: Lint & Test on: push: branches: - - master + - main pull_request: diff --git a/.github/workflows/sentry-release.yml b/.github/workflows/sentry-release.yml index 01ed1daf..a3df5b1a 100644 --- a/.github/workflows/sentry-release.yml +++ b/.github/workflows/sentry-release.yml @@ -3,14 +3,14 @@ name: Create Sentry release on: push: branches: - - master + - main jobs: createSentryRelease: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@master + uses: actions/checkout@main - name: Create a Sentry.io release uses: tclindner/sentry-releases-action@v1.2.0 env: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index de682a31..84a59d54 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing to one of Our Projects -Our projects are open-source and are automatically deployed whenever commits are pushed to the `master` branch on each repository, so we've created a set of guidelines in order to keep everything clean and in working order. +Our projects are open-source and are automatically deployed whenever commits are pushed to the `main` branch on each repository, so we've created a set of guidelines in order to keep everything clean and in working order. Note that contributions may be rejected on the basis of a contributor failing to follow these guidelines. @@ -8,7 +8,7 @@ Note that contributions may be rejected on the basis of a contributor failing to 1. **No force-pushes** or modifying the Git history in any way. 2. If you have direct access to the repository, **create a branch for your changes** and create a pull request for that branch. If not, create a branch on a fork of the repository and create a pull request from there. - * It's common practice for a repository to reject direct pushes to `master`, so make branching a habit! + * It's common practice for a repository to reject direct pushes to `main`, so make branching a habit! * If PRing from your own fork, **ensure that "Allow edits from maintainers" is checked**. This gives permission for maintainers to commit changes directly to your fork, speeding up the review process. 3. **Adhere to the prevailing code style**, which we enforce using [`flake8`](http://flake8.pycqa.org/en/latest/index.html) and [`pre-commit`](https://pre-commit.com/). * Run `flake8` and `pre-commit` against your code [**before** you push it](https://soundcloud.com/lemonsaurusrex/lint-before-you-push). Your commit will be rejected by the build server if it fails to lint. @@ -18,7 +18,7 @@ Note that contributions may be rejected on the basis of a contributor failing to * Avoid making minor commits for fixing typos or linting errors. Since you've already set up a `pre-commit` hook to run the linting pipeline before a commit, you shouldn't be committing linting issues anyway. * A more in-depth guide to writing great commit messages can be found in Chris Beam's [*How to Write a Git Commit Message*](https://chris.beams.io/posts/git-commit/) 5. **Avoid frequent pushes to the main repository**. This goes for PRs opened against your fork as well. Our test build pipelines are triggered every time a push to the repository (or PR) is made. Try to batch your commits until you've finished working for that session, or you've reached a point where collaborators need your commits to continue their own work. This also provides you the opportunity to amend commits for minor changes rather than having to commit them on their own because you've already pushed. - * This includes merging master into your branch. Try to leave merging from master for after your PR passes review; a maintainer will bring your PR up to date before merging. Exceptions to this include: resolving merge conflicts, needing something that was pushed to master for your branch, or something was pushed to master that could potentionally affect the functionality of what you're writing. + * This includes merging main into your branch. Try to leave merging from main for after your PR passes review; a maintainer will bring your PR up to date before merging. Exceptions to this include: resolving merge conflicts, needing something that was pushed to main for your branch, or something was pushed to main that could potentionally affect the functionality of what you're writing. 6. **Don't fight the framework**. Every framework has its flaws, but the frameworks we've picked out have been carefully chosen for their particular merits. If you can avoid it, please resist reimplementing swathes of framework logic - the work has already been done for you! 7. If someone is working on an issue or pull request, **do not open your own pull request for the same task**. Instead, collaborate with the author(s) of the existing pull request. Duplicate PRs opened without communicating with the other author(s) and/or PyDis staff will be closed. Communication is key, and there's no point in two separate implementations of the same thing. * One option is to fork the other contributor's repository and submit your changes to their branch with your own pull request. We suggest following these guidelines when interacting with their repository as well. diff --git a/README.md b/README.md index a1eeb9fb..f88c2cf7 100644 --- a/README.md +++ b/README.md @@ -12,14 +12,14 @@ If you happen to run into issues with setup, please don't hesitate to open an is If you're looking to contribute or play around with the code, take a look at [the wiki][8] or the [`docs` directory](docs). If you're looking for things to do, check out [our issues][9]. -[1]: https://github.com/python-discord/site/workflows/Lint%20&%20Test/badge.svg?branch=master -[2]: https://github.com/python-discord/site/actions?query=workflow%3A%22Lint+%26+Test%22+branch%3Amaster -[3]: https://github.com/python-discord/site/workflows/Build%20&%20Deploy/badge.svg?branch=master -[4]: https://github.com/python-discord/site/actions?query=workflow%3A%22Build+%26+Deploy%22+branch%3Amaster -[5]: https://coveralls.io/repos/github/python-discord/site/badge.svg?branch=master -[6]: https://coveralls.io/github/python-discord/site?branch=master +[1]: https://github.com/python-discord/site/workflows/Lint%20&%20Test/badge.svg?branch=main +[2]: https://github.com/python-discord/site/actions?query=workflow%3A%22Lint+%26+Test%22+branch%3Amain +[3]: https://github.com/python-discord/site/workflows/Build%20&%20Deploy/badge.svg?branch=main +[4]: https://github.com/python-discord/site/actions?query=workflow%3A%22Build+%26+Deploy%22+branch%3Amain +[5]: https://coveralls.io/repos/github/python-discord/site/badge.svg?branch=main +[6]: https://coveralls.io/github/python-discord/site?branch=main [7]: https://pythondiscord.com [8]: https://pythondiscord.com/pages/contributing/site/ [9]: https://github.com/python-discord/site/issues -[10]: https://raw.githubusercontent.com/python-discord/branding/master/logos/badge/badge_github.svg +[10]: https://raw.githubusercontent.com/python-discord/branding/main/logos/badge/badge_github.svg [11]: https://discord.gg/python diff --git a/pydis_site/static/css/error_pages.css b/pydis_site/static/css/error_pages.css index 77bb7e2b..ee41fa5c 100644 --- a/pydis_site/static/css/error_pages.css +++ b/pydis_site/static/css/error_pages.css @@ -4,7 +4,7 @@ html { body { background-color: #7289DA; - background-image: url("https://raw.githubusercontent.com/python-discord/branding/master/logos/banner_pattern/banner_pattern.svg"); + background-image: url("https://raw.githubusercontent.com/python-discord/branding/main/logos/banner_pattern/banner_pattern.svg"); background-size: 128px; font-family: "Hind", "Helvetica", "Arial", sans-serif; display: flex; diff --git a/pydis_site/templates/home/timeline.html b/pydis_site/templates/home/timeline.html index f3c58fc2..ece2e4e5 100644 --- a/pydis_site/templates/home/timeline.html +++ b/pydis_site/templates/home/timeline.html @@ -54,7 +54,7 @@

Our logo is born. Thanks @Aperture!

+ src="https://raw.githubusercontent.com/python-discord/branding/main/logos/logo_banner/logo_site_banner.svg">

-- cgit v1.2.3 From de7c6b37623464579794dcfb00db0ff106c7dc82 Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Sat, 13 Mar 2021 20:08:19 +0000 Subject: Dockerfile optimisations Copies dependencies before code for caching and removes git dependency. --- Dockerfile | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/Dockerfile b/Dockerfile index 44567b8a..5d8ba5da 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,25 +8,20 @@ ENV PIP_NO_CACHE_DIR=false \ PIPENV_HIDE_EMOJIS=1 \ PIPENV_NOSPIN=1 -# Install git -RUN apt-get -y update \ - && apt-get install -y \ - git \ - && rm -rf /var/lib/apt/lists/* - -# Create non-root user. -RUN useradd --system --shell /bin/false --uid 1500 pysite - # Install pipenv RUN pip install -U pipenv # Copy the project files into working directory WORKDIR /app -COPY . . + +# Copy dependency files +COPY Pipfile Pipfile.lock ./ # Install project dependencies RUN pipenv install --system --deploy +# Copy project code +COPY . . # Set Git SHA environment variable ARG git_sha="development" -- cgit v1.2.3 From 9d68056285490e2b62e660b7012ef32f82182029 Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Sun, 14 Mar 2021 21:59:03 +0000 Subject: Update deploy.yaml --- .github/workflows/deploy.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 8abf2bfb..efc08040 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -13,6 +13,7 @@ jobs: if: github.event.workflow_run.conclusion == 'success' name: Deploy to Kubernetes Cluster runs-on: ubuntu-latest + environment: production steps: - name: Create SHA Container Tag -- cgit v1.2.3 From bc66963ff6d5dcb64e08c8cc35bd18fbedf2f025 Mon Sep 17 00:00:00 2001 From: Boris Muratov <8bee278@gmail.com> Date: Tue, 16 Mar 2021 01:41:53 +0200 Subject: Add more records to test the endpoint with --- postgres/init.sql | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/postgres/init.sql b/postgres/init.sql index ae86fca0..190a705c 100644 --- a/postgres/init.sql +++ b/postgres/init.sql @@ -13,6 +13,11 @@ INSERT INTO users VALUES ( current_timestamp ); +INSERT INTO users VALUES ( + 1, + current_timestamp +); + CREATE TABLE channels ( id varchar, name varchar, @@ -24,6 +29,36 @@ INSERT INTO channels VALUES( 'python-general' ); +INSERT INTO channels VALUES( + '11', + 'help-apple' +); + +INSERT INTO channels VALUES( + '12', + 'help-cherry' +); + +INSERT INTO channels VALUES( + '21', + 'ot0-hello' +); + +INSERT INTO channels VALUES( + '22', + 'ot1-world' +); + +INSERT INTO channels VALUES( + '31', + 'voice-chat-0' +); + +INSERT INTO channels VALUES( + '32', + 'code-help-voice-0' +); + INSERT INTO channels VALUES( '1234', 'zebra' @@ -53,3 +88,59 @@ INSERT INTO messages VALUES( now() + INTERVAL '10 minutes,', '1234' ); + +INSERT INTO messages VALUES( + 2, + 0, + false, + now(), + '11' +); + +INSERT INTO messages VALUES( + 3, + 0, + false, + now(), + '12' +); + +INSERT INTO messages VALUES( + 4, + 1, + false, + now(), + '21' +); + +INSERT INTO messages VALUES( + 5, + 1, + false, + now(), + '22' +); + +INSERT INTO messages VALUES( + 6, + 1, + false, + now(), + '31' +); + +INSERT INTO messages VALUES( + 7, + 1, + false, + now(), + '32' +); + +INSERT INTO messages VALUES( + 8, + 1, + true, + now(), + '32' +); -- cgit v1.2.3 From 7064ebaa12b6bfab30fc7c46a8ab2770c140a9b7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Mar 2021 19:50:15 +0000 Subject: Bump urllib3 from 1.26.2 to 1.26.3 Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.26.2 to 1.26.3. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/1.26.2...1.26.3) Signed-off-by: dependabot[bot] --- Pipfile.lock | 282 +++++++++++++++++++++++++++-------------------------------- 1 file changed, 127 insertions(+), 155 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index fe97c5dd..7c55a5d2 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -21,7 +21,6 @@ "sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17", "sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0" ], - "markers": "python_version >= '3.5'", "version": "==3.3.1" }, "bleach": { @@ -29,7 +28,6 @@ "sha256:2bce3d8fab545a6528c8fa5d9f9ae8ebc85a56da365c7f85180bfe96a35ef22f", "sha256:3c4c520fdb9db59ef139915a5db79f8b51bc2a7257ea0389f30c846883430a4b" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==3.1.5" }, "certifi": { @@ -41,79 +39,76 @@ }, "cffi": { "hashes": [ - "sha256:00a1ba5e2e95684448de9b89888ccd02c98d512064b4cb987d48f4b40aa0421e", - "sha256:00e28066507bfc3fe865a31f325c8391a1ac2916219340f87dfad602c3e48e5d", - "sha256:045d792900a75e8b1e1b0ab6787dd733a8190ffcf80e8c8ceb2fb10a29ff238a", - "sha256:0638c3ae1a0edfb77c6765d487fee624d2b1ee1bdfeffc1f0b58c64d149e7eec", - "sha256:105abaf8a6075dc96c1fe5ae7aae073f4696f2905fde6aeada4c9d2926752362", - "sha256:155136b51fd733fa94e1c2ea5211dcd4c8879869008fc811648f16541bf99668", - "sha256:1a465cbe98a7fd391d47dce4b8f7e5b921e6cd805ef421d04f5f66ba8f06086c", - "sha256:1d2c4994f515e5b485fd6d3a73d05526aa0fcf248eb135996b088d25dfa1865b", - "sha256:2c24d61263f511551f740d1a065eb0212db1dbbbbd241db758f5244281590c06", - "sha256:51a8b381b16ddd370178a65360ebe15fbc1c71cf6f584613a7ea08bfad946698", - "sha256:594234691ac0e9b770aee9fcdb8fa02c22e43e5c619456efd0d6c2bf276f3eb2", - "sha256:5cf4be6c304ad0b6602f5c4e90e2f59b47653ac1ed9c662ed379fe48a8f26b0c", - "sha256:64081b3f8f6f3c3de6191ec89d7dc6c86a8a43911f7ecb422c60e90c70be41c7", - "sha256:6bc25fc545a6b3d57b5f8618e59fc13d3a3a68431e8ca5fd4c13241cd70d0009", - "sha256:798caa2a2384b1cbe8a2a139d80734c9db54f9cc155c99d7cc92441a23871c03", - "sha256:7c6b1dece89874d9541fc974917b631406233ea0440d0bdfbb8e03bf39a49b3b", - "sha256:840793c68105fe031f34d6a086eaea153a0cd5c491cde82a74b420edd0a2b909", - "sha256:8d6603078baf4e11edc4168a514c5ce5b3ba6e3e9c374298cb88437957960a53", - "sha256:9cc46bc107224ff5b6d04369e7c595acb700c3613ad7bcf2e2012f62ece80c35", - "sha256:9f7a31251289b2ab6d4012f6e83e58bc3b96bd151f5b5262467f4bb6b34a7c26", - "sha256:9ffb888f19d54a4d4dfd4b3f29bc2c16aa4972f1c2ab9c4ab09b8ab8685b9c2b", - "sha256:a5ed8c05548b54b998b9498753fb9cadbfd92ee88e884641377d8a8b291bcc01", - "sha256:a7711edca4dcef1a75257b50a2fbfe92a65187c47dab5a0f1b9b332c5919a3fb", - "sha256:af5c59122a011049aad5dd87424b8e65a80e4a6477419c0c1015f73fb5ea0293", - "sha256:b18e0a9ef57d2b41f5c68beefa32317d286c3d6ac0484efd10d6e07491bb95dd", - "sha256:b4e248d1087abf9f4c10f3c398896c87ce82a9856494a7155823eb45a892395d", - "sha256:ba4e9e0ae13fc41c6b23299545e5ef73055213e466bd107953e4a013a5ddd7e3", - "sha256:c6332685306b6417a91b1ff9fae889b3ba65c2292d64bd9245c093b1b284809d", - "sha256:d5ff0621c88ce83a28a10d2ce719b2ee85635e85c515f12bac99a95306da4b2e", - "sha256:d9efd8b7a3ef378dd61a1e77367f1924375befc2eba06168b6ebfa903a5e59ca", - "sha256:df5169c4396adc04f9b0a05f13c074df878b6052430e03f50e68adf3a57aa28d", - "sha256:ebb253464a5d0482b191274f1c8bf00e33f7e0b9c66405fbffc61ed2c839c775", - "sha256:ec80dc47f54e6e9a78181ce05feb71a0353854cc26999db963695f950b5fb375", - "sha256:f032b34669220030f905152045dfa27741ce1a6db3324a5bc0b96b6c7420c87b", - "sha256:f60567825f791c6f8a592f3c6e3bd93dd2934e3f9dac189308426bd76b00ef3b", - "sha256:f803eaa94c2fcda012c047e62bc7a51b0bdabda1cad7a92a522694ea2d76e49f" - ], - "version": "==1.14.4" + "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813", + "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06", + "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea", + "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee", + "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396", + "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73", + "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315", + "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1", + "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49", + "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892", + "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482", + "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058", + "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5", + "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53", + "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045", + "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3", + "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5", + "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e", + "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c", + "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369", + "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827", + "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053", + "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa", + "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4", + "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322", + "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132", + "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62", + "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa", + "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0", + "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396", + "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e", + "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991", + "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6", + "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1", + "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406", + "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d", + "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c" + ], + "version": "==1.14.5" }, "chardet": { "hashes": [ "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==4.0.0" }, "cryptography": { "hashes": [ - "sha256:0003a52a123602e1acee177dc90dd201f9bb1e73f24a070db7d36c588e8f5c7d", - "sha256:0e85aaae861d0485eb5a79d33226dd6248d2a9f133b81532c8f5aae37de10ff7", - "sha256:594a1db4511bc4d960571536abe21b4e5c3003e8750ab8365fafce71c5d86901", - "sha256:69e836c9e5ff4373ce6d3ab311c1a2eed274793083858d3cd4c7d12ce20d5f9c", - "sha256:788a3c9942df5e4371c199d10383f44a105d67d401fb4304178020142f020244", - "sha256:7e177e4bea2de937a584b13645cab32f25e3d96fc0bc4a4cf99c27dc77682be6", - "sha256:83d9d2dfec70364a74f4e7c70ad04d3ca2e6a08b703606993407bf46b97868c5", - "sha256:84ef7a0c10c24a7773163f917f1cb6b4444597efd505a8aed0a22e8c4780f27e", - "sha256:9e21301f7a1e7c03dbea73e8602905a4ebba641547a462b26dd03451e5769e7c", - "sha256:9f6b0492d111b43de5f70052e24c1f0951cb9e6022188ebcb1cc3a3d301469b0", - "sha256:a69bd3c68b98298f490e84519b954335154917eaab52cf582fa2c5c7efc6e812", - "sha256:b4890d5fb9b7a23e3bf8abf5a8a7da8e228f1e97dc96b30b95685df840b6914a", - "sha256:c366df0401d1ec4e548bebe8f91d55ebcc0ec3137900d214dd7aac8427ef3030", - "sha256:dc42f645f8f3a489c3dd416730a514e7a91a59510ddaadc09d04224c098d3302" - ], - "version": "==3.3.1" + "sha256:066bc53f052dfeda2f2d7c195cf16fb3e5ff13e1b6b7415b468514b40b381a5b", + "sha256:0923ba600d00718d63a3976f23cab19aef10c1765038945628cd9be047ad0336", + "sha256:2d32223e5b0ee02943f32b19245b61a62db83a882f0e76cc564e1cec60d48f87", + "sha256:4169a27b818de4a1860720108b55a2801f32b6ae79e7f99c00d79f2a2822eeb7", + "sha256:57ad77d32917bc55299b16d3b996ffa42a1c73c6cfa829b14043c561288d2799", + "sha256:5ecf2bcb34d17415e89b546dbb44e73080f747e504273e4d4987630493cded1b", + "sha256:600cf9bfe75e96d965509a4c0b2b183f74a4fa6f5331dcb40fb7b77b7c2484df", + "sha256:66b57a9ca4b3221d51b237094b0303843b914b7d5afd4349970bb26518e350b0", + "sha256:93cfe5b7ff006de13e1e89830810ecbd014791b042cbe5eec253be11ac2b28f3", + "sha256:9e98b452132963678e3ac6c73f7010fe53adf72209a32854d55690acac3f6724", + "sha256:df186fcbf86dc1ce56305becb8434e4b6b7504bc724b71ad7a3239e0c9d14ef2", + "sha256:fec7fb46b10da10d9e1d078d1ff8ed9e05ae14f431fdbd11145edd0550b9a964" + ], + "version": "==3.4.6" }, "defusedxml": { "hashes": [ - "sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93", - "sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5" + "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", + "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==0.6.0" + "version": "==0.7.1" }, "django": { "hashes": [ @@ -173,7 +168,6 @@ "sha256:90eb236eb4f1a92124bd7c37852bbe09c0d21158477cc237556d59842a91c509", "sha256:dfdb3af75ad27cdd4458b0544ec8574174f2b90f99bc2cafab6a15b4bc1895a8" ], - "markers": "python_version >= '3.5'", "version": "==0.11.0" }, "django-nyt": { @@ -219,7 +213,6 @@ "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.10" }, "libsass": { @@ -245,7 +238,6 @@ "sha256:1fafe3f1ecabfb514a5285fca634a53c1b32a81cb0feb154264d55bf2ff22c17", "sha256:c467cd6233885534bf0fe96e62e3cf46cfc1605112356c4f9981512b8174de59" ], - "markers": "python_version >= '3.5'", "version": "==3.2.2" }, "oauthlib": { @@ -253,50 +245,52 @@ "sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889", "sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==3.1.0" }, "packaging": { "hashes": [ - "sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858", - "sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093" + "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5", + "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==20.8" + "version": "==20.9" }, "pillow": { "hashes": [ - "sha256:006de60d7580d81f4a1a7e9f0173dc90a932e3905cc4d47ea909bc946302311a", - "sha256:0a2e8d03787ec7ad71dc18aec9367c946ef8ef50e1e78c71f743bc3a770f9fae", - "sha256:0eeeae397e5a79dc088d8297a4c2c6f901f8fb30db47795113a4a605d0f1e5ce", - "sha256:11c5c6e9b02c9dac08af04f093eb5a2f84857df70a7d4a6a6ad461aca803fb9e", - "sha256:2fb113757a369a6cdb189f8df3226e995acfed0a8919a72416626af1a0a71140", - "sha256:4b0ef2470c4979e345e4e0cc1bbac65fda11d0d7b789dbac035e4c6ce3f98adb", - "sha256:59e903ca800c8cfd1ebe482349ec7c35687b95e98cefae213e271c8c7fffa021", - "sha256:5abd653a23c35d980b332bc0431d39663b1709d64142e3652890df4c9b6970f6", - "sha256:5f9403af9c790cc18411ea398a6950ee2def2a830ad0cfe6dc9122e6d528b302", - "sha256:6b4a8fd632b4ebee28282a9fef4c341835a1aa8671e2770b6f89adc8e8c2703c", - "sha256:6c1aca8231625115104a06e4389fcd9ec88f0c9befbabd80dc206c35561be271", - "sha256:795e91a60f291e75de2e20e6bdd67770f793c8605b553cb6e4387ce0cb302e09", - "sha256:7ba0ba61252ab23052e642abdb17fd08fdcfdbbf3b74c969a30c58ac1ade7cd3", - "sha256:7c9401e68730d6c4245b8e361d3d13e1035cbc94db86b49dc7da8bec235d0015", - "sha256:81f812d8f5e8a09b246515fac141e9d10113229bc33ea073fec11403b016bcf3", - "sha256:895d54c0ddc78a478c80f9c438579ac15f3e27bf442c2a9aa74d41d0e4d12544", - "sha256:8de332053707c80963b589b22f8e0229f1be1f3ca862a932c1bcd48dafb18dd8", - "sha256:92c882b70a40c79de9f5294dc99390671e07fc0b0113d472cbea3fde15db1792", - "sha256:95edb1ed513e68bddc2aee3de66ceaf743590bf16c023fb9977adc4be15bd3f0", - "sha256:b63d4ff734263ae4ce6593798bcfee6dbfb00523c82753a3a03cbc05555a9cc3", - "sha256:bd7bf289e05470b1bc74889d1466d9ad4a56d201f24397557b6f65c24a6844b8", - "sha256:cc3ea6b23954da84dbee8025c616040d9aa5eaf34ea6895a0a762ee9d3e12e11", - "sha256:cc9ec588c6ef3a1325fa032ec14d97b7309db493782ea8c304666fb10c3bd9a7", - "sha256:d3d07c86d4efa1facdf32aa878bd508c0dc4f87c48125cc16b937baa4e5b5e11", - "sha256:d8a96747df78cda35980905bf26e72960cba6d355ace4780d4bdde3b217cdf1e", - "sha256:e38d58d9138ef972fceb7aeec4be02e3f01d383723965bfcef14d174c8ccd039", - "sha256:eb472586374dc66b31e36e14720747595c2b265ae962987261f044e5cce644b5", - "sha256:fbd922f702582cb0d71ef94442bfca57624352622d75e3be7a1e7e9360b07e72" - ], - "markers": "python_version >= '3.6'", - "version": "==8.0.1" + "sha256:15306d71a1e96d7e271fd2a0737038b5a92ca2978d2e38b6ced7966583e3d5af", + "sha256:1940fc4d361f9cc7e558d6f56ff38d7351b53052fd7911f4b60cd7bc091ea3b1", + "sha256:1f93f2fe211f1ef75e6f589327f4d4f8545d5c8e826231b042b483d8383e8a7c", + "sha256:30d33a1a6400132e6f521640dd3f64578ac9bfb79a619416d7e8802b4ce1dd55", + "sha256:328240f7dddf77783e72d5ed79899a6b48bc6681f8d1f6001f55933cb4905060", + "sha256:46c2bcf8e1e75d154e78417b3e3c64e96def738c2a25435e74909e127a8cba5e", + "sha256:5762ebb4436f46b566fc6351d67a9b5386b5e5de4e58fdaa18a1c83e0e20f1a8", + "sha256:5a2d957eb4aba9d48170b8fe6538ec1fbc2119ffe6373782c03d8acad3323f2e", + "sha256:5cf03b9534aca63b192856aa601c68d0764810857786ea5da652581f3a44c2b0", + "sha256:5daba2b40782c1c5157a788ec4454067c6616f5a0c1b70e26ac326a880c2d328", + "sha256:63cd413ac52ee3f67057223d363f4f82ce966e64906aea046daf46695e3c8238", + "sha256:6efac40344d8f668b6c4533ae02a48d52fd852ef0654cc6f19f6ac146399c733", + "sha256:71b01ee69e7df527439d7752a2ce8fb89e19a32df484a308eca3e81f673d3a03", + "sha256:71f31ee4df3d5e0b366dd362007740106d3210fb6a56ec4b581a5324ba254f06", + "sha256:72027ebf682abc9bafd93b43edc44279f641e8996fb2945104471419113cfc71", + "sha256:74cd9aa648ed6dd25e572453eb09b08817a1e3d9f8d1bd4d8403d99e42ea790b", + "sha256:81b3716cc9744ffdf76b39afb6247eae754186838cedad0b0ac63b2571253fe6", + "sha256:8565355a29655b28fdc2c666fd9a3890fe5edc6639d128814fafecfae2d70910", + "sha256:87f42c976f91ca2fc21a3293e25bd3cd895918597db1b95b93cbd949f7d019ce", + "sha256:89e4c757a91b8c55d97c91fa09c69b3677c227b942fa749e9a66eef602f59c28", + "sha256:8c4e32218c764bc27fe49b7328195579581aa419920edcc321c4cb877c65258d", + "sha256:903293320efe2466c1ab3509a33d6b866dc850cfd0c5d9cc92632014cec185fb", + "sha256:90882c6f084ef68b71bba190209a734bf90abb82ab5e8f64444c71d5974008c6", + "sha256:98afcac3205d31ab6a10c5006b0cf040d0026a68ec051edd3517b776c1d78b09", + "sha256:a01da2c266d9868c4f91a9c6faf47a251f23b9a862dce81d2ff583135206f5be", + "sha256:aeab4cd016e11e7aa5cfc49dcff8e51561fa64818a0be86efa82c7038e9369d0", + "sha256:b07c660e014852d98a00a91adfbe25033898a9d90a8f39beb2437d22a203fc44", + "sha256:bead24c0ae3f1f6afcb915a057943ccf65fc755d11a1410a909c1fefb6c06ad1", + "sha256:d1d6bca39bb6dd94fba23cdb3eeaea5e30c7717c5343004d900e2a63b132c341", + "sha256:e2cd8ac157c1e5ae88b6dd790648ee5d2777e76f1e5c7d184eaddb2938594f34", + "sha256:e5739ae63636a52b706a0facec77b2b58e485637e1638202556156e424a02dc2", + "sha256:f36c3ff63d6fc509ce599a2f5b0d0732189eed653420e7294c039d342c6e204a", + "sha256:f91b50ad88048d795c0ad004abbe1390aa1882073b1dca10bfd55d0b8cf18ec5" + ], + "version": "==8.1.2" }, "psycopg2-binary": { "hashes": [ @@ -344,7 +338,6 @@ "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0", "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.20" }, "pygments": { @@ -360,17 +353,16 @@ "crypto" ], "hashes": [ - "sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e", - "sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96" + "sha256:a5c70a06e1f33d81ef25eecd50d50bd30e34de1ca8b2b9fa3fe0daaabcf69bf7", + "sha256:b70b15f89dc69b993d8a8d32c299032d5355c82f9b5b7e851d1a6d706dffe847" ], - "version": "==1.7.1" + "version": "==2.0.1" }, "pyparsing": { "hashes": [ "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.4.7" }, "python3-openid": { @@ -382,10 +374,10 @@ }, "pytz": { "hashes": [ - "sha256:3e6b7dd2d1e0a59084bcee14a17af60c5c562cdc16d828e8eba2e683d3a7e268", - "sha256:5c55e189b682d420be27c6995ba6edce0c0a77dd67bfbe2ae6607134d5851ffd" + "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da", + "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798" ], - "version": "==2020.4" + "version": "==2021.1" }, "pyyaml": { "hashes": [ @@ -417,8 +409,7 @@ "requests-oauthlib": { "hashes": [ "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d", - "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a", - "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc" + "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a" ], "version": "==1.3.0" }, @@ -435,7 +426,6 @@ "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.15.0" }, "sorl-thumbnail": { @@ -443,7 +433,6 @@ "sha256:c56cd651feab3bdc415d5301600198e2e70c08234dad48b8f6cfa4746cc102c7", "sha256:fbe6dfd66a1aceb7e0203895ff5622775e50266f8d8cfd841fe1500bd3e19018" ], - "markers": "python_version >= '3.4'", "version": "==12.7.0" }, "sqlparse": { @@ -451,16 +440,15 @@ "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0", "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8" ], - "markers": "python_version >= '3.5'", "version": "==0.4.1" }, "urllib3": { "hashes": [ - "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08", - "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473" + "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80", + "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", - "version": "==1.26.2" + "index": "pypi", + "version": "==1.26.3" }, "webencodings": { "hashes": [ @@ -499,7 +487,6 @@ "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==20.3.0" }, "bandit": { @@ -521,7 +508,6 @@ "sha256:32e43d604bbe7896fe7c248a9c2276447dbef840feb28fe20494f62af110211d", "sha256:cf22deb93d4bcf92f345a5c3cd39d3d41d6340adc60c78bbbd6588c384fda6a1" ], - "markers": "python_full_version >= '3.6.1'", "version": "==3.2.0" }, "chardet": { @@ -529,7 +515,6 @@ "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==4.0.0" }, "coverage": { @@ -697,31 +682,27 @@ "sha256:91f36bfb1ab7949b3b40e23736db18231bf7593edada2ba5c3a174a7b23657ac", "sha256:c9e1f2d0db7ddb9a704c2a0217be31214e91a4fe1dea1efad19ae42ba0c285c9" ], - "markers": "python_version >= '3.4'", "version": "==4.0.5" }, "gitpython": { "hashes": [ - "sha256:6eea89b655917b500437e9668e4a12eabdcf00229a0df1762aabd692ef9b746b", - "sha256:befa4d101f91bad1b632df4308ec64555db684c360bd7d2130b4807d49ce86b8" + "sha256:3283ae2fba31c913d857e12e5ba5f9a7772bbc064ae2bb09efafa71b0dd4939b", + "sha256:be27633e7509e58391f10207cd32b2a6cf5b908f92d9cd30da2e514e1137af61" ], - "markers": "python_version >= '3.4'", - "version": "==3.1.11" + "version": "==3.1.14" }, "identify": { "hashes": [ - "sha256:943cd299ac7f5715fcb3f684e2fc1594c1e0f22a90d15398e5888143bd4144b5", - "sha256:cc86e6a9a390879dcc2976cef169dd9cc48843ed70b7380f321d1b118163c60e" + "sha256:99d40094d8c9fc0b88b379200ecb4f8345a593878656867fa5363abc850766ab", + "sha256:f516f69e62df018a3965a835dccfb3397ed02ddaf513584633c9acaf084be3f2" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.5.10" + "version": "==2.1.4" }, "idna": { "hashes": [ "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.10" }, "mccabe": { @@ -744,7 +725,6 @@ "sha256:5fad80b613c402d5b7df7bd84812548b2a61e9977387a80a5fc5c396492b13c9", "sha256:b236cde0ac9a6aedd5e3c34517b423cd4fd97ef723849da6b0d2231142d89c00" ], - "markers": "python_version >= '2.6'", "version": "==5.5.1" }, "pep8-naming": { @@ -768,23 +748,20 @@ "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367", "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.6.0" }, "pydocstyle": { "hashes": [ - "sha256:19b86fa8617ed916776a11cd8bc0197e5b9856d5433b777f51a3defe13075325", - "sha256:aca749e190a01726a4fb472dd4ef23b5c9da7b9205c0a7857c06533de13fd678" + "sha256:164befb520d851dbcf0e029681b91f4f599c62c5cd8933fd54b1bfbd50e89e1f", + "sha256:d4449cf16d7e6709f63192146706933c7a334af7c0f083904799ccb851c50f6d" ], - "markers": "python_version >= '3.5'", - "version": "==5.1.1" + "version": "==6.0.0" }, "pyflakes": { "hashes": [ "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92", "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.2.0" }, "pyyaml": { @@ -819,30 +796,27 @@ "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.15.0" }, "smmap": { "hashes": [ - "sha256:54c44c197c819d5ef1991799a7e30b662d1e520f2ac75c9efbeb54a742214cf4", - "sha256:9c98bbd1f9786d22f14b3d4126894d56befb835ec90cef151af566c7e19b5d24" + "sha256:7bfcf367828031dc893530a29cb35eb8c8f2d7c8f2d0989354d75d24c8573714", + "sha256:84c2751ef3072d4f6b2785ec7ee40244c6f45eb934d9e543e2c51f1bd3d54c50" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==3.0.4" + "version": "==3.0.5" }, "snowballstemmer": { "hashes": [ - "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0", - "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52" + "sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2", + "sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914" ], - "version": "==2.0.0" + "version": "==2.1.0" }, "stevedore": { "hashes": [ "sha256:3a5bbd0652bf552748871eaa73a4a8dc2899786bc497a2aa1fcb4dcdb0debeee", "sha256:50d7b78fbaf0d04cd62411188fa7eedcb03eb7f4c4b37005615ceebe582aa82a" ], - "markers": "python_version >= '3.6'", "version": "==3.3.0" }, "toml": { @@ -850,24 +824,22 @@ "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.10.2" }, "urllib3": { "hashes": [ - "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08", - "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473" + "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80", + "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", - "version": "==1.26.2" + "index": "pypi", + "version": "==1.26.3" }, "virtualenv": { "hashes": [ - "sha256:54b05fc737ea9c9ee9f8340f579e5da5b09fb64fd010ab5757eb90268616907c", - "sha256:b7a8ec323ee02fb2312f098b6b4c9de99559b462775bc8fe3627a73706603c1b" + "sha256:49ec4eb4c224c6f7dd81bb6d0a28a09ecae5894f4e593c89b0db0885f565a107", + "sha256:83f95875d382c7abafe06bd2a4cdd1b363e1bb77e02f155ebe8ac082a916b37c" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==20.2.2" + "version": "==20.4.3" } } } -- cgit v1.2.3 From 90c5e62d1c0e8e347d1f16df4849ea942086affe Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Tue, 23 Mar 2021 09:43:32 +0300 Subject: Adds Streamyard Banner To Homepage --- pydis_site/static/images/sponsors/streamyard.png | Bin 0 -> 86678 bytes pydis_site/templates/home/index.html | 3 +++ 2 files changed, 3 insertions(+) create mode 100644 pydis_site/static/images/sponsors/streamyard.png diff --git a/pydis_site/static/images/sponsors/streamyard.png b/pydis_site/static/images/sponsors/streamyard.png new file mode 100644 index 00000000..a1527e8d Binary files /dev/null and b/pydis_site/static/images/sponsors/streamyard.png differ diff --git a/pydis_site/templates/home/index.html b/pydis_site/templates/home/index.html index 04815b7f..c35af2aa 100644 --- a/pydis_site/templates/home/index.html +++ b/pydis_site/templates/home/index.html @@ -204,6 +204,9 @@ Notion + + StreamYard +
-- cgit v1.2.3 From 7e05e56d7e650adbaf2e8f552408368d1ff55521 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Tue, 23 Mar 2021 13:09:46 +0300 Subject: Centers Sponsor Logos Center justifies sponsor logos on the home page to make wrapping look cleaner. --- pydis_site/static/css/home/index.css | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pydis_site/static/css/home/index.css b/pydis_site/static/css/home/index.css index 58ca8888..106feb7c 100644 --- a/pydis_site/static/css/home/index.css +++ b/pydis_site/static/css/home/index.css @@ -208,7 +208,11 @@ h1 { padding-bottom: 3rem; } +#sponsors .columns { + justify-content: center; +} + #sponsors img { height: 5rem; - margin-right: 2rem; + margin: auto 1rem; } -- cgit v1.2.3 From 20737ab238719ce7a2df78b0ca5101884f28db0d Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Tue, 23 Mar 2021 13:42:14 +0300 Subject: Centers Sponsor Title --- pydis_site/static/css/home/index.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pydis_site/static/css/home/index.css b/pydis_site/static/css/home/index.css index 106feb7c..808c0517 100644 --- a/pydis_site/static/css/home/index.css +++ b/pydis_site/static/css/home/index.css @@ -206,6 +206,8 @@ h1 { #sponsors .hero-body { padding-top: 2rem; padding-bottom: 3rem; + + text-align: center; } #sponsors .columns { -- cgit v1.2.3 From c7c571ae404fe6f0ffba53713d86c14090366f0b Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Tue, 23 Mar 2021 13:43:00 +0300 Subject: Reduces The Width Of The Sponsors Tab Reduces the width of the sponsor tab to reduce the chance of one logo ending up on a new line by itself. --- pydis_site/static/css/home/index.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pydis_site/static/css/home/index.css b/pydis_site/static/css/home/index.css index 808c0517..6cfbf69f 100644 --- a/pydis_site/static/css/home/index.css +++ b/pydis_site/static/css/home/index.css @@ -212,6 +212,8 @@ h1 { #sponsors .columns { justify-content: center; + margin: auto; + max-width: 80%; } #sponsors img { -- cgit v1.2.3 From fc53a53db1952d785ef3ca0eca8ec54dc5971db7 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Tue, 23 Mar 2021 15:05:34 +0300 Subject: Removes Gunicorn Import In Debug mode Moves the gunicorn import below the start server command for debug mode to ensure it isn't imported if it isn't used. This solves issues with manage.py being unable to start on windows. --- manage.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/manage.py b/manage.py index fb5ee40c..71af23c4 100755 --- a/manage.py +++ b/manage.py @@ -7,7 +7,6 @@ import time from typing import List import django -import gunicorn.app.wsgiapp from django.contrib.auth import get_user_model from django.core.management import call_command, execute_from_command_line @@ -156,6 +155,9 @@ class SiteManager: call_command("runserver", "0.0.0.0:8000") return + # Import gunicorn only if we aren't in debug mode. + import gunicorn.app.wsgiapp + # Patch the arguments for gunicorn sys.argv = [ "gunicorn", -- cgit v1.2.3 From c244fea3bc7c9560b3ff5cd4bd40f9e19cf62ae1 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Wed, 24 Mar 2021 22:46:05 +0100 Subject: Remove the 100K user banner. --- pydis_site/static/css/home/index.css | 4 ++++ pydis_site/templates/home/index.html | 8 +------- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/pydis_site/static/css/home/index.css b/pydis_site/static/css/home/index.css index 6cfbf69f..ee6f6e4c 100644 --- a/pydis_site/static/css/home/index.css +++ b/pydis_site/static/css/home/index.css @@ -45,6 +45,10 @@ h1 { box-shadow: 0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22); } +#wave-hero-centered { + margin: auto auto; +} + #wave-hero-right img{ border-radius: 10px; box-shadow: 0 1px 6px rgba(0,0,0,0.16), 0 1px 6px rgba(0,0,0,0.23); diff --git a/pydis_site/templates/home/index.html b/pydis_site/templates/home/index.html index c35af2aa..f3470a83 100644 --- a/pydis_site/templates/home/index.html +++ b/pydis_site/templates/home/index.html @@ -29,7 +29,7 @@
{# Embedded Welcome video #} -
+
- - {# Right side content #} -
- 100K members! -
-
-- cgit v1.2.3 From 80e956971fac63d5198e366ed095d3a699ded7cb Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Wed, 24 Mar 2021 22:59:06 +0100 Subject: Update the timeline. Adds recent events, and also cleans up the HTML a bit. --- pydis_site/templates/home/index.html | 4 +- pydis_site/templates/home/timeline.html | 950 +++++++++++++++++++------------- 2 files changed, 564 insertions(+), 390 deletions(-) diff --git a/pydis_site/templates/home/index.html b/pydis_site/templates/home/index.html index f3470a83..67f29e41 100644 --- a/pydis_site/templates/home/index.html +++ b/pydis_site/templates/home/index.html @@ -92,7 +92,7 @@
-
New Timeline!
+
Interactive timeline
@@ -104,7 +104,7 @@

- Start from our humble beginnings to discover the events that made our community what it is today. + Discover the history of our community, and learn about the events that made our community what it is today.

diff --git a/pydis_site/templates/home/timeline.html b/pydis_site/templates/home/timeline.html index ece2e4e5..9f4175b2 100644 --- a/pydis_site/templates/home/timeline.html +++ b/pydis_site/templates/home/timeline.html @@ -3,53 +3,53 @@ {% block title %}Timeline{% endblock %} {% block head %} - - + + {% endblock %} {% block content %} -{% include "base/navbar.html" %} + {% include "base/navbar.html" %} -
+
-
-
- Picture -
+
+
+ Picture +
-
-

Python Discord is created

-

joe becomes one of the owners around 3 days after it - is created, and lemon joins the owner team later in the year, when the community - has around 300 members.

+
+

Python Discord is created

+

joe becomes one of the owners around 3 days after it + is created, and lemon joins the owner team later in the year, when the community + has around 300 members.

-
- Jan 8th, 2017 -
-
+
+ Jan 8th, 2017 +
+
-
-
- -
+
+
+ +
-
-

Python Discord hits 1,000 members

-

Our main source of new users at this point is a post on Reddit that - happens to get very good SEO. We are one of the top 10 search engine hits for the search term - "python discord".

+
+

Python Discord hits 1,000 members

+

Our main source of new users at this point is a post on Reddit that + happens to get very good SEO. We are one of the top 10 search engine hits for the search term + "python discord".

-
- Nov 10th, 2017 -
-
+
+ Nov 10th, 2017 +
+
-
-
- Picture -
+
+
+ Picture +

Our logo is born. Thanks @Aperture!

@@ -57,464 +57,638 @@ src="https://raw.githubusercontent.com/python-discord/branding/main/logos/logo_banner/logo_site_banner.svg">

-
- Feb 3rd, 2018 -
-
+
+ Feb 3rd, 2018 +
+
-
-
- -
+
+
+ +
-
-

PyDis hits 2,000 members; pythondiscord.com and @Python are live

-

The public moderation bot we're using at the time, Rowboat, announces - it will be shutting down. We decide that we'll write our own bot to handle moderation, so that we - can have more control over its features. We also buy a domain and start making a website in Flask. -

+
+

PyDis hits 2,000 members; pythondiscord.com and @Python are live

+

The public moderation bot we're using at the time, Rowboat, announces + it will be shutting down. We decide that we'll write our own bot to handle moderation, so that we + can have more control over its features. We also buy a domain and start making a website in Flask. +

-
- Mar 4th, 2018 -
-
+
+ Mar 4th, 2018 +
+
-
-
- -
+
+
+ +
-
-

First code jam with the theme “snakes”

-

Our very first Code Jam attracts a handful of users who work in random - teams of 2. We ask our participants to write a snake-themed Discord bot. Most of the code written - for this jam still lives on in SeasonalBot, and you can play with it by using the - .snakes command. For more information on this event, see the event page

+
+

First code jam with the theme “snakes”

+

Our very first Code Jam attracts a handful of users who work in random + teams of 2. We ask our participants to write a snake-themed Discord bot. Most of the code written + for this jam still lives on in SeasonalBot, and you can play with it by using the + .snakes command. For more information on this event, see the event page

+ +
+ Mar 23rd, 2018 +
+
+
-
- Mar 23rd, 2018 -
-
+
+
+
-
-
- -
+
+

The privacy policy is created

+

Since data privacy is quite important to us, we create a privacy page + pretty much as soon as our new bot and site starts collecting some data. To this day, we keep our privacy policy up to date with all + changes, and since April 2020 we've started doing monthly data reviews.

+ +
+ May 21st, 2018 +
+
+
-
-

The privacy policy is created

-

Since data privacy is quite important to us, we create a privacy page - pretty much as soon as our new bot and site starts collecting some data. To this day, we keep our privacy policy up to date with all - changes, and since April 2020 we've started doing monthly data reviews.

+
+
+ +
-
- May 21st, 2018 -
-
+
+

Do You Even Python and PyDis merger

+

At this point in time, there are only two serious Python communities on + Discord - Ours, and one called Do You Even Python. We approach the owners of DYEP with a bold + proposal - let's shut down their community, replace it with links to ours, and in return we will let + their staff join our staff. This gives us a big boost in members, and eventually leads to @eivl and + @Mr. Hemlock joining our Admin team

+ +
+ Jun 9th, 2018 +
+
-
-
- -
+
+
+ +
-
-

Do You Even Python and PyDis merger

-

At this point in time, there are only two serious Python communities on - Discord - Ours, and one called Do You Even Python. We approach the owners of DYEP with a bold - proposal - let's shut down their community, replace it with links to ours, and in return we will let - their staff join our staff. This gives us a big boost in members, and eventually leads to @eivl and - @Mr. Hemlock joining our Admin team

+
+

PyDis hits 5,000 members and partners with r/Python

+

As we continue to grow, we approach the r/Python subreddit and ask to + become their official Discord community. They agree, and we become listed in their sidebar, giving + us yet another source of new members.

-
- Jun 9th, 2018 -
-
+
+ Jun 20th, 2018 +
+
-
-
- -
+
+
+ +
-
-

PyDis hits 5,000 members and partners with r/Python

-

As we continue to grow, we approach the r/Python subreddit and ask to - become their official Discord community. They agree, and we become listed in their sidebar, giving - us yet another source of new members.

+
+

PyDis is now partnered with Discord; the vanity URL discord.gg/python is created

+

After being rejected for their Partner program several times, we + finally get approved. The recent partnership with the r/Python subreddit plays a significant role in + qualifying us for this partnership.

-
- Jun 20th, 2018 -
-
+
+ Jul 10th, 2018 +
+
-
-
- -
+
+
+ +
-
-

PyDis is now partnered with Discord; the vanity URL discord.gg/python is created

-

After being rejected for their Partner program several times, we - finally get approved. The recent partnership with the r/Python subreddit plays a significant role in - qualifying us for this partnership.

+
+

First Hacktoberfest PyDis event; @SeasonalBot is created

+

We create a second bot for our community and fill it up with simple, + fun and relatively easy issues. The idea is to create an approachable arena for our members to cut + their open-source teeth on, and to provide lots of help and hand-holding for those who get stuck. + We're training our members to be productive contributors in the open-source ecosystem.

-
- Jul 10th, 2018 -
-
+
+ Oct 1st, 2018 +
+
-
-
- -
+
+
+ +
-
-

First Hacktoberfest PyDis event; @SeasonalBot is created

-

We create a second bot for our community and fill it up with simple, - fun and relatively easy issues. The idea is to create an approachable arena for our members to cut - their open-source teeth on, and to provide lots of help and hand-holding for those who get stuck. - We're training our members to be productive contributors in the open-source ecosystem.

+
+

PyDis hits 10,000 members

+

We partner with RLBot, move from GitLab to GitHub, and start putting + together the first Advent of Code event.

-
- Oct 1st, 2018 -
-
+
+ Nov 24th, 2018 +
+
-
-
- -
+
+
+ +
-
-

PyDis hits 10,000 members

-

We partner with RLBot, move from GitLab to GitHub, and start putting - together the first Advent of Code event.

+
+

django-simple-bulma is released on PyPi

+

Our very first package on PyPI, django-simple-bulma is a package that + sets up the Bulma CSS framework for your Django application and lets you configure everything in + settings.py.

-
- Nov 24th, 2018 -
-
+
+ Dec 19th, 2018 +
+
-
-
- -
+
+
+ +
-
-

django-simple-bulma is released on PyPi

-

Our very first package on PyPI, django-simple-bulma is a package that - sets up the Bulma CSS framework for your Django application and lets you configure everything in - settings.py.

+
+

PyDis hits 15,000 members; the “hot ones special” video is released

+
+ +
+ +
+ Apr 8th, 2019 +
+
+
-
- Dec 19th, 2018 -
-
+
+
+
-
-
- -
+
+

The Django rewrite of pythondiscord.com is now live!

+

The site is getting more and more complex, and it's time for a rewrite. + We decide to go for a different stack, and build a website based on Django, DRF, Bulma and + PostgreSQL.

-
-

PyDis hits 15,000 members; the “hot ones special” video is released

-
- -
+
+ Sep 15, 2019 +
+
+
-
- Apr 8th, 2019 -
-
+
+
+
-
-
- -
+
+

The code of conduct is created

+

Inspired by the Adafruit, Rust and Django communities, an essential + community pillar is created; Our Code of + Conduct.

-
-

The Django rewrite of pythondiscord.com is now live!

-

The site is getting more and more complex, and it's time for a rewrite. - We decide to go for a different stack, and build a website based on Django, DRF, Bulma and - PostgreSQL.

+
+ Oct 26th, 2019 +
+
+
-
- Sep 15, 2019 -
-
+
+
+ Picture
-
-
- -
+
+

Ves Zappa becomes an owner

+

After being a long time active contributor to our projects and the driving + force behind our events, Ves Zappa joined the Owners team alongside joe & lemon.

-
-

The code of conduct is created

-

Inspired by the Adafruit, Rust and Django communities, an essential - community pillar is created; Our Code of - Conduct.

+
+ Sept 22nd, 2019 +
+
+
-
- Oct 26th, 2019 -
-
+
+
+
-
-
- Picture -
+
+

PyDis hits 30,000 members

+

More than tripling in size since the year before, the community hits + 30000 users. At this point, we're probably the largest Python chat community on the planet.

-
-

Ves Zappa becomes an owner

-

After being a long time active contributor to our projects and the driving force behind our events, Ves Zappa joined the Owners team alongside joe & lemon.

+
+ Dec 22nd, 2019 +
+
+
+ +
+
+ +
-
- Sept 22nd, 2019 -
-
+
+

PyDis sixth code jam with the theme “Ancient technology” and the technology Kivy

+

Our Code Jams are becoming an increasingly big deal, and the Kivy core + developers join us to judge the event and help out our members during the event. One of them, + @tshirtman, even joins our staff!

+ +
+ +
+ +
+ Jan 17, 2020 +
+
-
-
- -
+
+
+ +
-
-

PyDis hits 30,000 members

-

More than tripling in size since the year before, the community hits - 30000 users. At this point, we're probably the largest Python chat community on the planet.

+
+

The new help channel system is live

+

We release our dynamic help-channel system, which allows you to claim + your very own help channel instead of fighting over the static help channels. We release a Help Channel Guide to + help our members fully understand how the system works.

-
- Dec 22nd, 2019 -
-
+
+ Apr 5th, 2020 +
+
-
-
- -
+
+
+ +
-
-

PyDis sixth code jam with the theme “Ancient technology” and the technology Kivy

-

Our Code Jams are becoming an increasingly big deal, and the Kivy core - developers join us to judge the event and help out our members during the event. One of them, - @tshirtman, even joins our staff!

+
+

Python Discord hits 40,000 members, and is now bigger than Liechtenstein.

+

+

-
- -
+
+ Apr 14, 2020 +
+
+
-
- Jan 17, 2020 -
-
+
+
+
-
-
- -
+
+

PyDis Game Jam 2020 with the “Three of a Kind” theme and Arcade as the technology

+

The creator of Arcade, Paul Vincent Craven, joins us as a judge. + Several of the Code Jam participants also end up getting involved contributing to the Arcade + repository.

+ +
+ +
+ +
+ Apr 17th, 2020 +
+
+
-
-

The new help channel system is live

-

We release our dynamic help-channel system, which allows you to claim - your very own help channel instead of fighting over the static help channels. We release a Help Channel Guide to - help our members fully understand how the system works.

+
+
+ +
+ +
+

ModMail is now live

+

Having originally planned to write our own ModMail bot from scratch, we + come across an exceptionally good ModMail bot by + kyb3r and decide to just self-host that one instead.

-
- Apr 5th, 2020 -
-
+
+ May 25th, 2020 +
+
-
-
- -
+
+
+ +
-
-

Python Discord hits 40,000 members, and is now bigger than Liechtenstein.

-

-

+
+

Python Discord is now listed on python.org/community

+

After working towards this goal for months, we finally work out an + arrangement with the PSF that allows us to be listed on that most holiest of websites: + https://python.org/. There was much rejoicing.

-
- Apr 14, 2020 -
-
+
+ May 28th, 2020 +
+
-
-
- -
+
+
+ +
-
-

PyDis Game Jam 2020 with the “Three of a Kind” theme and Arcade as the technology

-

The creator of Arcade, Paul Vincent Craven, joins us as a judge. - Several of the Code Jam participants also end up getting involved contributing to the Arcade - repository.

+
+

Python Discord Public Statistics are now live

+

After getting numerous requests to publish beautiful data on member + count and channel use, we create stats.pythondiscord.com for + all to enjoy.

-
- -
+
+ Jun 4th, 2020 +
+
+
-
- Apr 17th, 2020 -
-
+
+
+
-
-
- -
+
+

PyDis summer code jam 2020 with the theme “Early Internet” and Django as the technology

+

Sponsored by the Django Software Foundation and JetBrains, the Summer + Code Jam for 2020 attracts hundreds of participants, and sees the creation of some fantastic + projects. Check them out in our judge stream below:

+ +
+ +
+ +
+ Jul 31st, 2020 +
+
+
-
-

ModMail is now live

-

Having originally planned to write our own ModMail bot from scratch, we - come across an exceptionally good ModMail bot by - kyb3r and decide to just self-host that one instead.

+
+
+ +
-
- May 25th, 2020 -
-
+
+

Python Discord is now the new home of the PyWeek event!

+

PyWeek, a game jam that has been running since 2005, joins Python + Discord as one of our official events. Find more information about PyWeek on their official website.

+ +
+ Aug 16th, 2020 +
+
-
-
- -
+
+
+ Picture +
-
-

Python Discord is now listed on python.org/community

-

After working towards this goal for months, we finally work out an - arrangement with the PSF that allows us to be listed on that most holiest of websites: - https://python.org/. There was much rejoicing.

+
+

Python Discord hosts the 2020 CPython Core Developer Q&A

+
+ +
+ +
+ Oct 21st, 2020 +
+
+
-
- May 28th, 2020 -
-
+
+
+
-
-
- -
+
+

Python Discord hits 100,000 members!

+

Only six months after hitting 40,000 users, we hit 100,000 users. A + monumental milestone, + and one we're very proud of. To commemorate it, we create this timeline.

-
-

Python Discord Public Statistics are now live

-

After getting numerous requests to publish beautiful data on member - count and channel use, we create stats.pythondiscord.com for all to enjoy.

+
+ Oct 22nd, 2020 +
+
+
-
- Jun 4th, 2020 -
-
+
+
+
-
-
- -
+
+

We migrate all our infrastructure to Kubernetes

+

As our tech stack grows, we decide to migrate all our services over to a + container orchestration paradigm via Kubernetes. This gives us better control and scalability. + Joe Banks takes on the role as DevOps Lead. +

-
-

PyDis summer code jam 2020 with the theme “Early Internet” and Django as the technology

-

Sponsored by the Django Software Foundation and JetBrains, the Summer - Code Jam for 2020 attracts hundreds of participants, and sees the creation of some fantastic - projects. Check them out in our judge stream below:

+
+ Nov 29th, 2020 +
+
+
+ +
+
+ +
+ +
+

Advent of Code attracts hundreds of participants

+

+ A total of 443 Python Discord members sign up to be part of + Eric Wastl's excellent Advent of Code event. + As always, we provide dedicated announcements, scoreboards, bot commands and channels for our members + to enjoy the event in. -

- -
+

-
- Jul 31st, 2020 -
-
+
+ December 1st - 25th, 2020 +
+
-
-
- -
-
-

Python Discord is now the new home of the PyWeek event!

-

PyWeek, a game jam that has been running since 2005, joins Python - Discord as one of our official events. Find more information about PyWeek on their official website.

+
+
+ +
+ +
+

We release The PEP 8 song

+

We release the PEP 8 song on our YouTube channel, which finds tens of + thousands of listeners!

-
- Aug 16th, 2020 -
-
+
+ +
+ +
+ February 8th, 2021 +
+
-
-
- Picture -
+
+
+ +
-
-

Python Discord hosts the 2020 CPython Core Developer Q&A

-
- -
+
+

We now have 150,000 members!

+

Our growth continues to accelerate.

-
- Oct 21st, 2020 -
-
+
+ Feb 18th, 2021 +
+
-
-
- -
+
+
+ +
-
-

Python Discord hits 100,000 members.

-

After years of hard work, we hit 100,000 users. A monumental milestone, - and one we're very proud of. To commemorate it, we create this timeline.

+
+

Lemon on Talk Python To Me

+

Lemon goes on the Talk Python to Me podcast to discuss the history of Python + Discord, + the critical importance of culture, and how to run a massive community. You can find the episode + at talkpython.fm. +

+ + + +
+ Mar 1st, 2021 +
+
+
+ +
+
+ +
-
- Oct 22nd, 2020 -
-
+
+

Lemon on Teaching Python

+

Lemon goes on the Teaching Python podcast to discuss how the pandemic has + changed the way we learn, and what role communities like Python Discord can play in this new world. + You can find the episode at teachingpython.fm. +

+ + + +
+ Mar 13th, 2021 +
+
+
+ +
+
+ +
+ +
+

New feature: Weekly discussion channel

+

Every week (or two weeks), we'll be posting a new topic to discuss in a + channel called #weekly-topic-discussion. Our inaugural topic is a PyCon talk by Anthony Shaw called + Wily Python: Writing simpler and more maintainable Python.. +

+ +
+ +
+ +
+ Mar 13th, 2021 +
+
+ +
+
+ +
+ +
+

Summer Code Jam 2020 Highlights

+

+ We release a new video to our YouTube showing the best projects from the Summer Code Jam 2020. + Better late than never! +

+ +
+ +
+ +
+ Mar 21st, 2021 +
+
+
+
-
+
- + {% endblock %} -- cgit v1.2.3 From d9a55b3d09d5c19cbb9b589f5a189f9f4fb38498 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Thu, 25 Mar 2021 10:34:02 +0100 Subject: Use real names in the timeline Using nicknames like Ves Zappa and Lemon doesn't always feel appropriate, so I've replaced some of those references with real names. --- pydis_site/templates/home/timeline.html | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/pydis_site/templates/home/timeline.html b/pydis_site/templates/home/timeline.html index 9f4175b2..ea354a57 100644 --- a/pydis_site/templates/home/timeline.html +++ b/pydis_site/templates/home/timeline.html @@ -19,8 +19,8 @@

Python Discord is created

-

joe becomes one of the owners around 3 days after it - is created, and lemon joins the owner team later in the year, when the community +

Joe Banks becomes one of the owners around 3 days after it + is created, and Leon Sandøy joins the owner team later in the year, when the community has around 300 members.

@@ -283,9 +283,9 @@
-

Ves Zappa becomes an owner

+

Sebastiaan Zeef becomes an owner

After being a long time active contributor to our projects and the driving - force behind our events, Ves Zappa joined the Owners team alongside joe & lemon.

+ force behind many of our events, Sebastiaan Zeef joins the Owners Team alongside Joe & Leon.

Sept 22nd, 2019 @@ -600,11 +600,10 @@
-

Lemon on Talk Python To Me

-

Lemon goes on the Talk Python to Me podcast to discuss the history of Python - Discord, - the critical importance of culture, and how to run a massive community. You can find the episode - at talkpython.fm. +

Leon Sandøy appears on Talk Python To Me

+

Leon goes on the Talk Python to Me podcast with Michael Kennedy + to discuss the history of Python Discord, the critical importance of culture, and how to run a massive + community. You can find the episode at talkpython.fm.