Skip to content

Commit

Permalink
Merge branch 'master' into mbaruh/channel-activity
Browse files Browse the repository at this point in the history
  • Loading branch information
mbaruh committed Mar 11, 2021
2 parents fdc1be6 + e0fc61a commit 89bf60e
Show file tree
Hide file tree
Showing 9 changed files with 435 additions and 76 deletions.
80 changes: 67 additions & 13 deletions pydis_site/apps/api/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
Role,
User
)
from .models.bot.nomination import NominationEntry

admin.site.site_header = "Python Discord | Administration"
admin.site.site_title = "Python Discord"
Expand Down Expand Up @@ -218,15 +219,18 @@ 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)

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())
nomination_ids = NominationEntry.objects.filter(
actor__id=self.value()
).values_list("nomination_id").distinct()
return queryset.filter(id__in=nomination_ids)


@admin.register(Nomination)
Expand All @@ -236,9 +240,6 @@ class NominationAdmin(admin.ModelAdmin):
search_fields = (
"user__name",
"user__id",
"actor__name",
"actor__id",
"reason",
"end_reason"
)

Expand All @@ -247,34 +248,87 @@ 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:
"""Prevent adding from django admin."""
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."""
Expand Down
75 changes: 75 additions & 0 deletions pydis_site/apps/api/migrations/0068_split_nomination_tables.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# 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()


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 = [
('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.', 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.',
on_delete=django.db.models.deletion.CASCADE, related_name='nomination_set',
to='api.User')),
('nomination', models.ForeignKey(help_text='The nomination this entry belongs to.',
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(
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 a review was made.'),
),
]
1 change: 1 addition & 0 deletions pydis_site/apps/api/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
Message,
MessageDeletionContext,
Nomination,
NominationEntry,
OffensiveMessage,
OffTopicChannelName,
Reminder,
Expand Down
2 changes: 1 addition & 1 deletion pydis_site/apps/api/models/bot/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
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
Expand Down
52 changes: 40 additions & 12 deletions pydis_site/apps/api/models/bot/nomination.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,12 @@


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,
Expand All @@ -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 a review was made."
)

def __str__(self):
"""Representation that makes the target and state of the nomination immediately evident."""
Expand All @@ -52,3 +45,38 @@ class Meta:
"""Set the ordering of nominations to most recent first."""

ordering = ("-inserted_at",)


class NominationEntry(ModelReprMixin, models.Model):
"""A nomination entry created by a single staff member."""

nomination = models.ForeignKey(
Nomination,
on_delete=models.CASCADE,
help_text="The nomination this entry belongs to.",
related_name="entries"
)
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.",
default="",
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"

# Set default ordering here to latest first
# so we don't need to define it everywhere
ordering = ("-inserted_at",)
25 changes: 23 additions & 2 deletions pydis_site/apps/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
Infraction,
MessageDeletionContext,
Nomination,
NominationEntry,
OffTopicChannelName,
OffensiveMessage,
Reminder,
Expand Down Expand Up @@ -338,16 +339,36 @@ def create(self, validated_data: dict) -> User:
raise ValidationError({"id": ["User with ID already present."]})


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
)

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."""

entries = NominationEntrySerializer(many=True, read_only=True)

class Meta:
"""Metadata defined for the Django REST Framework."""

model = Nomination
fields = (
'id', 'active', 'actor', 'reason', 'user',
'inserted_at', 'end_reason', 'ended_at')
'id', 'active', 'user', 'inserted_at', 'end_reason', 'ended_at', 'reviewed', 'entries'
)


class OffensiveMessageSerializer(ModelSerializer):
Expand Down
16 changes: 10 additions & 6 deletions pydis_site/apps/api/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
Message,
MessageDeletionContext,
Nomination,
NominationEntry,
OffTopicChannelName,
OffensiveMessage,
Reminder,
Expand Down Expand Up @@ -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 = (
Expand Down Expand Up @@ -135,6 +130,15 @@ def setUp(self):
),
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!",
)
)

Expand Down

0 comments on commit 89bf60e

Please sign in to comment.