diff options
Diffstat (limited to 'pydis_site')
-rw-r--r-- | pydis_site/apps/api/admin.py | 55 | ||||
-rw-r--r-- | pydis_site/apps/api/migrations/0044_migrate_nominations_from_infraction_to_nomination_model.py | 64 | ||||
-rw-r--r-- | pydis_site/apps/api/migrations/0045_add_plural_name_for_log_entry.py | 17 | ||||
-rw-r--r-- | pydis_site/apps/api/models/bot/nomination.py | 5 | ||||
-rw-r--r-- | pydis_site/apps/api/models/log_entry.py | 5 | ||||
-rw-r--r-- | pydis_site/apps/api/tests/test_models.py | 39 | ||||
-rw-r--r-- | pydis_site/static/css/home/index.css | 19 | ||||
-rw-r--r-- | pydis_site/templates/home/index.html | 49 |
8 files changed, 203 insertions, 50 deletions
diff --git a/pydis_site/apps/api/admin.py b/pydis_site/apps/api/admin.py index c3784317..059f52eb 100644 --- a/pydis_site/apps/api/admin.py +++ b/pydis_site/apps/api/admin.py @@ -1,18 +1,63 @@ +from typing import Optional + from django.contrib import admin +from django.http import HttpRequest from .models import ( - BotSetting, DeletedMessage, - DocumentationLink, Infraction, - MessageDeletionContext, Nomination, - OffTopicChannelName, Role, - Tag, User + BotSetting, + DeletedMessage, + DocumentationLink, + Infraction, + LogEntry, + MessageDeletionContext, + Nomination, + OffTopicChannelName, + Role, + Tag, + User ) +class LogEntryAdmin(admin.ModelAdmin): + """Allows viewing logs in the Django Admin without allowing edits.""" + + actions = None + list_display = ('timestamp', 'application', 'level', 'message') + fieldsets = ( + ('Overview', {'fields': ('timestamp', 'application', 'logger_name')}), + ('Metadata', {'fields': ('level', 'module', 'line')}), + ('Contents', {'fields': ('message',)}) + ) + list_filter = ('application', 'level', 'timestamp') + search_fields = ('message',) + readonly_fields = ( + 'application', + 'logger_name', + 'timestamp', + 'level', + 'module', + 'line', + 'message' + ) + + def has_add_permission(self, request: HttpRequest) -> bool: + """Deny manual LogEntry creation.""" + return False + + def has_delete_permission( + self, + request: HttpRequest, + obj: Optional[LogEntry] = None + ) -> bool: + """Deny LogEntry deletion.""" + return False + + admin.site.register(BotSetting) admin.site.register(DeletedMessage) admin.site.register(DocumentationLink) admin.site.register(Infraction) +admin.site.register(LogEntry, LogEntryAdmin) admin.site.register(MessageDeletionContext) admin.site.register(Nomination) admin.site.register(OffTopicChannelName) diff --git a/pydis_site/apps/api/migrations/0044_migrate_nominations_from_infraction_to_nomination_model.py b/pydis_site/apps/api/migrations/0044_migrate_nominations_from_infraction_to_nomination_model.py new file mode 100644 index 00000000..a56450c0 --- /dev/null +++ b/pydis_site/apps/api/migrations/0044_migrate_nominations_from_infraction_to_nomination_model.py @@ -0,0 +1,64 @@ +# Generated by Django 2.2.5 on 2019-09-30 12:15 +import logging + +from django.db import migrations +from django.db.models import Q + +log = logging.getLogger('nomination_migration') + + +def migrate_nominations_to_new_model(apps, schema_editor): + """ + Migrations nominations from the infraction model to the nomination model. + + This migration works by replaying the nomination history in chronological order, adding and + ending nominations as we've recorded them. + """ + Infraction = apps.get_model('api', 'Infraction') + Nomination = apps.get_model('api', 'Nomination') + + all_nominations = ( + Q(reason__startswith="Helper nomination:") | Q(reason__startswith="Unwatched (talent-pool):") + ) + + for infraction in Infraction.objects.filter(all_nominations).order_by('inserted_at'): + if infraction.reason.startswith("Helper nomination:"): + if Nomination.objects.filter(user=infraction.user, active=True).exists(): + log.error( + f"User `{infraction.user.id}` already has an active nomination, aborting." + ) + continue + nomination = Nomination( + user=infraction.user, + inserted_at=infraction.inserted_at, + reason=infraction.reason[19:], # Strip "Helper nomination: " prefix + actor=infraction.actor, + active=True, + ) + nomination.save() + infraction.delete() + elif infraction.reason.startswith("Unwatched (talent-pool):"): + if not Nomination.objects.filter(user=infraction.user, active=True).exists(): + log.error( + f"User `{infraction.user.id}` has no active nomination, can't end it!" + ) + continue + nomination = Nomination.objects.get(user=infraction.user, active=True) + nomination.end_reason = infraction.reason[25:] # Strip "Unwatched (talent-pool):" + nomination.ended_at = infraction.inserted_at + nomination.active = False + nomination.save() + infraction.delete() + else: + log.error(f"I don't understand this infraction: {infraction}") + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0043_infraction_hidden_warnings_to_notes'), + ] + + operations = [ + migrations.RunPython(migrate_nominations_to_new_model), + ] diff --git a/pydis_site/apps/api/migrations/0045_add_plural_name_for_log_entry.py b/pydis_site/apps/api/migrations/0045_add_plural_name_for_log_entry.py new file mode 100644 index 00000000..6b9933d8 --- /dev/null +++ b/pydis_site/apps/api/migrations/0045_add_plural_name_for_log_entry.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.3 on 2019-10-11 17:48 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0044_migrate_nominations_from_infraction_to_nomination_model'), + ] + + operations = [ + migrations.AlterModelOptions( + name='logentry', + options={'verbose_name_plural': 'Log entries'}, + ), + ] diff --git a/pydis_site/apps/api/models/bot/nomination.py b/pydis_site/apps/api/models/bot/nomination.py index 8a8f4d36..cd9951aa 100644 --- a/pydis_site/apps/api/models/bot/nomination.py +++ b/pydis_site/apps/api/models/bot/nomination.py @@ -39,3 +39,8 @@ class Nomination(ModelReprMixin, models.Model): help_text="When the nomination was ended.", null=True ) + + def __str__(self): + """Representation that makes the target and state of the nomination immediately evident.""" + status = "active" if self.active else "ended" + return f"Nomination of {self.user} ({status})" diff --git a/pydis_site/apps/api/models/log_entry.py b/pydis_site/apps/api/models/log_entry.py index acd7953a..488af48e 100644 --- a/pydis_site/apps/api/models/log_entry.py +++ b/pydis_site/apps/api/models/log_entry.py @@ -48,3 +48,8 @@ class LogEntry(ModelReprMixin, models.Model): message = models.TextField( help_text="The textual content of the log line." ) + + class Meta: + """Customizes the default generated plural name to valid English.""" + + verbose_name_plural = 'Log entries' diff --git a/pydis_site/apps/api/tests/test_models.py b/pydis_site/apps/api/tests/test_models.py index 2120b056..bce76942 100644 --- a/pydis_site/apps/api/tests/test_models.py +++ b/pydis_site/apps/api/tests/test_models.py @@ -1,13 +1,21 @@ -from datetime import datetime as dt, timezone +from datetime import datetime as dt from django.test import SimpleTestCase +from django.utils import timezone from ..models import ( - DeletedMessage, DocumentationLink, - Infraction, Message, - MessageDeletionContext, ModelReprMixin, - OffTopicChannelName, Reminder, - Role, Tag, User + DeletedMessage, + DocumentationLink, + Infraction, + Message, + MessageDeletionContext, + ModelReprMixin, + Nomination, + OffTopicChannelName, + Reminder, + Role, + Tag, + User ) @@ -27,6 +35,19 @@ class ReprMixinTests(SimpleTestCase): class StringDunderMethodTests(SimpleTestCase): def setUp(self): + self.nomination = Nomination( + id=123, + actor=User( + id=9876, name='Mr. Hemlock', + discriminator=6666, avatar_hash=None + ), + user=User( + id=9876, name="Hemlock's Cat", + discriminator=7777, avatar_hash=None + ), + reason="He purrrrs like the best!", + ) + self.objects = ( DeletedMessage( id=45, @@ -102,3 +123,9 @@ class StringDunderMethodTests(SimpleTestCase): def test_returns_string(self): for instance in self.objects: self.assertIsInstance(str(instance), str) + + def test_nomination_str_representation(self): + self.assertEqual( + "Nomination of Hemlock's Cat#7777 (active)", + str(self.nomination) + ) diff --git a/pydis_site/static/css/home/index.css b/pydis_site/static/css/home/index.css index a90a60d7..4c36031b 100644 --- a/pydis_site/static/css/home/index.css +++ b/pydis_site/static/css/home/index.css @@ -71,15 +71,6 @@ span.repo-language-dot.javascript { } @media screen and (min-width: 1088px) { - .column.is-half, .column.is-half-tablet { - flex: none; - width: 50%; - } - - .columns:not(.is-desktop) { - display: flex; - } - .video-container iframe { height: calc(42vw * 0.5625); max-height: 371px; @@ -88,18 +79,10 @@ span.repo-language-dot.javascript { } @media screen and (max-width: 1087px) { - .column.is-half, .column.is-half-tablet { - flex: none; - width: 100%; - } - - .columns:not(.is-desktop) { - display: block; - } - .video-container iframe { height: calc(92vw * 0.5625); max-height: none; max-width: none; } } + diff --git a/pydis_site/templates/home/index.html b/pydis_site/templates/home/index.html index 205e92ff..0fa2f67c 100644 --- a/pydis_site/templates/home/index.html +++ b/pydis_site/templates/home/index.html @@ -15,32 +15,30 @@ <div class="container is-spaced"> <h1 class="is-size-1">Who are we?</h1> <br> - <div class="columns"> - <div class="column is-half"> + <div class="columns is-desktop"> + <div class="column is-half-desktop"> <p> We're a large community focused around the Python programming language. - We believe anyone can learn programming, and are very dedicated to helping - novice developers take their first steps into the world of code. We also + We believe anyone can learn to code, and are very dedicated to helping + novice developers take their first steps into the world of programming. We also attract a lot of expert developers who are seeking friendships, collaborators, - or looking to hone their craft by teaching and getting involved in the community. - - <br/><br/> - - We organise regular community events like code jams, open source hackathons, - seasonal events and community challenges. Through our sponsorships and with - help from donations, we are even able to award prizes to the winners of our events. - - <br/><br/> - + and who wish to hone their craft by teaching and getting involved in the community. + </p> + <p> + We organise regular community events such as code jams, open-source hackathons, + seasonal events, and community challenges. Through our sponsorships and donations, + many of our events even have prizes to win! + </p> + <p> You can find help with most Python-related problems in one of our help channels. - Our staff of nearly 50 dedicated expert Helpers are available around the clock + Our staff of over 50 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. </p> </div> {# Intro video #} - <div class="column is-half video-container"> + <div class="column is-half-desktop video-container"> <iframe src="https://www.youtube.com/embed/DIBXg8Qh7bA" frameborder="0" allow="accelerometer; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> @@ -50,11 +48,11 @@ {# Projects #} <h1 class="is-size-1">Projects</h1> <br> - <div class="columns is-multiline"> + <div class="columns is-multiline is-tablet"> {# Display projects from HomeView.repos #} {% for repo in repo_data %} - <div class="column is-one-third"> + <div class="column is-one-third-desktop is-half-tablet"> <div class="card has-equal-height github-card"> <div class="card-content"> <div class="repo-headline"> @@ -84,11 +82,20 @@ <h1 class="title is-6 has-text-grey"> Sponsors </h1> - <a href="https://linode.com"><img src="{% static "images/sponsors/linode.png" %}" alt="Linode"/></a> - <a href="https://jetbrains.com"><img src="{% static "images/sponsors/jetbrains.png" %}" alt="JetBrains"/></a> - <a href="https://adafruit.com"><img src="{% static "images/sponsors/adafruit.png" %}" alt="Adafruit"/></a> + <div class="columns is-mobile is-multiline"> + <a href="https://linode.com" class="column is-narrow"> + <img src="{% static "images/sponsors/linode.png" %}" alt="Linode"/> + </a> + <a href="https://jetbrains.com" class="column is-narrow"> + <img src="{% static "images/sponsors/jetbrains.png" %}" alt="JetBrains"/> + </a> + <a href="https://adafruit.com" class="column is-narrow"> + <img src="{% static "images/sponsors/adafruit.png" %}" alt="Adafruit"/> + </a> + </div> </div> </div> </section> {% endblock %} + |