Compare commits

..

No commits in common. "1df3b67fa10e78a421e385d802facb775eb9cb19" and "f6c4c732d6971ad8248843e186eff23f78a37686" have entirely different histories.

13 changed files with 162 additions and 187 deletions

View file

@ -13,33 +13,50 @@ class Item(models.Model):
max_length=50 max_length=50
) )
class Quality(models.IntegerChoices): QUALITY_POOR = 0
POOR = 0 QUALITY_COMMON = 1
COMMON = 1 QUALITY_UNCOMMON = 2
UNCOMMON = 2 QUALITY_RARE = 3
RARE = 3 QUALITY_EPIC = 4
EPIC = 4 QUALITY_LEGENDARY = 5
LEGENDARY = 5 QUALITY_ARTIFACT = 6
ARTIFACT = 6 QUALITY_CHOICES = [
(QUALITY_POOR, "Poor"),
(QUALITY_COMMON, "Common"),
(QUALITY_UNCOMMON, "Uncommon"),
(QUALITY_RARE, "Rare"),
(QUALITY_EPIC, "Epic"),
(QUALITY_LEGENDARY, "Legendary"),
(QUALITY_ARTIFACT, "Artifact"),
]
quality = models.PositiveSmallIntegerField( quality = models.PositiveSmallIntegerField(
choices=Quality.choices choices=QUALITY_CHOICES
) )
class Klass(models.IntegerChoices): KLASS_CONSUMABLES = 0
CONSUMABLES = 0 KLASS_CONTAINERS = 1
CONTAINERS = 1 KLASS_WEAPONS = 2
WEAPONS = 2 KLASS_ARMOR = 4
ARMOR = 4 KLASS_PROJECTILES = 6
PROJECTILES = 6 KLASS_TRADE_GOODS = 7
TRADE_GOODS = 7 KLASS_RECIPES = 9
RECIPES = 9 KLASS_QUIVER = 11
QUIVER = 11 KLASS_QUEST_ITEMS = 12
QUEST_ITEMS = 12 KLASS_MISCELLANEOUS = 15
MISCELLANEOUS = 15 KLASS_CHOICES = [
(KLASS_CONSUMABLES, "Consumables"),
(KLASS_CONTAINERS, "Containers"),
(KLASS_WEAPONS, "Weapons"),
(KLASS_ARMOR, "Armor"),
(KLASS_PROJECTILES, "Projectiles"),
(KLASS_TRADE_GOODS, "Trade Goods"),
(KLASS_RECIPES, "Recipes"),
(KLASS_QUIVER, "Quiver"),
(KLASS_QUEST_ITEMS, "Quest Items"),
(KLASS_MISCELLANEOUS, "Miscellaneous"),
]
klass = models.PositiveSmallIntegerField( klass = models.PositiveSmallIntegerField(
choices=Klass.choices choices=KLASS_CHOICES
) )
sub_klass = models.SmallIntegerField( sub_klass = models.SmallIntegerField(

View file

@ -160,11 +160,11 @@ CRISPY_TEMPLATE_PACK = "bootstrap4"
# Drakul # Drakul
DEFAULT_ATTENDANCE = { DEFAULT_ATTENDANCE = {
"NO_RESPONSE": 0.0, "No Response": 0.0,
"SIGNED_OFF": 0.0, "Signed Off": 0.0,
"SIGNED_UP": 1.0, "Signed Up": 1.0,
"STANDBY": 1.0, "Stand By": 1.0,
"CONFIRMED": 1.0, "Confirmed": 1.0,
} }
ATTENDANCE_COLORS = [ ATTENDANCE_COLORS = [

View file

@ -5,7 +5,7 @@ from .models import Raid, RaidResponse, RaidComment, InstanceReset
class RaidResponseInline(admin.TabularInline): class RaidResponseInline(admin.TabularInline):
model = RaidResponse model = RaidResponse
fields = ["character", "role", "status", "group", "attendance", "note"] fields = ["character", "role", "status", "attendance", "note"]
extra = 0 extra = 0

View file

@ -16,16 +16,16 @@ class RaidResponseForm(ModelForm):
self.fields["character"].queryset = user.characters self.fields["character"].queryset = user.characters
self.fields["character"].initial = user.main self.fields["character"].initial = user.main
self.fields["role"].initial = user.main.role self.fields["role"].initial = user.main.role
self.fields["status"].choices = RaidResponse.UserStatus.choices self.fields["status"].choices = RaidResponse.USER_STATUS_CHOICES
self.helper = FormHelper() self.helper = FormHelper()
if self.instance.status <= RaidResponse.Status.SIGNED_OFF: if self.instance.status <= RaidResponse.SIGNED_OFF:
signup_button = StrictButton( signup_button = StrictButton(
"Sign Up", "Sign Up",
type="submit", type="submit",
name="status", name="status",
value=RaidResponse.Status.SIGNED_UP, value=RaidResponse.SIGNED_UP,
css_class="btn-success btn-block" css_class="btn-success btn-block"
) )
else: else:
@ -33,7 +33,7 @@ class RaidResponseForm(ModelForm):
"Change", "Change",
type="submit", type="submit",
name="status", name="status",
value=RaidResponse.Status.SIGNED_UP, value=RaidResponse.SIGNED_UP,
css_class="btn-primary btn-block" css_class="btn-primary btn-block"
) )
self.helper.layout = Layout( self.helper.layout = Layout(
@ -46,9 +46,9 @@ class RaidResponseForm(ModelForm):
"Sign Off", "Sign Off",
type="submit", type="submit",
name="status", name="status",
value=RaidResponse.Status.SIGNED_OFF, value=RaidResponse.SIGNED_OFF,
css_class="btn-danger btn-block", css_class="btn-danger btn-block",
disabled=self.instance.pk and self.instance.status == RaidResponse.Status.SIGNED_OFF disabled=self.instance.pk and self.instance.status == RaidResponse.SIGNED_OFF
), ),
css_class="col-md-3" css_class="col-md-3"
), ),
@ -109,7 +109,7 @@ class RaidCommentForm(ModelForm):
RaidResponseFormSet = inlineformset_factory( RaidResponseFormSet = inlineformset_factory(
Raid, # parent model Raid, # parent model
RaidResponse, RaidResponse,
fields=["character", "role", "status", "group", "note", "attendance"], fields=["character", "role", "status", "note", "attendance"],
can_delete=False, can_delete=False,
extra=1 extra=1
) )
@ -124,7 +124,6 @@ class RaidResponseFormSetHelper(FormHelper):
Field("character", css_class="character-select"), Field("character", css_class="character-select"),
Field("role", css_class="role-select"), Field("role", css_class="role-select"),
Field("status", css_class="status-select"), Field("status", css_class="status-select"),
Field("group", css_class="group-select"),
Field("note"), Field("note"),
Field("attendance", css_class="attendance-input"), Field("attendance", css_class="attendance-input"),
) )

