Add actual no-response RaidResponses to database; allow setting default attendance value for all response statuses in settings.
This commit is contained in:
parent
dd7f45599d
commit
c01bb56b5d
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -3,3 +3,6 @@ from django.apps import AppConfig
|
|||
|
||||
class RaidsConfig(AppConfig):
|
||||
name = "drakul.raids"
|
||||
|
||||
def ready(self):
|
||||
from . import signals # noqa
|
||||
|
|
|
@ -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",
|
||||
|
|
22
drakul/raids/migrations/0005_auto_20200106_2305.py
Normal file
22
drakul/raids/migrations/0005_auto_20200106_2305.py
Normal file
|
@ -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')]),
|
||||
),
|
||||
]
|
|
@ -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)
|
||||
|
|
43
drakul/raids/signals.py
Normal file
43
drakul/raids/signals.py
Normal file
|
@ -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)
|
||||
)
|
|
@ -52,9 +52,8 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{% 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" %}
|
||||
<div class="card mb-2">
|
||||
<h6 class="card-header d-flex response-status-{{ status | slugify }}-bg">
|
||||
<span class="mr-auto">{{ status }} ({{ status_responses | length }})</span>
|
||||
|
@ -93,7 +92,6 @@
|
|||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
|
||||
<div id="comments" class="card mt-4 mb-4">
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue