Skip to content

Commit

Permalink
Merge branches 'master' and 'task/2479' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
tienne-B committed Aug 27, 2024
2 parents 3bef92d + 7b6ca2e commit 4e2a921
Show file tree
Hide file tree
Showing 23 changed files with 3,219 additions and 602 deletions.
548 changes: 468 additions & 80 deletions data/fixtures/after_round_1.json

Large diffs are not rendered by default.

1,198 changes: 1,010 additions & 188 deletions data/fixtures/after_round_4.json

Large diffs are not rendered by default.

1,198 changes: 1,010 additions & 188 deletions data/fixtures/before_oqf_ssf.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions tabbycat/actionlog/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ def serialize(self):
return {
'id': self.id,
'user': self.user.username if self.user else self.ip_address or _("anonymous"),
'agent': self.agent,
'type': self.get_type_display(),
# As the team names are passed in the content of the message for all users,
# must assume they don't have permission for real names
Expand Down
57 changes: 16 additions & 41 deletions tabbycat/adjfeedback/admin.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
from django import forms
from django.contrib import admin, messages
from django.contrib.contenttypes.admin import GenericTabularInline
from django.contrib.contenttypes.prefetch import GenericPrefetch
from django.db.models import Prefetch
from django.utils.translation import gettext, gettext_lazy as _, ngettext
from django.utils.translation import gettext_lazy as _, ngettext
from django_better_admin_arrayfield.admin.mixins import DynamicArrayMixin

from draw.models import DebateTeam
from utils.admin import custom_titled_filter, ModelAdmin

from .models import (AdjudicatorBaseScoreHistory, AdjudicatorFeedback, AdjudicatorFeedbackBooleanAnswer,
AdjudicatorFeedbackFloatAnswer, AdjudicatorFeedbackIntegerAnswer, AdjudicatorFeedbackManyAnswer,
AdjudicatorFeedbackQuestion, AdjudicatorFeedbackStringAnswer)
from .models import (AdjudicatorBaseScoreHistory, AdjudicatorFeedback, AdjudicatorFeedbackQuestion,
BooleanAnswer, FloatAnswer, IntegerAnswer, ManyAnswer, StringAnswer)


# ==============================================================================
Expand Down Expand Up @@ -57,46 +58,20 @@ class AdjudicatorFeedbackQuestionAdmin(DynamicArrayMixin, ModelAdmin):
# Adjudicator feedback answers
# ==============================================================================

@admin.register(AdjudicatorFeedbackBooleanAnswer)
@admin.register(AdjudicatorFeedbackFloatAnswer)
@admin.register(AdjudicatorFeedbackIntegerAnswer)
@admin.register(AdjudicatorFeedbackManyAnswer)
@admin.register(AdjudicatorFeedbackStringAnswer)
class AdjudicatorFeedbackAnswerAdmin(ModelAdmin):
list_display = ('question', 'get_target', 'get_source', 'answer', 'get_feedback_description')
list_select_related = ('question', 'feedback__adjudicator',
'feedback__source_adjudicator__adjudicator',
'feedback__source_team__team')
list_filter = (
'question', 'answer',
('feedback__adjudicator__name', custom_titled_filter(_('target'))),
('feedback__source_adjudicator__adjudicator__name', custom_titled_filter(_('source adjudicator'))),
('feedback__source_team__team__short_name', custom_titled_filter(_('source team'))),
)
raw_id_fields = ('feedback',)

@admin.display(description=_("Target"))
def get_target(self, obj):
return obj.feedback.adjudicator.name
@admin.register(BooleanAnswer)
@admin.register(FloatAnswer)
@admin.register(IntegerAnswer)
@admin.register(ManyAnswer)
@admin.register(StringAnswer)
class AnswerAdmin(ModelAdmin):
list_display = ('question', 'answer', 'content_object')

@admin.display(description=_("Source"))
def get_source(self, obj):
if obj.feedback.source_team and obj.feedback.source_adjudicator:
return "<ERROR: both source team and source adjudicator>"
elif obj.feedback.source_team:
return obj.feedback.source_team.team.short_name
elif obj.feedback.source_adjudicator:
return obj.feedback.source_adjudicator.adjudicator.name

@admin.display(description=_("Feedback timestamp and version"))
def get_feedback_description(self, obj):
return gettext("%(timestamp)s (version %(version)s)") % {
'timestamp': obj.feedback.timestamp.isoformat(),
'version': obj.feedback.version,
}
def get_queryset(self, request):
prefetch = GenericPrefetch("content_object", [AdjudicatorFeedback.objects.select_related('source_team__team', 'source_adjudicator__adjudicator', 'adjudicator').all()])
return super().get_queryset(request).prefetch_related(prefetch)


class BaseAdjudicatorFeedbackAnswerInline(admin.TabularInline):
class BaseAdjudicatorFeedbackAnswerInline(GenericTabularInline):
model = None # Must be set by subclasses
fields = ('question', 'answer')
extra = 1
Expand Down
2 changes: 1 addition & 1 deletion tabbycat/adjfeedback/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ def save_adjudicatorfeedback(self, **kwargs):
for question in self._tournament.adj_feedback_questions.filter(**self.question_filter):
response = self.cleaned_data[question.reference]
if response is not None and response != "":
question.answer_type_class(feedback=af, question=question, answer=response).save()
question.answer_type_class(content_object=af, question=question, answer=response).save()

return af

Expand Down
294 changes: 294 additions & 0 deletions tabbycat/adjfeedback/migrations/0016_question_and_more.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,294 @@
# Generated by Django 5.0.6 on 2024-07-21 12:42

from itertools import chain

import django.db.models.deletion
import django_better_admin_arrayfield.models.fields
import utils.models
from django.db import migrations, models


answer_migrations = [
[
migrations.AlterModelOptions(
name=table,
options={
"verbose_name": f"{name} answer",
"verbose_name_plural": f"{name} answers",
},
),
migrations.RemoveConstraint(
model_name=table,
name=f"adjfeed_{table}_question__feedback_uniq",
),
migrations.AddField(
model_name=table,
name="content_type",
field=models.ForeignKey(
null=True,
limit_choices_to=models.Q(
("app_label", "adjfeedback"), ("model", "adjudicatorfeedback")
),
on_delete=django.db.models.deletion.CASCADE,
to="contenttypes.contenttype",
verbose_name="content type",
),
),
migrations.RunSQL(f"UPDATE adjfeedback_{table} SET content_type_id=(SELECT id FROM django_content_type WHERE model='adjudicatorfeedback')"),
migrations.AlterField(
model_name=table,
name="content_type",
field=models.ForeignKey(
null=False,
limit_choices_to=models.Q(
("app_label", "adjfeedback"), ("model", "adjudicatorfeedback")
),
on_delete=django.db.models.deletion.CASCADE,
to="contenttypes.contenttype",
verbose_name="content type",
),
),
migrations.RenameField(
model_name=table,
old_name="feedback",
new_name="object",
),
migrations.AlterField(
model_name=table,
name="object_id",
field=models.PositiveIntegerField(verbose_name="object id"),
preserve_default=False,
),
migrations.AlterField(
model_name=table,
name="question",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="adjfeedback.question",
verbose_name="question",
),
),
migrations.AddConstraint(
model_name=table,
constraint=utils.models.UniqueConstraint(
fields=("question", "content_type", "object_id"),
name=f"adjfeed_{table}_question__content_type__object_id_uniq",
),
),
migrations.RenameModel(table, table[19:]),
]
for table, name in [
('adjudicatorfeedbackbooleananswer', 'boolean'),
('adjudicatorfeedbackfloatanswer', 'float'),
('adjudicatorfeedbackmanyanswer', 'multiple select'),
('adjudicatorfeedbackstringanswer', 'string'),
('adjudicatorfeedbackintegeranswer', 'integer'),
]
]


