Compare commits
3 commits
4bcdf7eca9
...
12acc50d5a
Author | SHA1 | Date | |
---|---|---|---|
Casper V. Kristensen | 12acc50d5a | ||
Casper V. Kristensen | 5381795edc | ||
Casper V. Kristensen | a02c22c543 |
|
@ -6,13 +6,15 @@
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="d-flex justify-content-center align-items-center pt-5">
|
<div class="container">
|
||||||
<form class="m-auto" method="post" action="{% url 'login' %}">
|
<div class="d-flex justify-content-center align-items-center pt-5">
|
||||||
{% csrf_token %}
|
<form class="m-auto" method="post" action="{% url 'login' %}">
|
||||||
{{ form | crispy }}
|
{% csrf_token %}
|
||||||
<button type="submit" class="btn btn-primary btn-block">Log In</button>
|
{{ form | crispy }}
|
||||||
<input type="hidden" name="next" value="{{ next }}">
|
<button type="submit" class="btn btn-primary btn-block">Log In</button>
|
||||||
<p class="text-center mt-2"><a href="{% url 'signup' %}">Sign Up</a></p>
|
<input type="hidden" name="next" value="{{ next }}">
|
||||||
</form>
|
<p class="text-center mt-2"><a href="{% url 'signup' %}">Sign Up</a></p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
|
@ -6,13 +6,15 @@
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="d-flex justify-content-center align-items-center mt-5">
|
<div class="container">
|
||||||
<form class="m-auto" method="post" action="{% url 'password_change' %}">
|
<div class="d-flex justify-content-center align-items-center mt-5">
|
||||||
<h2>Change Password</h2>
|
<form class="m-auto" method="post" action="{% url 'password_change' %}">
|
||||||
<hr>
|
<h2>Change Password</h2>
|
||||||
{% csrf_token %}
|
<hr>
|
||||||
{{ form | crispy }}
|
{% csrf_token %}
|
||||||
<button type="submit" class="btn btn-primary btn-block">Change Password</button>
|
{{ form | crispy }}
|
||||||
</form>
|
<button type="submit" class="btn btn-primary btn-block">Change Password</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
|
@ -6,19 +6,21 @@
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="d-flex justify-content-center align-items-center pt-5">
|
<div class="container">
|
||||||
<form class="m-auto" method="post" action="{% url 'signup' %}">
|
<div class="d-flex justify-content-center align-items-center pt-5">
|
||||||
<h2>Sign Up</h2>
|
<form class="m-auto" method="post" action="{% url 'signup' %}">
|
||||||
<hr>
|
<h2>Sign Up</h2>
|
||||||
{% csrf_token %}
|
<hr>
|
||||||
{{ user_form | crispy }}
|
{% csrf_token %}
|
||||||
<h5 class="mt-5">Character</h5>
|
{{ user_form | crispy }}
|
||||||
<hr>
|
<h5 class="mt-5">Character</h5>
|
||||||
{{ character_form | crispy }}
|
<hr>
|
||||||
<hr>
|
{{ character_form | crispy }}
|
||||||
<button type="submit" class="btn btn-primary btn-block">Sign Up</button>
|
<hr>
|
||||||
<input type="hidden" name="next" value="{{ next }}">
|
<button type="submit" class="btn btn-primary btn-block">Sign Up</button>
|
||||||
<p class="text-center mt-2"><a href="{% url 'login' %}">Log In</a></p>
|
<input type="hidden" name="next" value="{{ next }}">
|
||||||
</form>
|
<p class="text-center mt-2"><a href="{% url 'login' %}">Log In</a></p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
|
@ -4,24 +4,26 @@
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>Bank</h2>
|
<div class="container">
|
||||||
<div class="card">
|
<h2>Bank</h2>
|
||||||
<div class="card-header d-flex">
|
<div class="card">
|
||||||
<div class="nav nav-tabs card-header-tabs mr-auto">
|
<div class="card-header d-flex">
|
||||||
<a class="nav-item nav-link {% active 'bank' %}" href="{% url 'bank' %}">Items</a>
|
<div class="nav nav-tabs card-header-tabs mr-auto">
|
||||||
<a class="nav-item nav-link {% active 'bank_item_log' %}" href="{% url 'bank_item_log' %}">Log</a>
|
<a class="nav-item nav-link {% active 'bank' %}" href="{% url 'bank' %}">Items</a>
|
||||||
<a class="nav-item nav-link {% active 'bank_money_log' %}" href="{% url 'bank_money_log' %}">Money</a>
|
<a class="nav-item nav-link {% active 'bank_item_log' %}" href="{% url 'bank_item_log' %}">Log</a>
|
||||||
{% if perms.bank.add_itemtransaction and perms.bank.add_moneytransaction %}
|
<a class="nav-item nav-link {% active 'bank_money_log' %}" href="{% url 'bank_money_log' %}">Money</a>
|
||||||
<a class="nav-item nav-link {% active 'bank_import' %}" href="{% url 'bank_import' %}">Import</a>
|
{% if perms.bank.add_itemtransaction and perms.bank.add_moneytransaction %}
|
||||||
{% endif %}
|
<a class="nav-item nav-link {% active 'bank_import' %}" href="{% url 'bank_import' %}">Import</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% block bank_header %}{% endblock bank_header %}
|
||||||
</div>
|
</div>
|
||||||
{% block bank_header %}{% endblock bank_header %}
|
<div class="card-body p-0">
|
||||||
|
{% block bank_content %}
|
||||||
|
{% endblock bank_content %}
|
||||||
|
</div>
|
||||||
|
{% block bank_footer %}
|
||||||
|
{% endblock bank_footer %}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body p-0">
|
|
||||||
{% block bank_content %}
|
|
||||||
{% endblock bank_content %}
|
|
||||||
</div>
|
|
||||||
{% block bank_footer %}
|
|
||||||
{% endblock bank_footer %}
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
|
@ -1,3 +1,13 @@
|
||||||
|
.container {
|
||||||
|
max-width: 1140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width:1600px) {
|
||||||
|
.container-wide {
|
||||||
|
max-width: 1500px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* WoW class-coloured text */
|
/* WoW class-coloured text */
|
||||||
.color-druid, .color-class-1 {color: #ff7d0a;}
|
.color-druid, .color-class-1 {color: #ff7d0a;}
|
||||||
.color-hunter, .color-class-2 {color: #abd473;}
|
.color-hunter, .color-class-2 {color: #abd473;}
|
||||||
|
|
|
@ -43,10 +43,8 @@
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main class="flex-shrink-0" role="main">
|
<main class="flex-shrink-0" role="main">
|
||||||
<div class="container">
|
{% block content %}
|
||||||
{% block content %}
|
{% endblock content %}
|
||||||
{% endblock content %}
|
|
||||||
</div>
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer class="footer mt-auto">
|
<footer class="footer mt-auto">
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from crispy_forms.bootstrap import StrictButton
|
from crispy_forms.bootstrap import StrictButton
|
||||||
from crispy_forms.helper import FormHelper
|
from crispy_forms.helper import FormHelper
|
||||||
from crispy_forms.layout import Submit, Layout, Column, Row, Field
|
from crispy_forms.layout import Submit, Layout, Column, Row, Field, HTML
|
||||||
from django.forms import ModelForm, inlineformset_factory
|
from django.forms import ModelForm, inlineformset_factory
|
||||||
|
|
||||||
from .models import RaidResponse, RaidComment, Raid
|
from .models import RaidResponse, RaidComment, Raid
|
||||||
|
@ -68,6 +68,40 @@ class RaidResponseForm(ModelForm):
|
||||||
return self.cleaned_data["role"] or self.cleaned_data["character"].role
|
return self.cleaned_data["role"] or self.cleaned_data["character"].role
|
||||||
|
|
||||||
|
|
||||||
|
class GuestRaidResponseForm(ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = RaidResponse
|
||||||
|
fields = ["guest_name", "guest_klass", "role", "status"]
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self.fields["status"].initial = RaidResponse.Status.SIGNED_UP
|
||||||
|
|
||||||
|
self.helper = FormHelper()
|
||||||
|
self.helper.layout = Layout(
|
||||||
|
Row(
|
||||||
|
Column("guest_name", css_class="col-12 col-md-3"),
|
||||||
|
Column("guest_klass", css_class="col-12 col-md-2"),
|
||||||
|
Column("role", css_class="col-12 col-md-2"),
|
||||||
|
Column("status", css_class="col-12 col-md-3"),
|
||||||
|
Column(
|
||||||
|
HTML("<label> </label>"), # offsets the button consistent with the other input elements
|
||||||
|
StrictButton(
|
||||||
|
"Add",
|
||||||
|
type="submit",
|
||||||
|
css_class=f"btn-block btn-primary"
|
||||||
|
),
|
||||||
|
css_class="form-group col-md-2"
|
||||||
|
),
|
||||||
|
css_class="form-row"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.helper.render_hidden_fields = True # Include MultiFormMixin's hidden form_id_field_prefix
|
||||||
|
self.helper.form_show_labels = True
|
||||||
|
self.helper.form_tag = False
|
||||||
|
|
||||||
|
|
||||||
class RaidResponseChangeForm(ModelForm):
|
class RaidResponseChangeForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = RaidResponse
|
model = RaidResponse
|
||||||
|
@ -120,7 +154,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", "guest_name", "guest_klass", "role", "status", "group", "note", "attendance"],
|
||||||
can_delete=False,
|
can_delete=False,
|
||||||
extra=1
|
extra=1
|
||||||
)
|
)
|
||||||
|
@ -133,10 +167,12 @@ class RaidResponseFormSetHelper(FormHelper):
|
||||||
super().__init__(form)
|
super().__init__(form)
|
||||||
self.layout = Layout(
|
self.layout = Layout(
|
||||||
Field("character", css_class="character-select"),
|
Field("character", css_class="character-select"),
|
||||||
|
Field("guest_name", css_class="guest-name"),
|
||||||
|
Field("guest_klass", css_class="guest-klass"),
|
||||||
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("group", css_class="group-select"),
|
||||||
Field("note"),
|
Field("note"),
|
||||||
Field("attendance", css_class="attendance-input"),
|
Field("attendance", css_class="attendance-input", style="width: 10ch"),
|
||||||
)
|
)
|
||||||
self.form_tag = False
|
self.form_tag = False
|
||||||
|
|
31
drakul/raids/migrations/0010_auto_20200923_1450.py
Normal file
31
drakul/raids/migrations/0010_auto_20200923_1450.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
# Generated by Django 3.1.1 on 2020-09-23 14:50
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('users', '0005_auto_20191121_1646'),
|
||||||
|
('raids', '0009_auto_20200528_0410'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='raidresponse',
|
||||||
|
name='guest_klass',
|
||||||
|
field=models.PositiveSmallIntegerField(blank=True, choices=[(1, 'Druid'), (2, 'Hunter'), (3, 'Mage'), (4, 'Paladin'), (5, 'Priest'), (6, 'Rogue'), (7, 'Shaman'), (8, 'Warlock'), (9, 'Warrior')], null=True, verbose_name='Guest class'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='raidresponse',
|
||||||
|
name='guest_name',
|
||||||
|
field=models.CharField(blank=True, max_length=12, null=True, validators=[django.core.validators.MinLengthValidator(2)], verbose_name='Guest name'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='raidresponse',
|
||||||
|
name='character',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='raid_responses', to='users.character'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -2,6 +2,7 @@ from datetime import timedelta
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.core.validators import MinLengthValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from drakul.users.models import Character
|
from drakul.users.models import Character
|
||||||
|
@ -53,9 +54,26 @@ class RaidResponse(models.Model):
|
||||||
Character,
|
Character,
|
||||||
related_name="raid_responses",
|
related_name="raid_responses",
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
db_index=True
|
db_index=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
guest_name = models.CharField(
|
||||||
|
"Guest name",
|
||||||
|
max_length=12, # Blizzard limits character names to 2-12 characters
|
||||||
|
validators=[MinLengthValidator(2)],
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
|
||||||
|
guest_klass = models.PositiveSmallIntegerField(
|
||||||
|
"Guest class",
|
||||||
|
choices=Character.Klass.choices,
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
|
||||||
role = models.PositiveSmallIntegerField(
|
role = models.PositiveSmallIntegerField(
|
||||||
choices=Character.Role.choices,
|
choices=Character.Role.choices,
|
||||||
blank=True,
|
blank=True,
|
||||||
|
@ -116,9 +134,23 @@ class RaidResponse(models.Model):
|
||||||
self._original_status = self.status
|
self._original_status = self.status
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
# Either Character or Guest Name/Guest Class must be set
|
||||||
|
if self.character is None:
|
||||||
|
errors = {}
|
||||||
|
if self.guest_name is None:
|
||||||
|
errors["guest_name"] = "This field is required for guests."
|
||||||
|
if self.guest_klass is None:
|
||||||
|
errors["guest_klass"] = "This field is required for guests."
|
||||||
|
if errors:
|
||||||
|
raise ValidationError(errors)
|
||||||
|
else:
|
||||||
|
self.guest_name = None
|
||||||
|
self.guest_klass = None
|
||||||
|
|
||||||
# Make sure no-responses and sign-offs are character- and role-agnostic, but all other responses are not
|
# Make sure no-responses and sign-offs are character- and role-agnostic, but all other responses are not
|
||||||
if self.status <= RaidResponse.Status.SIGNED_OFF:
|
if self.status <= RaidResponse.Status.SIGNED_OFF:
|
||||||
self.character = self.character.user.main
|
if not self.is_guest:
|
||||||
|
self.character = self.character.user.main
|
||||||
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."})
|
||||||
|
@ -128,9 +160,30 @@ class RaidResponse(models.Model):
|
||||||
self.attendance = RaidResponse.Status(self.status).default_attendance
|
self.attendance = RaidResponse.Status(self.status).default_attendance
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
# Delete guest "No Response"s
|
||||||
|
if self.is_guest and self.status == RaidResponse.Status.NO_RESPONSE:
|
||||||
|
self.delete()
|
||||||
|
return
|
||||||
|
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
self._original_status = self.status
|
self._original_status = self.status
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_guest(self):
|
||||||
|
return self.character is None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def character_name(self):
|
||||||
|
if self.is_guest:
|
||||||
|
return self.guest_name
|
||||||
|
return self.character.name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def character_klass(self):
|
||||||
|
if self.is_guest:
|
||||||
|
return self.guest_klass
|
||||||
|
return self.character.klass
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.character} {self.get_status_display()} for '{self.raid}'"
|
return f"{self.character} {self.get_status_display()} for '{self.raid}'"
|
||||||
|
|
||||||
|
|
|
@ -4,52 +4,54 @@
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>Raids</h2>
|
<div class="container">
|
||||||
<div class="d-flex justify-content-center mb-1">
|
<h2>Raids</h2>
|
||||||
<div class="btn-group" role="group" aria-label="Calendar navigation controls">
|
<div class="d-flex justify-content-center mb-1">
|
||||||
<a class="btn btn-secondary" role="button" href="{% url 'raid_calendar' previous_month.year previous_month.month %}"><</a>
|
<div class="btn-group" role="group" aria-label="Calendar navigation controls">
|
||||||
<a class="btn btn-secondary" role="button" href="{% url 'raid_calendar' %}">{{ month | date:"YEAR_MONTH_FORMAT" }}</a>
|
<a class="btn btn-secondary" role="button" href="{% url 'raid_calendar' previous_month.year previous_month.month %}"><</a>
|
||||||
<a class="btn btn-secondary" role="button" href="{% url 'raid_calendar' next_month.year next_month.month %}">></a>
|
<a class="btn btn-secondary" role="button" href="{% url 'raid_calendar' %}">{{ month | date:"YEAR_MONTH_FORMAT" }}</a>
|
||||||
|
<a class="btn btn-secondary" role="button" href="{% url 'raid_calendar' next_month.year next_month.month %}">></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<table class="raid-calendar table table-bordered bg-white mb-1">
|
||||||
|
<thead class="thead-light text-center">
|
||||||
|
<tr>
|
||||||
|
<th scope="col" class="w-14">Monday</th>
|
||||||
|
<th scope="col" class="w-14">Tuesday</th>
|
||||||
|
<th scope="col" class="w-14">Wednesday</th>
|
||||||
|
<th scope="col" class="w-14">Thursday</th>
|
||||||
|
<th scope="col" class="w-14">Friday</th>
|
||||||
|
<th scope="col" class="w-14">Saturday</th>
|
||||||
|
<th scope="col" class="w-14">Sunday</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for week in calendar %}
|
||||||
|
<tr>
|
||||||
|
{% for day in week %}
|
||||||
|
<td class="{% if day.date.month != month.month %}o-50{% endif %} {% if day.date == today %}table-warning{% endif %}">
|
||||||
|
<p class="mb-2"><small>{{ day.date | date:"j" }}</small></p>
|
||||||
|
<div class="d-flex flex-column">
|
||||||
|
{% for instance_reset in day.instance_resets %}
|
||||||
|
<span class="mb-1 badge badge-light text-left text-wrap font-weight-normal">
|
||||||
|
<span class="text-monospace">{{ instance_reset.time }}</span> <span class="text-muted">{{ instance_reset.name }} Resets</span>
|
||||||
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
{% for raid in day.raids %}
|
||||||
|
<a class="mb-1 badge response-status-{{ raid.max_status | default:0 }}-bg text-left text-wrap font-weight-normal" href="{% url 'raid_detail' raid.id %}">
|
||||||
|
<span class="text-monospace">{{ raid.date.time }}</span> <strong>{{ raid.title }}</strong>
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-end">
|
||||||
|
{% if perms.raids.add_raid %}<a class="btn btn-success" role="button" href="{% url 'raid_create' %}">Add New</a>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table class="raid-calendar table table-bordered bg-white mb-1">
|
|
||||||
<thead class="thead-light text-center">
|
|
||||||
<tr>
|
|
||||||
<th scope="col" class="w-14">Monday</th>
|
|
||||||
<th scope="col" class="w-14">Tuesday</th>
|
|
||||||
<th scope="col" class="w-14">Wednesday</th>
|
|
||||||
<th scope="col" class="w-14">Thursday</th>
|
|
||||||
<th scope="col" class="w-14">Friday</th>
|
|
||||||
<th scope="col" class="w-14">Saturday</th>
|
|
||||||
<th scope="col" class="w-14">Sunday</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for week in calendar %}
|
|
||||||
<tr>
|
|
||||||
{% for day in week %}
|
|
||||||
<td class="{% if day.date.month != month.month %}o-50{% endif %} {% if day.date == today %}table-warning{% endif %}">
|
|
||||||
<p class="mb-2"><small>{{ day.date | date:"j" }}</small></p>
|
|
||||||
<div class="d-flex flex-column">
|
|
||||||
{% for instance_reset in day.instance_resets %}
|
|
||||||
<span class="mb-1 badge badge-light text-left text-wrap font-weight-normal">
|
|
||||||
<span class="text-monospace">{{ instance_reset.time }}</span> <span class="text-muted">{{ instance_reset.name }} Resets</span>
|
|
||||||
</span>
|
|
||||||
{% endfor %}
|
|
||||||
{% for raid in day.raids %}
|
|
||||||
<a class="mb-1 badge response-status-{{ raid.max_status | default:0 }}-bg text-left text-wrap font-weight-normal" href="{% url 'raid_detail' raid.id %}">
|
|
||||||
<span class="text-monospace">{{ raid.date.time }}</span> <strong>{{ raid.title }}</strong>
|
|
||||||
</a>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
{% endfor %}
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-end">
|
|
||||||
{% if perms.raids.add_raid %}<a class="btn btn-success" role="button" href="{% url 'raid_create' %}">Add New</a>{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
|
@ -6,100 +6,103 @@
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="card mb-3">
|
<div class="container container-wide">
|
||||||
<h5 class="card-header">Raid Details</h5>
|
<div class="card mb-3">
|
||||||
<div class="card-body">
|
<h5 class="card-header">Raid Details</h5>
|
||||||
{% crispy raid_form %}
|
<div class="card-body">
|
||||||
|
{% crispy raid_form %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="container container-wide">
|
||||||
<div class="card mb-3">
|
<div class="card mb-3">
|
||||||
<h5 class="card-header">Raid Responses</h5>
|
<h5 class="card-header">Raid Responses</h5>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form class="responses-form table-borderless" method="post" action="{% url 'raid_change' raid.id %}">
|
<form class="responses-form table-borderless" method="post" action="{% url 'raid_change' raid.id %}">
|
||||||
{% crispy raid_response_formset raid_response_formset_helper %}
|
{% crispy raid_response_formset raid_response_formset_helper %}
|
||||||
<hr>
|
<hr>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="form-group col-lg-6">
|
<div class="form-group col-lg-6">
|
||||||
<label>Change Status</label>
|
<label>Change Status</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<div class="input-group-prepend">
|
<div class="input-group-prepend">
|
||||||
<label class="input-group-text" for="change-status-from-status-select">Set all</label>
|
<label class="input-group-text" for="change-status-from-status-select">Set all</label>
|
||||||
|
</div>
|
||||||
|
<select class="custom-select" id="change-status-from-status-select">
|
||||||
|
<option value="1">Signed Off</option>
|
||||||
|
<option value="2">Backup</option>
|
||||||
|
<option selected value="3">Signed Up</option>
|
||||||
|
<option value="4">Standby</option>
|
||||||
|
<option value="5">Confirmed</option>
|
||||||
|
</select>
|
||||||
|
<div class="input-group-append input-group-prepend">
|
||||||
|
<label class="input-group-text" for="change-status-to-status-select">to</label>
|
||||||
|
</div>
|
||||||
|
<select class="custom-select" id="change-status-to-status-select">
|
||||||
|
<option value="1">Signed Off</option>
|
||||||
|
<option value="2">Backup</option>
|
||||||
|
<option value="3">Signed Up</option>
|
||||||
|
<option value="4">Standby</option>
|
||||||
|
<option selected value="5">Confirmed</option>
|
||||||
|
</select>
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button class="btn btn-outline-secondary" type="button"
|
||||||
|
onclick="changeSignupStatus(
|
||||||
|
from=document.querySelector('#change-status-from-status-select').value,
|
||||||
|
to=document.querySelector('#change-status-to-status-select').value
|
||||||
|
);"
|
||||||
|
>Go</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<select class="custom-select" id="change-status-from-status-select">
|
</div>
|
||||||
<option value="1">Signed Off</option>
|
<div class="form-group col-lg-6">
|
||||||
<option value="2">Backup</option>
|
<label>Give Attendance</label>
|
||||||
<option selected value="3">Signed Up</option>
|
<div class="input-group">
|
||||||
<option value="4">Standby</option>
|
<div class="input-group-prepend">
|
||||||
<option value="5">Confirmed</option>
|
<label class="input-group-text" for="set-attendance-status-select">Give all</label>
|
||||||
</select>
|
</div>
|
||||||
<div class="input-group-append input-group-prepend">
|
<select class="custom-select" id="set-attendance-status-select">
|
||||||
<label class="input-group-text" for="change-status-to-status-select">to</label>
|
<option value="1">Signed Off</option>
|
||||||
</div>
|
<option value="2">Backup</option>
|
||||||
<select class="custom-select" id="change-status-to-status-select">
|
<option value="3">Signed Up</option>
|
||||||
<option value="1">Signed Off</option>
|
<option value="4">Standby</option>
|
||||||
<option value="2">Backup</option>
|
<option selected value="5">Confirmed</option>
|
||||||
<option value="3">Signed Up</option>
|
</select>
|
||||||
<option value="4">Standby</option>
|
<input type="number" class="form-control" id="set-attendance-value-input" aria-label="Attendance Input" value="1.0">
|
||||||
<option selected value="5">Confirmed</option>
|
<div class="input-group-append">
|
||||||
</select>
|
<button class="btn btn-outline-secondary" type="button"
|
||||||
<div class="input-group-append">
|
onclick="setAttendanceForStatus(
|
||||||
<button class="btn btn-outline-secondary" type="button"
|
status=document.querySelector('#set-attendance-status-select').value,
|
||||||
onclick="changeSignupStatus(
|
value=document.querySelector('#set-attendance-value-input').value
|
||||||
from=document.querySelector('#change-status-from-status-select').value,
|
)"
|
||||||
to=document.querySelector('#change-status-to-status-select').value
|
>Go</button>
|
||||||
);"
|
</div>
|
||||||
>Go</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group col-lg-6">
|
<hr>
|
||||||
<label>Give Attendance</label>
|
<div class="row">
|
||||||
<div class="input-group">
|
<div class="col-xl-7">
|
||||||
<div class="input-group-prepend">
|
<div class="alert alert-primary d-flex justify-content-around" role="alert">
|
||||||
<label class="input-group-text" for="set-attendance-status-select">Give all</label>
|
<div><span id="total-signed-off" class="font-weight-bold">?</span> Signed Off</div>
|
||||||
|
<div><span id="total-backup" class="font-weight-bold">?</span> Backup</div>
|
||||||
|
<div><span id="total-signed-up" class="font-weight-bold">?</span> Signed Up</div>
|
||||||
|
<div><span id="total-standby" class="font-weight-bold">?</span> Standby</div>
|
||||||
|
<div><span id="total-confirmed" class="font-weight-bold">?</span> Confirmed</div>
|
||||||
</div>
|
</div>
|
||||||
<select class="custom-select" id="set-attendance-status-select">
|
</div>
|
||||||
<option value="1">Signed Off</option>
|
<div class="col-xl-5">
|
||||||
<option value="2">Backup</option>
|
<div class="alert alert-success d-flex justify-content-around" role="alert">
|
||||||
<option value="3">Signed Up</option>
|
<div class="font-weight-bolder">Confirmed</div>
|
||||||
<option value="4">Standby</option>
|
<div><span id="total-confirmed-tank" class="font-weight-bold">?</span> Tanks</div>
|
||||||
<option selected value="5">Confirmed</option>
|
<div><span id="total-confirmed-healer" class="font-weight-bold">?</span> Healers</div>
|
||||||
</select>
|
<div><span id="total-confirmed-damage" class="font-weight-bold">?</span> Damage</div>
|
||||||
<input type="number" class="form-control" id="set-attendance-value-input" aria-label="Attendance Input" value="1.0">
|
|
||||||
<div class="input-group-append">
|
|
||||||
<button class="btn btn-outline-secondary" type="button"
|
|
||||||
onclick="setAttendanceForStatus(
|
|
||||||
status=document.querySelector('#set-attendance-status-select').value,
|
|
||||||
value=document.querySelector('#set-attendance-value-input').value
|
|
||||||
)"
|
|
||||||
>Go</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<input class="btn btn-primary float-right" type="submit" name="submit" value="Save">
|
||||||
<hr>
|
</form>
|
||||||
<div class="row">
|
</div>
|
||||||
<div class="col-xl-7">
|
|
||||||
<div class="alert alert-primary d-flex justify-content-around" role="alert">
|
|
||||||
<div><span id="total-signed-off" class="font-weight-bold">?</span> Signed Off</div>
|
|
||||||
<div><span id="total-backup" class="font-weight-bold">?</span> Backup</div>
|
|
||||||
<div><span id="total-signed-up" class="font-weight-bold">?</span> Signed Up</div>
|
|
||||||
<div><span id="total-standby" class="font-weight-bold">?</span> Standby</div>
|
|
||||||
<div><span id="total-confirmed" class="font-weight-bold">?</span> Confirmed</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-xl-5">
|
|
||||||
<div class="alert alert-success d-flex justify-content-around" role="alert">
|
|
||||||
<div class="font-weight-bolder">Confirmed</div>
|
|
||||||
<div><span id="total-confirmed-tank" class="font-weight-bold">?</span> Tanks</div>
|
|
||||||
<div><span id="total-confirmed-healer" class="font-weight-bold">?</span> Healers</div>
|
|
||||||
<div><span id="total-confirmed-damage" class="font-weight-bold">?</span> Damage</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<input class="btn btn-primary float-right" type="submit" name="submit" value="Save">
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
@ -121,7 +124,8 @@
|
||||||
|
|
||||||
function trIsExtra(tr) {
|
function trIsExtra(tr) {
|
||||||
let characterSelect = tr.querySelector(".character-select");
|
let characterSelect = tr.querySelector(".character-select");
|
||||||
return characterSelect == null || characterSelect.value === ""; // the "--------" character has value=""
|
let guestName = tr.querySelector(".guest-name");
|
||||||
|
return (characterSelect == null || characterSelect.value === "") && (guestName == null || guestName.value === ""); // the "--------" character has value=""
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSelectTotals() {
|
function updateSelectTotals() {
|
||||||
|
@ -160,8 +164,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSelectTotals();
|
updateSelectTotals();
|
||||||
responseForm.querySelectorAll("select").forEach((select) => {
|
responseForm.querySelectorAll("select, input").forEach((element) => {
|
||||||
select.addEventListener("change", updateSelectTotals);
|
element.addEventListener("change", updateSelectTotals);
|
||||||
});
|
});
|
||||||
|
|
||||||
function changeSignupStatus(from, to) {
|
function changeSignupStatus(from, to) {
|
||||||
|
|
|
@ -6,9 +6,11 @@
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<form method="post">
|
<div class="container">
|
||||||
{% csrf_token %}
|
<form method="post">
|
||||||
<p>Are you sure you want to delete "{{ object }}"?</p>
|
{% csrf_token %}
|
||||||
<button type="submit" class="btn btn-danger">Confirm</button>
|
<p>Are you sure you want to delete "{{ object }}"?</p>
|
||||||
</form>
|
<button type="submit" class="btn btn-danger">Confirm</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
|
@ -6,5 +6,7 @@
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% crispy form %}
|
<div class="container">
|
||||||
|
{% crispy form %}
|
||||||
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
|
@ -6,132 +6,146 @@
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="card mb-1">
|
<div class="container">
|
||||||
|
<div class="card mb-1">
|
||||||
<h4 class="card-header d-flex">
|
<h4 class="card-header d-flex">
|
||||||
<span class="mr-auto">{{ raid.title }} <small class="text-muted">{{ raid.date }}</small></span>
|
<span class="mr-auto">{{ raid.title }} <small class="text-muted">{{ raid.date }}</small></span>
|
||||||
{% if perms.raids.change_raid %}<a class="btn btn-outline-primary btn-sm ml-2" role="button" href="{% url 'raid_change' raid.id %}">Edit</a>{% endif %}
|
{% if perms.raids.change_raid %}<a class="btn btn-outline-primary btn-sm ml-2" role="button" href="{% url 'raid_change' raid.id %}">Edit</a>{% endif %}
|
||||||
{% if perms.raids.delete_raid %}<a class="btn text-danger btn-link btn-sm ml-2" role="button" href="{% url 'raid_delete' raid.id %}">Delete</a>{% endif %}
|
{% if perms.raids.delete_raid %}<a class="btn text-danger btn-link btn-sm ml-2" role="button" href="{% url 'raid_delete' raid.id %}">Delete</a>{% endif %}
|
||||||
</h4>
|
</h4>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p class="card-text">{{ raid.description | urlize | linebreaksbr | default:"<em>No description</em>" }}</p>
|
<p class="card-text">{{ raid.description | urlize | linebreaksbr | default:"<em>No description</em>" }}</p>
|
||||||
{% if raid.is_optional %}
|
{% if raid.is_optional %}
|
||||||
<p class="card-text mb-0"><small class="text-muted">This raid is optional.</small></p>
|
<p class="card-text mb-0"><small class="text-muted">This raid is optional.</small></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p class="card-text"><small class="text-muted">Response deadline: {{ raid.response_deadline }}.</small></p>
|
<p class="card-text"><small class="text-muted">Response deadline: {{ raid.response_deadline }}.</small></p>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% if response_form %}
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body pb-0">
|
|
||||||
<form class="raid-response-form" method="post" action="{% url 'raid_detail' raid.id %}">
|
|
||||||
{% crispy response_form %}
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% if response_form %}
|
||||||
<div class="mb-4"></div>
|
<div class="card">
|
||||||
|
<div class="card-body pb-0">
|
||||||
<div class="modal fade" id="exportModal" tabindex="-1" role="dialog" aria-labelledby="exportModalLabel" aria-hidden="true">
|
<form class="raid-response-form" method="post" action="{% url 'raid_detail' raid.id %}">
|
||||||
<div class="modal-dialog" role="document">
|
{% crispy response_form %}
|
||||||
<div class="modal-content">
|
</form>
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title" id="exportModalLabel">Export</h5>
|
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
</div>
|
||||||
<p>Export list of characters for automatic invitation using <a href="https://www.curseforge.com/wow/addons/exorsus-raid-tools">Exorsus Raid Tools</a>.</p>
|
{% endif %}
|
||||||
<div class="form-group">
|
{% if guest_response_form %}
|
||||||
<label for="characters-textinput" class="col-form-label">Characters:</label>
|
<div class="mb-2"></div>
|
||||||
<textarea readonly class="form-control" id="characters-textinput" onclick="this.select();"></textarea>
|
<div class="card">
|
||||||
|
<h6 class="card-header">Add Guest</h6>
|
||||||
|
<div class="card-body pt-2 pb-0">
|
||||||
|
<form class="guest-raid-response-form" method="post" action="{% url 'raid_detail' raid.id %}">
|
||||||
|
{% crispy guest_response_form %}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="mb-4"></div>
|
||||||
|
|
||||||
|
<div class="modal fade" id="exportModal" tabindex="-1" role="dialog" aria-labelledby="exportModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="exportModalLabel">Export</h5>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>Export list of characters for automatic invitation using <a href="https://www.curseforge.com/wow/addons/exorsus-raid-tools">Exorsus Raid Tools</a>.</p>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="characters-textinput" class="col-form-label">Characters:</label>
|
||||||
|
<textarea readonly class="form-control" id="characters-textinput" onclick="this.select();"></textarea>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{% for status, status_responses in raid_responses.items %}
|
{% for status, status_responses in raid_responses.items %}
|
||||||
{% regroup status_responses by group as group_responses_list %}
|
{% regroup status_responses by group as group_responses_list %}
|
||||||
{% for group, group_responses in group_responses_list %}
|
{% for group, group_responses in group_responses_list %}
|
||||||
<div class="card mb-2 response-container" data-response-container-status="{{ status }}" data-response-container-group="{{ group }}">
|
<div class="card mb-2 response-container" data-response-container-status="{{ status }}" data-response-container-group="{{ group }}">
|
||||||
<h6 class="card-header d-flex response-status-{{ status }}-bg">
|
<h6 class="card-header d-flex response-status-{{ status }}-bg">
|
||||||
<span class="mr-auto">
|
<span class="mr-auto">
|
||||||
{{ status.label }}{% if group_responses_list|length > 1 %}: {{ group_responses.0.get_group_display }}{% endif %} ({{ group_responses | length }})
|
{{ status.label }}{% if group_responses_list|length > 1 %}: {{ group_responses.0.get_group_display }}{% endif %} ({{ group_responses | length }})
|
||||||
</span>
|
</span>
|
||||||
<span type="button" class="badge badge-dark" data-toggle="modal" data-target="#exportModal">Export</span>
|
<span type="button" class="badge badge-dark" data-toggle="modal" data-target="#exportModal">Export</span>
|
||||||
</h6>
|
</h6>
|
||||||
<div class="card-body mb-n4">
|
<div class="card-body mb-n4">
|
||||||
{% regroup group_responses by get_role_display as role_responses_list %}
|
{% regroup group_responses by get_role_display as role_responses_list %}
|
||||||
{% for role, role_responses in role_responses_list %}
|
{% for role, role_responses in role_responses_list %}
|
||||||
{% if role is not None %}
|
{% if role is not None %}
|
||||||
<h6 class="card-title border-bottom pb-2">{{ role }} ({{ role_responses | length }})</h6>
|
<h6 class="card-title border-bottom pb-2">{{ role }} ({{ role_responses | length }})</h6>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="d-flex flex-wrap">
|
<div class="d-flex flex-wrap">
|
||||||
{% regroup role_responses by character.klass as class_responses_list %}
|
{% regroup role_responses by character.klass as class_responses_list %}
|
||||||
{% for class, class_responses in class_responses_list %}
|
{% for class, class_responses in class_responses_list %}
|
||||||
<div class="d-flex flex-column mr-2 mb-4">
|
<div class="d-flex flex-column mr-2 mb-4">
|
||||||
{% regroup class_responses by character.user.rank as rank_responses_list %}
|
{% regroup class_responses by character.user.rank as rank_responses_list %}
|
||||||
{% for rank, rank_responses in rank_responses_list %}
|
{% for rank, rank_responses in rank_responses_list %}
|
||||||
<!-- TODO: Uncomment to show user ranks in raid signup
|
<!-- TODO: Uncomment to show user ranks in raid signup
|
||||||
<div class="d-flex justify-content-center align-items-center">
|
<div class="d-flex justify-content-center align-items-center">
|
||||||
<hr class="flex-grow-1 my-0">
|
<hr class="flex-grow-1 my-0">
|
||||||
<div class="mx-2 my-0 font-weight-light text-muted small">{{ rank }}</div>
|
<div class="mx-2 my-0 font-weight-light text-muted small">{{ rank }}</div>
|
||||||
<hr class="flex-grow-1 my-0">
|
<hr class="flex-grow-1 my-0">
|
||||||
</div>
|
|
||||||
-->
|
|
||||||
{% for response in rank_responses %}
|
|
||||||
<div class="alert response-character-alert mb-1 alert-class-{{ response.character.klass }}" role="alert" data-response-id="{{ response.id }}"
|
|
||||||
{% if response.role is not None %} data-response-role="{{ response.role }}"{% endif %}>
|
|
||||||
{% if response.character != response.character.user.main %}
|
|
||||||
<abbr title="{{ response.character.user.main }}">{{ response.character.name }}</abbr>
|
|
||||||
{% else %}
|
|
||||||
{{ response.character.name }}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
-->
|
||||||
|
{% for response in rank_responses %}
|
||||||
|
<div class="alert response-character-alert mb-1 alert-class-{{ response.character_klass }} text-truncate" role="alert" data-response-id="{{ response.id }}"
|
||||||
|
{% if response.role is not None %} data-response-role="{{ response.role }}"{% endif %}>
|
||||||
|
{% if response.is_guest %}
|
||||||
|
<span class="float-left">⇌</span>{{ response.character_name }}
|
||||||
|
{% elif response.character != response.character.user.main %}
|
||||||
|
<abbr title="{{ response.character.user.main }}">{{ response.character_name }}</abbr>
|
||||||
|
{% else %}
|
||||||
|
{{ response.character_name }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% empty %}
|
|
||||||
{% if perms.raids.change_raid %}
|
|
||||||
<div class="card mb-2 response-container" style="opacity: 0.4" data-response-container-status="{{ status }}" data-response-container-group="1">
|
|
||||||
<h6 class="card-header d-flex response-status-{{ status }}-bg">
|
|
||||||
<span class="mr-auto">{{ status.label }}</span>
|
|
||||||
</h6>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<div id="comments" class="card mt-4 mb-4">
|
|
||||||
<h6 class="card-header">Comments</h6>
|
|
||||||
<div class="card-body">
|
|
||||||
{% for comment in raid.comments.all %}
|
|
||||||
<div class="card mb-2">
|
|
||||||
<div class="card-body py-3">
|
|
||||||
<h6 class="card-title mb-1"><strong>{{ comment.user.main.name }}</strong> <small class="card-subtitle text-muted">· {{ comment.date_created }}</small></h6>
|
|
||||||
<p class="card-text">
|
|
||||||
{{ comment.body | linebreaksbr }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<em>No comments.</em>
|
{% if perms.raids.change_raid %}
|
||||||
|
<div class="card mb-2 response-container" style="opacity: 0.4" data-response-container-status="{{ status }}" data-response-container-group="1">
|
||||||
|
<h6 class="card-header d-flex response-status-{{ status }}-bg">
|
||||||
|
<span class="mr-auto">{{ status.label }}</span>
|
||||||
|
</h6>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
{% if comment_form %}
|
<div id="comments" class="card mt-4 mb-4">
|
||||||
<hr class="my-4">
|
<h6 class="card-header">Comments</h6>
|
||||||
<h6>Add Comment</h6>
|
<div class="card-body">
|
||||||
<form class="form" method="post" action="{% url 'raid_detail' raid.id %}">
|
{% for comment in raid.comments.all %}
|
||||||
{% crispy comment_form %}
|
<div class="card mb-2">
|
||||||
</form>
|
<div class="card-body py-3">
|
||||||
{% endif %}
|
<h6 class="card-title mb-1"><strong>{{ comment.user.main.name }}</strong> <small class="card-subtitle text-muted">· {{ comment.date_created }}</small></h6>
|
||||||
|
<p class="card-text">
|
||||||
|
{{ comment.body | linebreaksbr }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% empty %}
|
||||||
|
<em>No comments.</em>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if comment_form %}
|
||||||
|
<hr class="my-4">
|
||||||
|
<h6>Add Comment</h6>
|
||||||
|
<form class="form" method="post" action="{% url 'raid_detail' raid.id %}">
|
||||||
|
{% crispy comment_form %}
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
|
@ -14,7 +14,7 @@ from django.views.generic.edit import BaseUpdateView
|
||||||
|
|
||||||
from drakul.base.views import MultiModelFormView
|
from drakul.base.views import MultiModelFormView
|
||||||
from .forms import RaidResponseForm, RaidCommentForm, RaidForm, RaidResponseFormSetHelper, RaidResponseFormSet, \
|
from .forms import RaidResponseForm, RaidCommentForm, RaidForm, RaidResponseFormSetHelper, RaidResponseFormSet, \
|
||||||
RaidResponseChangeForm
|
RaidResponseChangeForm, GuestRaidResponseForm
|
||||||
from .models import Raid, RaidResponse, InstanceReset
|
from .models import Raid, RaidResponse, InstanceReset
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
@ -117,6 +117,8 @@ class RaidDetailView(SingleObjectMixin, MultiModelFormView):
|
||||||
}
|
}
|
||||||
if self.object.response_deadline > timezone.now():
|
if self.object.response_deadline > timezone.now():
|
||||||
classes["response_form"] = RaidResponseForm
|
classes["response_form"] = RaidResponseForm
|
||||||
|
if self.request.user.has_perm("raids.change_raid"):
|
||||||
|
classes["guest_response_form"] = GuestRaidResponseForm
|
||||||
return classes
|
return classes
|
||||||
|
|
||||||
def get_response_form_instance(self):
|
def get_response_form_instance(self):
|
||||||
|
@ -129,6 +131,10 @@ class RaidDetailView(SingleObjectMixin, MultiModelFormView):
|
||||||
form.instance.raid = self.object
|
form.instance.raid = self.object
|
||||||
form.save()
|
form.save()
|
||||||
|
|
||||||
|
def guest_response_form_valid(self, form):
|
||||||
|
form.instance.raid = self.object
|
||||||
|
form.save()
|
||||||
|
|
||||||
def comment_form_valid(self, form):
|
def comment_form_valid(self, form):
|
||||||
form.instance.raid = self.object
|
form.instance.raid = self.object
|
||||||
form.instance.user = self.request.user
|
form.instance.user = self.request.user
|
||||||
|
@ -138,12 +144,19 @@ class RaidDetailView(SingleObjectMixin, MultiModelFormView):
|
||||||
kwargs["user"] = self.request.user
|
kwargs["user"] = self.request.user
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
|
def get_guest_response_form_kwargs(self, kwargs):
|
||||||
|
kwargs["prefix"] = "guest"
|
||||||
|
return kwargs
|
||||||
|
|
||||||
def get_comment_form_success_url(self):
|
def get_comment_form_success_url(self):
|
||||||
return reverse("raid_detail", kwargs={"pk": self.object.pk}) + "#comments"
|
return reverse("raid_detail", kwargs={"pk": self.object.pk}) + "#comments"
|
||||||
|
|
||||||
def get_response_form_success_url(self):
|
def get_response_form_success_url(self):
|
||||||
return reverse("raid_detail", kwargs={"pk": self.object.pk})
|
return reverse("raid_detail", kwargs={"pk": self.object.pk})
|
||||||
|
|
||||||
|
def get_guest_response_form_success_url(self):
|
||||||
|
return reverse("raid_detail", kwargs={"pk": self.object.pk})
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
return super().get(request, *args, **kwargs)
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
|
@ -23,6 +23,7 @@ class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('users', '0002_create_default_ranks'),
|
('users', '0002_create_default_ranks'),
|
||||||
|
('raids', '0004_raid_is_optional'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
|
|
@ -6,29 +6,31 @@
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>Attendance</h2>
|
<div class="container">
|
||||||
<table class="table table-dark table-bordered text-center">
|
<h2>Attendance</h2>
|
||||||
<thead class="thead-light">
|
<table class="table table-dark table-bordered text-center">
|
||||||
<tr>
|
<thead class="thead-light">
|
||||||
{% for raid in raid_list %}
|
|
||||||
<th><a class="text-reset" href="{% url 'raid_detail' raid.id %}">{{ raid.date | date:"d/m"}}</a></th>
|
|
||||||
{% endfor %}
|
|
||||||
<th>Avg</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody class="table-sm">
|
|
||||||
{% for user, user_responses in attendance_matrix.items %}
|
|
||||||
<tr>
|
<tr>
|
||||||
{% for response in user_responses.values %}
|
{% for raid in raid_list %}
|
||||||
<td class="response-status-{{ response.status | default_if_none:'no-color' }}-bg">
|
<th><a class="text-reset" href="{% url 'raid_detail' raid.id %}">{{ raid.date | date:"d/m"}}</a></th>
|
||||||
{% attendance_cell response %}
|
|
||||||
</td>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<td class="font-weight-bold" style="color: {% avg_attendance_color user.avg_attendance %}">{{ user.avg_attendance | floatformat:2 }}</td>
|
<th>Avg</th>
|
||||||
<td class="text-left color-class-{{ user.main.klass }}">{{ user.main }}</td>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
</thead>
|
||||||
</tbody>
|
<tbody class="table-sm">
|
||||||
</table>
|
{% for user, user_responses in attendance_matrix.items %}
|
||||||
|
<tr>
|
||||||
|
{% for response in user_responses.values %}
|
||||||
|
<td class="response-status-{{ response.status | default_if_none:'no-color' }}-bg">
|
||||||
|
{% attendance_cell response %}
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
|
<td class="font-weight-bold" style="color: {% avg_attendance_color user.avg_attendance %}">{{ user.avg_attendance | floatformat:2 }}</td>
|
||||||
|
<td class="text-left color-class-{{ user.main.klass }}">{{ user.main }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -38,7 +38,10 @@ class AttendanceView(TemplateView):
|
||||||
context["raid_list"] = list(reversed(raids))
|
context["raid_list"] = list(reversed(raids))
|
||||||
|
|
||||||
context["attendance_matrix"] = {user: {raid: None for raid in reversed(raids)} for user in users}
|
context["attendance_matrix"] = {user: {raid: None for raid in reversed(raids)} for user in users}
|
||||||
for response in RaidResponse.objects.filter(raid__in=raids).select_related("raid", "character__user"):
|
for response in RaidResponse.objects.filter(
|
||||||
|
character__isnull=False,
|
||||||
|
raid__in=raids
|
||||||
|
).select_related("raid", "character__user"):
|
||||||
with suppress(KeyError): # KeyError means user was in previous raid but is now inactive
|
with suppress(KeyError): # KeyError means user was in previous raid but is now inactive
|
||||||
context["attendance_matrix"][response.character.user][response.raid] = response
|
context["attendance_matrix"][response.character.user][response.raid] = response
|
||||||
return context
|
return context
|
||||||
|
|
Loading…
Reference in a new issue