View file

@ -1,18 +0,0 @@
# Generated by Django 3.0.2 on 2020-04-21 02:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('raids', '0006_auto_20200417_0854'),
]
operations = [
migrations.AlterField(
model_name='raidresponse',
name='status',
field=models.PositiveSmallIntegerField(choices=[(0, 'No Response'), (1, 'Signed Off'), (2, 'Signed Up'), (3, 'Standby'), (4, 'Confirmed')]),
),
]

View file

@ -1,22 +0,0 @@
# Generated by Django 3.0.2 on 2020-04-21 17:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('raids', '0007_auto_20200421_0256'),
]
operations = [
migrations.AlterModelOptions(
name='raidresponse',
options={'ordering': ['-status', 'group', 'role', 'character__klass', 'character__user__rank', 'character__name']},
),
migrations.AddField(
model_name='raidresponse',
name='group',
field=models.PositiveSmallIntegerField(choices=[(1, 'Group 1'), (2, 'Group 2'), (3, 'Group 3'), (4, 'Group 4')], default=1),
),
]

View file

@ -57,40 +57,38 @@ class RaidResponse(models.Model):
) )
role = models.PositiveSmallIntegerField( role = models.PositiveSmallIntegerField(
choices=Character.Roles.choices, choices=Character.ROLE_CHOICES,
blank=True, blank=True,
null=True null=True
) )
class Status(models.IntegerChoices): NO_RESPONSE = 0
NO_RESPONSE = 0 SIGNED_OFF = 1
SIGNED_OFF = 1 SIGNED_UP = 2
SIGNED_UP = 2 STANDBY = 3
STANDBY = 3 CONFIRMED = 4
CONFIRMED = 4 STATUS_CHOICES = [
(NO_RESPONSE, "No Response"),
@property (SIGNED_OFF, "Signed Off"),
def default_attendance(self): (SIGNED_UP, "Signed Up"),
return settings.DEFAULT_ATTENDANCE[self.name] (STANDBY, "Stand By"),
(CONFIRMED, "Confirmed"),
class UserStatus(models.IntegerChoices): ]
SIGNED_OFF = 1 USER_STATUS_CHOICES = [
SIGNED_UP = 2 (SIGNED_OFF, "Signed Off"),
(SIGNED_UP, "Signed Up"),
]
status = models.PositiveSmallIntegerField( status = models.PositiveSmallIntegerField(
choices=Status.choices choices=STATUS_CHOICES
) )
class Groups(models.IntegerChoices): STATUS_DEFAULT_ATTENDANCE = {
GROUP_1 = 1 NO_RESPONSE: settings.DEFAULT_ATTENDANCE["No Response"],
GROUP_2 = 2 SIGNED_OFF: settings.DEFAULT_ATTENDANCE["Signed Off"],
GROUP_3 = 3 SIGNED_UP: settings.DEFAULT_ATTENDANCE["Signed Up"],
GROUP_4 = 4 STANDBY: settings.DEFAULT_ATTENDANCE["Stand By"],
CONFIRMED: settings.DEFAULT_ATTENDANCE["Confirmed"],
group = models.PositiveSmallIntegerField( }
choices=Groups.choices,
default=Groups.GROUP_1
)
attendance = models.DecimalField( attendance = models.DecimalField(
max_digits=3, max_digits=3,
@ -104,7 +102,7 @@ class RaidResponse(models.Model):
) )
class Meta: class Meta:
ordering = ["-status", "group", "role", "character__klass", "character__user__rank", "character__name"] ordering = ["-status", "role", "character__klass", "character__user__rank", "character__name"]
constraints = [ constraints = [
models.UniqueConstraint(fields=["raid", "character"], name="unique_character_raid_signup") models.UniqueConstraint(fields=["raid", "character"], name="unique_character_raid_signup")
] ]
@ -115,14 +113,14 @@ class RaidResponse(models.Model):
def clean(self): def clean(self):
# Make sure no-responses and sign-offs are role-agnostic, but all other responses are not # Make sure no-responses and sign-offs are role-agnostic, but all other responses are not
if self.status <= RaidResponse.Status.SIGNED_OFF: if self.status <= RaidResponse.SIGNED_OFF:
self.role = None self.role = None
elif self.role is None: elif self.role is None:
raise ValidationError({"role": "This field is required."}) raise ValidationError({"role": "This field is required."})
# Set attendance to one of the default values if status was changed # Set attendance to one of the default values if status was changed
if self.status != self._original_status: if self.status != self._original_status:
self.attendance = RaidResponse.Status(self.status).default_attendance self.attendance = RaidResponse.STATUS_DEFAULT_ATTENDANCE[self.status]
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
super().save(*args, **kwargs) super().save(*args, **kwargs)