class Migration(migrations.Migration):

dependencies = [
("adjfeedback", "0015_alter_adjudicatorfeedback_unique_together_and_more"),
("contenttypes", "0002_remove_content_type_name"),
("tournaments", "0013_scheduleevent"),
]

operations = [
migrations.CreateModel(
name="Question",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"seq",
models.IntegerField(
help_text="The order in which questions are displayed",
verbose_name="sequence number",
),
),
(
"text",
models.CharField(
help_text='The question displayed to participants, e.g., "Did you agree with the decision?"',
max_length=255,
verbose_name="text",
),
),
(
"name",
models.CharField(
help_text='A short name for the question, e.g., "Agree with decision"',
max_length=30,
verbose_name="name",
),
),
(
"answer_type",
models.CharField(
choices=[
("bc", "checkbox"),
("bs", "yes/no (dropdown)"),
("i", "integer (textbox)"),
("is", "integer scale"),
("f", "float"),
("t", "text"),
("tl", "long text"),
("ss", "select one"),
("ms", "select multiple"),
],
max_length=2,
verbose_name="answer type",
),
),
(
"required",
models.BooleanField(
default=True,
help_text="Whether participants are required to fill out this field",
verbose_name="required",
),
),
(
"min_value",
models.FloatField(
blank=True,
help_text="Minimum allowed value for numeric fields (ignored for text or boolean fields)",
null=True,
verbose_name="minimum value",
),
),
(
"max_value",
models.FloatField(
blank=True,
help_text="Maximum allowed value for numeric fields (ignored for text or boolean fields)",
null=True,
verbose_name="maximum value",
),
),
(
"choices",
django_better_admin_arrayfield.models.fields.ArrayField(
base_field=models.TextField(),
blank=True,
default=list,
help_text="Permissible choices for select one/multiple fields (ignored for other fields)",
size=None,
verbose_name="choices",
),
),
(
"for_content_type",
models.ForeignKey(
limit_choices_to=models.Q(
("app_label", "adjfeedback"), ("model", "adjudicatorfeedback")
),
on_delete=django.db.models.deletion.CASCADE,
to="contenttypes.contenttype",
verbose_name="for content type",
),
),
(
"tournament",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="tournaments.tournament",
verbose_name="tournament",
),
),
],
options={
"verbose_name": "question",
"verbose_name_plural": "questions",
},
),
migrations.RunSQL("INSERT INTO adjfeedback_question (id, seq, tournament_id, text, name, answer_type, required, min_value, max_value, choices, for_content_type_id) SELECT id, seq, tournament_id, text, name, answer_type, required, min_value, max_value, choices, (SELECT id FROM django_content_type WHERE model='adjudicatorfeedback') for_content_type_id FROM adjfeedback_adjudicatorfeedbackquestion", migrations.RunSQL.noop),
migrations.RunSQL("SELECT pg_catalog.setval('public.adjfeedback_question_id_seq', (SELECT last_value FROM adjfeedback_adjudicatorfeedbackquestion_id_seq), true)", migrations.RunSQL.noop),
migrations.RunSQL(
"ALTER TABLE adjfeedback_adjudicatorfeedbackquestion DROP CONSTRAINT adjfeed_adjudicatorfeedbackquestion_tournament__reference_uniq, DROP CONSTRAINT adjfeed_adjudicatorfeedbackquestion_tournament__seq_uniq, DROP COLUMN answer_type, DROP COLUMN choices, DROP COLUMN max_value, DROP COLUMN min_value, DROP COLUMN required, DROP COLUMN seq, DROP COLUMN name, DROP COLUMN text, DROP COLUMN tournament_id",
state_operations=[
migrations.RemoveConstraint(
model_name="adjudicatorfeedbackquestion",
name="adjfeed_adjudicatorfeedbackquestion_tournament__reference_uniq",
),
migrations.RemoveConstraint(
model_name="adjudicatorfeedbackquestion",
name="adjfeed_adjudicatorfeedbackquestion_tournament__seq_uniq",
),
migrations.RemoveField(
model_name="adjudicatorfeedbackquestion",
name="answer_type",
),
migrations.RemoveField(
model_name="adjudicatorfeedbackquestion",
name="choices",
),
migrations.RemoveField(
model_name="adjudicatorfeedbackquestion",
name="max_value",
),
migrations.RemoveField(
model_name="adjudicatorfeedbackquestion",
name="min_value",
),
migrations.RemoveField(
model_name="adjudicatorfeedbackquestion",
name="name",
),
migrations.RemoveField(
model_name="adjudicatorfeedbackquestion",
name="required",
),
migrations.RemoveField(
model_name="adjudicatorfeedbackquestion",
name="seq",
),
migrations.RemoveField(
model_name="adjudicatorfeedbackquestion",
name="text",
),
migrations.RemoveField(
model_name="adjudicatorfeedbackquestion",
name="tournament",
),
],
),
migrations.RunSQL(
"ALTER TABLE adjfeedback_adjudicatorfeedbackquestion RENAME COLUMN id TO question_ptr",
state_operations=[
migrations.RemoveField(
model_name="adjudicatorfeedbackquestion",
name="id",
),
migrations.AddField(
model_name="adjudicatorfeedbackquestion",
name="question_ptr",
field=models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
],
),
migrations.AlterField(
model_name="adjudicatorfeedbackquestion",
name="question_ptr",
field=models.OneToOneField(
auto_created=True,
default=1,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="adjfeedback.question",
),
preserve_default=False,
),
*chain.from_iterable(answer_migrations),
]
Loading

0 comments on commit 4e2a921

Please sign in to comment.