From c01bb56b5de5b906fc093d668da8f6c93b54b589 Mon Sep 17 00:00:00 2001 From: "Casper V. Kristensen" Date: Mon, 6 Jan 2020 23:07:56 +0100 Subject: [PATCH] Add actual no-response RaidResponses to database; allow setting default attendance value for all response statuses in settings. --- drakul/base/settings/common.py | 9 +++- drakul/base/static/css/main.css | 20 ++++----- drakul/raids/apps.py | 3 ++ drakul/raids/forms.py | 2 +- .../migrations/0005_auto_20200106_2305.py | 22 ++++++++++ drakul/raids/models.py | 28 +++++++----- drakul/raids/signals.py | 43 +++++++++++++++++++ drakul/raids/templates/raids/raid_detail.html | 4 +- drakul/raids/views.py | 16 ------- 9 files changed, 104 insertions(+), 43 deletions(-) create mode 100644 drakul/raids/migrations/0005_auto_20200106_2305.py create mode 100644 drakul/raids/signals.py diff --git a/drakul/base/settings/common.py b/drakul/base/settings/common.py index 884bda6..0df7c39 100755 --- a/drakul/base/settings/common.py +++ b/drakul/base/settings/common.py @@ -158,5 +158,10 @@ CRISPY_TEMPLATE_PACK = "bootstrap4" # Drakul -DEFAULT_ATTENDANCE_ATTENDING = 1.0 -DEFAULT_ATTENDANCE_NOT_ATTENDING = 0.0 +DEFAULT_ATTENDANCE = { + "No Response": 0.0, + "Signed Off": 0.0, + "Signed Up": 1.0, + "Stand By": 1.0, + "Confirmed": 1.0, +} diff --git a/drakul/base/static/css/main.css b/drakul/base/static/css/main.css index cdece3b..b90d627 100644 --- a/drakul/base/static/css/main.css +++ b/drakul/base/static/css/main.css @@ -122,6 +122,16 @@ /* Response bg and text colors from https://getbootstrap.com/docs/4.3/utilities/colors/ */ +.response-status-no-response-bg, .response-status-0-bg { /* secondary */ + color: #fff; + background-color: #6c757d; +} +a.response-status-no-response-bg:focus, a.response-status-0-bg:focus, +a.response-status-no-response-bg:hover, a.response-status-0-bg:hover { + color: #fff; + background-color: #545b62; +} + .response-status-signed-off-bg, .response-status-1-bg { /* danger */ color: #fff; background-color: #dc3545; @@ -162,16 +172,6 @@ a.response-status-confirmed-bg:hover, a.response-status-4-bg:hover { background-color: #1e7e34; } -.response-status-no-response-bg, .response-status-0-bg { /* secondary */ - color: #fff; - background-color: #6c757d; -} -a.response-status-no-response-bg:focus, a.response-status-0-bg:focus, -a.response-status-no-response-bg:hover, a.response-status-0-bg:hover { - color: #fff; - background-color: #545b62; -} - /* https://django-crispy-forms.readthedocs.io/en/latest/crispy_tag_forms.html#change-required-fields */ .asteriskField { diff --git a/drakul/raids/apps.py b/drakul/raids/apps.py index 0aa1c09..bf38183 100755 --- a/drakul/raids/apps.py +++ b/drakul/raids/apps.py @@ -3,3 +3,6 @@ from django.apps import AppConfig class RaidsConfig(AppConfig): name = "drakul.raids" + + def ready(self): + from . import signals # noqa diff --git a/drakul/raids/forms.py b/drakul/raids/forms.py index a4a2877..dcaf6b4 100644 --- a/drakul/raids/forms.py +++ b/drakul/raids/forms.py @@ -20,7 +20,7 @@ class RaidResponseForm(ModelForm): self.helper = FormHelper() - if self.instance.pk is None or self.instance.status == RaidResponse.SIGNED_OFF: + if self.instance.status <= RaidResponse.SIGNED_OFF: signup_button = StrictButton( "Sign Up", type="submit", diff --git a/drakul/raids/migrations/0005_auto_20200106_2305.py b/drakul/raids/migrations/0005_auto_20200106_2305.py new file mode 100644 index 0000000..673904e --- /dev/null +++ b/drakul/raids/migrations/0005_auto_20200106_2305.py @@ -0,0 +1,22 @@ +# Generated by Django 2.2.6 on 2020-01-06 23:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('raids', '0004_raid_is_optional'), + ] + + operations = [ + migrations.AlterModelOptions( + name='raidresponse', + options={'ordering': ['-status', 'role', 'character__klass', 'character__user__rank', 'character__name']}, + ), + migrations.AlterField( + model_name='raidresponse', + name='status', + field=models.PositiveSmallIntegerField(choices=[(0, 'No Response'), (1, 'Signed Off'), (2, 'Signed Up'), (3, 'Stand By'), (4, 'Confirmed')]), + ), + ] diff --git a/drakul/raids/models.py b/drakul/raids/models.py index e11709d..309f96d 100755 --- a/drakul/raids/models.py +++ b/drakul/raids/models.py @@ -62,11 +62,13 @@ class RaidResponse(models.Model): null=True ) + NO_RESPONSE = 0 SIGNED_OFF = 1 SIGNED_UP = 2 STANDBY = 3 CONFIRMED = 4 STATUS_CHOICES = [ + (NO_RESPONSE, "No Response"), (SIGNED_OFF, "Signed Off"), (SIGNED_UP, "Signed Up"), (STANDBY, "Stand By"), @@ -80,17 +82,24 @@ class RaidResponse(models.Model): choices=STATUS_CHOICES ) - note = models.CharField( - max_length=100, - blank=True, - null=True - ) + STATUS_DEFAULT_ATTENDANCE = { + NO_RESPONSE: settings.DEFAULT_ATTENDANCE["No Response"], + SIGNED_OFF: settings.DEFAULT_ATTENDANCE["Signed Off"], + SIGNED_UP: settings.DEFAULT_ATTENDANCE["Signed Up"], + STANDBY: settings.DEFAULT_ATTENDANCE["Stand By"], + CONFIRMED: settings.DEFAULT_ATTENDANCE["Confirmed"], + } attendance = models.DecimalField( max_digits=3, decimal_places=2, blank=True ) + note = models.CharField( + max_length=100, + blank=True, + null=True + ) class Meta: ordering = ["-status", "role", "character__klass", "character__user__rank", "character__name"] @@ -103,18 +112,15 @@ class RaidResponse(models.Model): self._original_status = self.status def clean(self): - # Make sure sign-offs are role-agnostic, but all other responses are not - if self.status == RaidResponse.SIGNED_OFF: + # Make sure no-responses and sign-offs are role-agnostic, but all other responses are not + if self.status <= RaidResponse.SIGNED_OFF: self.role = None elif self.role is None: raise ValidationError({"role": "This field is required."}) # Set attendance to one of the default values if status was changed if self.status != self._original_status: - if self.status >= RaidResponse.SIGNED_UP: - self.attendance = settings.DEFAULT_ATTENDANCE_ATTENDING # 1.0 by default - else: - self.attendance = settings.DEFAULT_ATTENDANCE_NOT_ATTENDING # 0.0 by default + self.attendance = RaidResponse.STATUS_DEFAULT_ATTENDANCE[self.status] def save(self, *args, **kwargs): super().save(*args, **kwargs) diff --git a/drakul/raids/signals.py b/drakul/raids/signals.py new file mode 100644 index 0000000..1481f44 --- /dev/null +++ b/drakul/raids/signals.py @@ -0,0 +1,43 @@ +from django.contrib.auth import get_user_model +from django.db.models import Q +from django.db.models.signals import post_save +from django.dispatch import receiver + +from .models import Raid, RaidResponse + +User = get_user_model() + + +@receiver(post_save, sender=Raid) +def create_raid_no_responses(instance: Raid, **kwargs): + # Delete all pre-existing no-responses, in case the deadline was changed + instance.responses.filter(status=RaidResponse.NO_RESPONSE).delete() + # Then create them (again) + users = User.objects \ + .exclude(Q(date_joined__gt=instance.date) | Q(characters__raid_responses__raid=instance) | Q(is_active=False)) \ + .select_related("main") + RaidResponse.objects.bulk_create( + RaidResponse( + raid=instance, + character=user.main, + status=RaidResponse.NO_RESPONSE, + attendance=RaidResponse.STATUS_DEFAULT_ATTENDANCE[RaidResponse.NO_RESPONSE] + ) + for user in users + ) + + +@receiver(post_save, sender=User) +def create_user_no_responses(instance: User, **kwargs): + # Delete all pre-existing no-responses for this user, in case date_joined or main as changed + RaidResponse.objects.filter(character__user=instance, status=RaidResponse.NO_RESPONSE).delete() + # Then create them (again) + RaidResponse.objects.bulk_create( + RaidResponse( + raid=raid, + character=instance.main, + status=RaidResponse.NO_RESPONSE, + attendance=RaidResponse.STATUS_DEFAULT_ATTENDANCE[RaidResponse.NO_RESPONSE] + ) + for raid in Raid.objects.filter(response_deadline__gte=instance.date_joined) + ) diff --git a/drakul/raids/templates/raids/raid_detail.html b/drakul/raids/templates/raids/raid_detail.html index 7f3d890..8d513c3 100644 --- a/drakul/raids/templates/raids/raid_detail.html +++ b/drakul/raids/templates/raids/raid_detail.html @@ -52,9 +52,8 @@ -{% regroup responses by get_status_display as status_responses_list %} +{% regroup raid.responses.all by get_status_display as status_responses_list %} {% for status, status_responses in status_responses_list %} -{% with status=status|default_if_none:"No Response" %}
{{ status }} ({{ status_responses | length }}) @@ -93,7 +92,6 @@ {% endfor %}
-{% endwith %} {% endfor %}
diff --git a/drakul/raids/views.py b/drakul/raids/views.py index 1a27996..74010df 100644 --- a/drakul/raids/views.py +++ b/drakul/raids/views.py @@ -133,22 +133,6 @@ class RaidDetailView(SingleObjectMixin, MultiModelFormView): def get_response_form_success_url(self): return reverse("raid_detail", kwargs={"pk": self.object.pk}) - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - raid = context["raid"] = self.get_object() - - # Create temporary pseudo-responses for users who haven't responded - no_response_users = User.objects \ - .exclude(Q(date_joined__gt=raid.date) | Q(characters__raid_responses__raid=raid) | Q(is_active=False)) \ - .select_related("main") \ - .order_by("main__klass", "rank", "main__name") - pseudo_no_responses = [RaidResponse(character=user.main, status=None) - for user in no_response_users] - prefetch_related_objects(pseudo_no_responses, "character__user__rank", "character__user__main") - context["responses"] = list(raid.responses.all()) + pseudo_no_responses - - return context - def get(self, request, *args, **kwargs): self.object = self.get_object() return super().get(request, *args, **kwargs)