View file

@ -11,7 +11,7 @@ User = get_user_model()
@receiver(post_save, sender=Raid) @receiver(post_save, sender=Raid)
def create_raid_no_responses(instance: Raid, **kwargs): def create_raid_no_responses(instance: Raid, **kwargs):
# Delete all pre-existing no-responses, in case the deadline was changed # Delete all pre-existing no-responses, in case the deadline was changed
instance.responses.filter(status=RaidResponse.Status.NO_RESPONSE).delete() instance.responses.filter(status=RaidResponse.NO_RESPONSE).delete()
# Then create them (again) # Then create them (again)
users = User.objects \ users = User.objects \
.exclude(Q(date_joined__gt=instance.date) | Q(characters__raid_responses__raid=instance) | Q(is_active=False)) \ .exclude(Q(date_joined__gt=instance.date) | Q(characters__raid_responses__raid=instance) | Q(is_active=False)) \
@ -20,8 +20,8 @@ def create_raid_no_responses(instance: Raid, **kwargs):
RaidResponse( RaidResponse(
raid=instance, raid=instance,
character=user.main, character=user.main,
status=RaidResponse.Status.NO_RESPONSE, status=RaidResponse.NO_RESPONSE,
attendance=RaidResponse.Status.NO_RESPONSE.default_attendance attendance=RaidResponse.STATUS_DEFAULT_ATTENDANCE[RaidResponse.NO_RESPONSE]
) )
for user in users for user in users
) )
@ -30,7 +30,7 @@ def create_raid_no_responses(instance: Raid, **kwargs):
@receiver(post_save, sender=User) @receiver(post_save, sender=User)
def create_user_no_responses(instance: User, **kwargs): def create_user_no_responses(instance: User, **kwargs):
# Delete all pre-existing no-responses for this user, in case date_joined or main was changed # Delete all pre-existing no-responses for this user, in case date_joined or main was changed
RaidResponse.objects.filter(character__user=instance, status=RaidResponse.Status.NO_RESPONSE).delete() RaidResponse.objects.filter(character__user=instance, status=RaidResponse.NO_RESPONSE).delete()
# Then create them (again) # Then create them (again)
if not instance.is_active: if not instance.is_active:
return return
@ -38,8 +38,8 @@ def create_user_no_responses(instance: User, **kwargs):
RaidResponse( RaidResponse(
raid=raid, raid=raid,
character=instance.main, character=instance.main,
status=RaidResponse.Status.NO_RESPONSE, status=RaidResponse.NO_RESPONSE,
attendance=RaidResponse.Status.NO_RESPONSE.default_attendance attendance=RaidResponse.STATUS_DEFAULT_ATTENDANCE[RaidResponse.NO_RESPONSE]
) )
for raid in Raid.objects.exclude( for raid in Raid.objects.exclude(
Q(date__lt=instance.date_joined) | Q(responses__character__user=instance) Q(date__lt=instance.date_joined) | Q(responses__character__user=instance)

View file

@ -54,49 +54,44 @@
{% regroup raid.responses.all 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 %} {% for status, status_responses in status_responses_list %}
{% regroup status_responses by get_group_display as group_responses_list %} <div class="card mb-2">
{% for group, group_responses in group_responses_list %} <h6 class="card-header d-flex response-status-{{ status | slugify }}-bg">
<div class="card mb-2"> <span class="mr-auto">{{ status }} ({{ status_responses | length }})</span>
<h6 class="card-header d-flex response-status-{{ status | slugify }}-bg"> <span type="button" class="badge badge-dark" data-toggle="modal" data-target="#exportModal">Export</span>
<span class="mr-auto"> </h6>
{{ status }}{% if group_responses_list|length > 1 %}: {{ group }}{% endif %} ({{ status_responses | length }}) <div class="card-body mb-n4">
</span> {% regroup status_responses by get_role_display as role_responses_list %}
<span type="button" class="badge badge-dark" data-toggle="modal" data-target="#exportModal">Export</span> {% for role, role_responses in role_responses_list %}
</h6> {% if role is not None %}
<div class="card-body mb-n4"> <h6 class="card-title border-bottom pb-2">{{ role }} ({{ role_responses | length }})</h6>
{% regroup group_responses by get_role_display as role_responses_list %} {% endif %}
{% for role, role_responses in role_responses_list %} <div class="d-flex flex-wrap">
{% if role is not None %} {% regroup role_responses by character.klass as class_responses_list %}
<h6 class="card-title border-bottom pb-2">{{ role }} ({{ role_responses | length }})</h6> {% for class, class_responses in class_responses_list %}
{% endif %} <div class="d-flex flex-column mr-2 mb-4">
<div class="d-flex flex-wrap"> {% regroup class_responses by character.user.rank as rank_responses_list %}
{% regroup role_responses by character.klass as class_responses_list %} {% for rank, rank_responses in rank_responses_list %}
{% for class, class_responses in class_responses_list %} <div class="d-flex justify-content-center align-items-center">
<div class="d-flex flex-column mr-2 mb-4"> <hr class="flex-grow-1 my-0">
{% regroup class_responses by character.user.rank as rank_responses_list %} <div class="mx-2 my-0 font-weight-light text-muted small">{{ rank }}</div>
{% for rank, rank_responses in rank_responses_list %} <hr class="flex-grow-1 my-0">
<div class="d-flex justify-content-center align-items-center">
<hr class="flex-grow-1 my-0">
<div class="mx-2 my-0 font-weight-light text-muted small">{{ rank }}</div>
<hr class="flex-grow-1 my-0">
</div>
{% for response in rank_responses %}
<a class="btn response-character-button mb-1 btn-class-{{ response.character.klass }}" role="button" href="#">
{% if response.character != response.character.user.main %}
<abbr title="{{ response.character.user.main }}">{{ response.character.name }}</abbr>
{% else %}
{{ response.character.name }}
{% endif %}
</a>
{% endfor %}
{% endfor %}
</div> </div>
{% for response in rank_responses %}
<a class="btn response-character-button mb-1 btn-class-{{ response.character.klass }}" role="button" href="#">
{% if response.character != response.character.user.main %}
<abbr title="{{ response.character.user.main }}">{{ response.character.name }}</abbr>
{% else %}
{{ response.character.name }}
{% endif %}
</a>
{% endfor %}
{% endfor %} {% endfor %}
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
{% endfor %}
</div> </div>
{% endfor %} </div>
{% endfor %} {% endfor %}
<div id="comments" class="card mt-4 mb-4"> <div id="comments" class="card mt-4 mb-4">
@ -109,7 +104,7 @@
<strong>{{ comment.user.main.name }}</strong> <small class="text-muted">&middot; {{ comment.date_created }}</small> <strong>{{ comment.user.main.name }}</strong> <small class="text-muted">&middot; {{ comment.date_created }}</small>
</p> </p>
<p class="card-text"> <p class="card-text">
{{ comment.body | linebreaksbr }} {{ comment.body }}
</p> </p>
</div> </div>
</div> </div>

View file

@ -44,23 +44,17 @@
{% regroup formset by status.value as status_forms_list %} {% regroup formset by status.value as status_forms_list %}
{% for status, status_forms in status_forms_list %} {% for status, status_forms in status_forms_list %}
{% regroup status_forms by group.value as group_forms_list %} {% regroup status_forms by role.value as role_forms_list %}
{% for group, group_forms in group_forms_list %} {% for role, role_forms in role_forms_list %}
{% regroup group_forms by role.value as role_forms_list %} {% for form in role_forms %}
{% for role, role_forms in role_forms_list %} {% if form_show_errors and not form.is_extra %}
{% for form in role_forms %} {% include "bootstrap4/errors.html" %}
{% if form_show_errors and not form.is_extra %}
{% include "bootstrap4/errors.html" %}
{% endif %}
<tr>
{% for field in form %}
{% include 'bootstrap4/field.html' with tag="td" form_show_labels=False %}
{% endfor %}
</tr>
{% endfor %}
{% if not forloop.last %}
<tr><td class="p-1"></td></tr>
{% endif %} {% endif %}
<tr>
{% for field in form %}
{% include 'bootstrap4/field.html' with tag="td" form_show_labels=False %}
{% endfor %}
</tr>
{% endfor %} {% endfor %}
{% if not forloop.last %} {% if not forloop.last %}
<tr><td class="p-2"></td></tr> <tr><td class="p-2"></td></tr>

View file

@ -4,7 +4,7 @@ from datetime import timedelta
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.auth.mixins import PermissionRequiredMixin
from django.db.models import Q, Max from django.db.models import Q, Max, prefetch_related_objects
from django.http import Http404 from django.http import Http404
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
from django.utils import timezone from django.utils import timezone

View file

@ -93,29 +93,41 @@ class Character(models.Model):
unique=True unique=True
) )
class Klasses(models.IntegerChoices): DRUID = 1
DRUID = 1 HUNTER = 2
HUNTER = 2 MAGE = 3
MAGE = 3 PALADIN = 4
PALADIN = 4 PRIEST = 5
PRIEST = 5 ROGUE = 6
ROGUE = 6 SHAMAN = 7
SHAMAN = 7 WARLOCK = 8
WARLOCK = 8 WARRIOR = 9
WARRIOR = 9 CLASS_CHOICES = [
(DRUID, "Druid"),
(HUNTER, "Hunter"),
(MAGE, "Mage"),
(PALADIN, "Paladin"),
(PRIEST, "Priest"),
(ROGUE, "Rogue"),
(SHAMAN, "Shaman"),
(WARLOCK, "Warlock"),
(WARRIOR, "Warrior"),
]
klass = models.PositiveSmallIntegerField( klass = models.PositiveSmallIntegerField(
"class", "class",
choices=Klasses.choices choices=CLASS_CHOICES
) )
class Roles(models.IntegerChoices): TANK = 1
TANK = 1 HEALER = 2
HEALER = 2 DAMAGE = 3
DAMAGE = 3 ROLE_CHOICES = [
(TANK, "Tank"),
(HEALER, "Healer"),
(DAMAGE, "Damage"),
]
role = models.PositiveSmallIntegerField( role = models.PositiveSmallIntegerField(
choices=Roles.choices choices=ROLE_CHOICES
) )
class Meta: class Meta:

View file

@ -14,7 +14,7 @@ def attendance_cell(response: RaidResponse):
if response is None: if response is None:
return "" return ""
if response.attendance == RaidResponse.Status(response.status).default_attendance: if response.attendance == RaidResponse.STATUS_DEFAULT_ATTENDANCE[response.status]:
cell = "" cell = ""
else: else:
cell = response.attendance cell = response.attendance