diff options
| -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 %} + | 
