diff options
| author | 2019-10-11 22:32:46 +0100 | |
|---|---|---|
| committer | 2019-10-11 22:32:46 +0100 | |
| commit | 689a5d9c41395a7da644b25c1e3eab95c2614604 (patch) | |
| tree | b535dc93dbf84e076f663a29cd9c44abdada61d4 /pydis_site/apps/api | |
| parent | Signals: Handle (and test) mapping updates/deletions (diff) | |
| parent | Merge pull request #281 from python-discord/simple-admin-log-entry-view (diff) | |
Merge branch 'master' into #201-django-allauth
Diffstat (limited to 'pydis_site/apps/api')
6 files changed, 174 insertions, 11 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) +        ) | 
