Compare commits

..

6 commits

7 changed files with 109 additions and 6 deletions

View file

@ -38,7 +38,7 @@
</nav> </nav>
</header> </header>
<main class="flex-shrink-0" role="main">, <main class="flex-shrink-0" role="main">
<div class="container"> <div class="container">
{% block content %} {% block content %}
{% endblock content %} {% endblock content %}

View file

@ -63,7 +63,7 @@ class RaidResponseForm(ModelForm):
class RaidForm(ModelForm): class RaidForm(ModelForm):
class Meta: class Meta:
model = Raid model = Raid
fields = ["title", "description", "date", "response_deadline"] fields = ["title", "description", "is_optional", "date", "response_deadline"]
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -76,6 +76,7 @@ class RaidForm(ModelForm):
self.helper.layout = Layout( self.helper.layout = Layout(
Field("title"), Field("title"),
Field("description"), Field("description"),
Field("is_optional"),
Row( Row(
Column("date", css_class="form-group col-md-6"), Column("date", css_class="form-group col-md-6"),
Column("response_deadline", css_class="form-group col-md-6"), Column("response_deadline", css_class="form-group col-md-6"),

View file

@ -0,0 +1,18 @@
# Generated by Django 2.2.6 on 2019-11-20 22:36
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('raids', '0003_auto_20191119_0412'),
]
operations = [
migrations.AddField(
model_name='raid',
name='is_optional',
field=models.BooleanField(default=False, help_text='Designates whether this raid is optional. Optional raids are not included in attendance calculations.'),
),
]

View file

@ -23,6 +23,13 @@ class Raid(models.Model):
help_text="Defaults to 24 hours before date and time of raid if not set." help_text="Defaults to 24 hours before date and time of raid if not set."
) )
is_optional = models.BooleanField(
default=False,
help_text=(
"Designates whether this raid is optional. Optional raids are not included in attendance calculations."
)
)
class Meta: class Meta:
ordering = ["-date"] ordering = ["-date"]

View file

@ -14,8 +14,11 @@
{% 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 | linebreaksbr | default:"<em>No description</em>" }}</p> <p class="card-text">{{ raid.description | urlize | linebreaksbr | default:"<em>No description</em>" }}</p>
<p class="card-text"><small class="text-muted">Response deadline: {{ raid.response_deadline }}</small></p> {% if raid.is_optional %}
<p class="card-text mb-0"><small class="text-muted">This raid is optional.</small></p>
{% endif %}
<p class="card-text"><small class="text-muted">Response deadline: {{ raid.response_deadline }}.</small></p>
</div> </div>
</div> </div>
{% if response_form %} {% if response_form %}
@ -29,11 +32,34 @@
{% endif %} {% endif %}
<div class="mb-4"></div> <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">&times;</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>
{% regroup responses by get_status_display as status_responses_list %} {% regroup responses by get_status_display as status_responses_list %}
{% for status, status_responses in status_responses_list %} {% for status, status_responses in status_responses_list %}
{% with status=status|default_if_none:"No Response" %} {% with status=status|default_if_none:"No Response" %}
<div class="card mb-2"> <div class="card mb-2">
<h6 class="card-header response-status-{{ status | slugify }}-bg">{{ status }} ({{ status_responses | length }})</h6> <h6 class="card-header d-flex response-status-{{ status | slugify }}-bg">
<span class="mr-auto">{{ status }} ({{ status_responses | length }})</span>
<span type="button" class="badge badge-dark" data-toggle="modal" data-target="#exportModal">Export</span>
</h6>
<div class="card-body mb-n4"> <div class="card-body mb-n4">
{% regroup status_responses by get_role_display as role_responses_list %} {% regroup status_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 %}
@ -47,7 +73,6 @@
{% for response in class_responses %} {% for response in class_responses %}
<a class="btn response-character-button mb-1 btn-class-{{ response.character.klass }}" role="button" href="#"> <a class="btn response-character-button mb-1 btn-class-{{ response.character.klass }}" role="button" href="#">
{{ response.character.name }} {{ response.character.name }}
{% if response.note is not None %}&#128489;{% endif %}
</a> </a>
{% endfor %} {% endfor %}
</div> </div>
@ -87,3 +112,15 @@
</div> </div>
</div> </div>
{% endblock content %} {% endblock content %}
{% block scripts %}
<script>
const charactersTextinput = document.querySelector("#characters-textinput");
$("#exportModal").on("show.bs.modal", function (event) {
charactersTextinput.textContent = $(event.relatedTarget).closest(".card").find(".response-character-button").map(function () {
return this.text.trim();
}).get().join("\n");
})
</script>
{% endblock scripts %}

View file

@ -1,4 +1,5 @@
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.contrib.auth.models import UserManager as DjangoUserManager
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator, MinLengthValidator from django.core.validators import RegexValidator, MinLengthValidator
from django.db import models, transaction from django.db import models, transaction
@ -20,6 +21,11 @@ class Rank(models.Model):
return self.name return self.name
class UserManager(DjangoUserManager):
def get_by_natural_key(self, username):
return super().get_by_natural_key(self.model.normalize_username(username))
class User(AbstractUser): class User(AbstractUser):
first_name = None first_name = None
last_name = None last_name = None
@ -37,15 +43,25 @@ class User(AbstractUser):
blank=True blank=True
) )
objects = UserManager()
class Meta: class Meta:
ordering = ["rank", "username"] ordering = ["rank", "username"]
def clean(self): def clean(self):
self.username = self.normalize_username(self.username)
if not hasattr(self, "rank"): if not hasattr(self, "rank"):
self.rank = Rank.objects.last() self.rank = Rank.objects.last()
if hasattr(self, "main") and self.main.user != self: if hasattr(self, "main") and self.main.user != self:
raise ValidationError({"main": "Main character must be owned by user."}) raise ValidationError({"main": "Main character must be owned by user."})
@classmethod
def normalize_username(cls, username):
"""
Make username case-insensitive.
"""
return super().normalize_username(username).lower()
@transaction.atomic @transaction.atomic
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if not hasattr(self, "main"): if not hasattr(self, "main"):

24
migrate.py Normal file
View file

@ -0,0 +1,24 @@
import json
with open("data.json", "r") as file:
data = json.load(file)
for o in data:
print(o)
if o["model"] == "raids.raidresponse":
if o["fields"]["status"] >= 2: # SIGNED_UP
o["fields"]["attendance"] = 1.0
else:
o["fields"]["attendance"] = 0.0
if o["model"] == "users.user":
o["fields"]["rank"] = 3 # veteran
print("=============================================")
print("=============================================")
print("=============================================")
with open("new.json", "w") as file:
skip = ["contenttypes.contenttype", "auth.permission", "admin.logentry"]
thedump = [d for d in data if d["model"] not in skip]
for d in thedump:
print(d)
json.dump(thedump, file)