Compare commits
3 commits
f5e3550c28
...
9af691f8ca
Author | SHA1 | Date | |
---|---|---|---|
Casper V. Kristensen | 9af691f8ca | ||
Casper V. Kristensen | d5e65c8191 | ||
Casper V. Kristensen | c44aacec5a |
|
@ -155,3 +155,8 @@ SITE_ID = 1
|
|||
|
||||
# Crispy Forms
|
||||
CRISPY_TEMPLATE_PACK = "bootstrap4"
|
||||
|
||||
|
||||
# Drakul
|
||||
DEFAULT_ATTENDANCE_ATTENDING = 1.0
|
||||
DEFAULT_ATTENDANCE_NOT_ATTENDING = 0.0
|
||||
|
|
|
@ -1,13 +1,23 @@
|
|||
/* WoW class-coloured text */
|
||||
.class-druid {color: #ff7d0a;}
|
||||
.class-hunter {color: #abd473;}
|
||||
.class-mage {color: #69ccf0;}
|
||||
.class-paladin {color: #f58cba;}
|
||||
.class-priest {color: #ffffff;}
|
||||
.class-rogue {color: #fff569;}
|
||||
.class-shaman {color: #0070de;}
|
||||
.class-warlock {color: #9482c9;}
|
||||
.class-warrior {color: #c79c6e;}
|
||||
.color-druid, .color-class-1 {color: #ff7d0a;}
|
||||
.color-hunter, .color-class-2 {color: #abd473;}
|
||||
.color-mage, .color-class-3 {color: #69ccf0;}
|
||||
.color-paladin, .color-class-4 {color: #f58cba;}
|
||||
.color-priest, .color-class-5 {color: #ffffff;}
|
||||
.color-rogue, .color-class-6 {color: #fff569;}
|
||||
.color-shaman, .color-class-7 {color: #0070de;}
|
||||
.color-warlock, .color-class-8 {color: #9482c9;}
|
||||
.color-warrior, .color-class-9 {color: #c79c6e;}
|
||||
/* WoW class-coloured backgrounds */
|
||||
.bg-druid, .bg-class-1 {background-color: #ff7d0a; color: #fff;}
|
||||
.bg-hunter, .bg-class-2 {background-color: #abd473; color: #343a40;}
|
||||
.bg-mage, .bg-class-3 {background-color: #69ccf0; color: #fff;}
|
||||
.bg-paladin, .bg-class-4 {background-color: #f58cba; color: #fff;}
|
||||
.bg-priest, .bg-class-5 {background-color: #ffffff; color: #343a40;}
|
||||
.bg-rogue, .bg-class-6 {background-color: #fff569; color: #343a40;}
|
||||
.bg-shaman, .bg-class-7 {background-color: #0070de; color: #fff;}
|
||||
.bg-warlock, .bg-class-8 {background-color: #9482c9; color: #fff;}
|
||||
.bg-warrior, .bg-class-9 {background-color: #c79c6e; color: #fff;}
|
||||
|
||||
|
||||
/* WoW class-coloured buttons */
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<div class="collapse navbar-collapse" id="navbar-collapse">
|
||||
<div class="navbar-nav mr-auto">
|
||||
<a class="nav-item nav-link" href="{% url 'raid_calendar' %}">Raids</a>
|
||||
<!--<a class="nav-item nav-link" href="{% url 'user_list' %}">Users</a>-->
|
||||
<a class="nav-item nav-link" href="{% url 'user_list' %}">Users</a>
|
||||
<!--<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">DKP</a>-->
|
||||
</div>
|
||||
{% if user.is_authenticated %}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from crispy_forms.bootstrap import StrictButton
|
||||
from crispy_forms.helper import FormHelper
|
||||
from crispy_forms.layout import Submit, Layout, Column, Row, Field
|
||||
from django.forms import ModelForm, modelformset_factory, inlineformset_factory
|
||||
from django.forms import ModelForm, inlineformset_factory
|
||||
|
||||
from .models import RaidResponse, RaidComment, Raid
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Generated by Django 2.2.6 on 2019-10-25 13:23
|
||||
# Generated by Django 2.2.6 on 2019-11-19 03:10
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
@ -55,7 +55,7 @@ class Migration(migrations.Migration):
|
|||
('role', models.PositiveSmallIntegerField(blank=True, choices=[(1, 'Tank'), (2, 'Healer'), (3, 'Damage')], null=True)),
|
||||
('status', models.PositiveSmallIntegerField(choices=[(1, 'Signed Off'), (2, 'Signed Up'), (3, 'Stand By'), (4, 'Confirmed')], default=2)),
|
||||
('note', models.CharField(blank=True, max_length=100, null=True)),
|
||||
('attendance', models.DecimalField(blank=True, decimal_places=2, max_digits=3, null=True)),
|
||||
('attendance', models.DecimalField(blank=True, decimal_places=2, max_digits=3)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-status', 'role', 'character__klass', 'character__name'],
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Generated by Django 2.2.6 on 2019-10-25 13:23
|
||||
# Generated by Django 2.2.6 on 2019-11-19 03:10
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
@ -10,9 +10,9 @@ class Migration(migrations.Migration):
|
|||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('raids', '0001_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('users', '0001_initial'),
|
||||
('raids', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
|
@ -29,7 +29,6 @@ class Raid(models.Model):
|
|||
def save(self, *args, **kwargs):
|
||||
if self.response_deadline is None:
|
||||
self.response_deadline = self.date - timedelta(hours=24)
|
||||
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
|
@ -85,8 +84,7 @@ class RaidResponse(models.Model):
|
|||
attendance = models.DecimalField(
|
||||
max_digits=3,
|
||||
decimal_places=2,
|
||||
blank=True,
|
||||
null=True
|
||||
blank=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -102,6 +100,15 @@ class RaidResponse(models.Model):
|
|||
elif self.role is None:
|
||||
raise ValidationError({"role": "This field is required."})
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# Set attendance if it hasn't been set manually
|
||||
if self.attendance is None:
|
||||
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
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return super().__str__() # TODO?
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
<form class="responses-form table-borderless" method="post" action="{% url 'raid_change' raid.id %}">
|
||||
{% crispy raid_response_formset raid_response_formset_helper %}
|
||||
<hr>
|
||||
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
<label>Change Status</label>
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
from django.contrib import admin
|
||||
from django.contrib.auth.admin import UserAdmin as DjangoUserAdmin
|
||||
|
||||
from .models import User, Character
|
||||
from .models import User, Character, Rank
|
||||
|
||||
|
||||
@admin.register(Rank)
|
||||
class RankAdmin(admin.ModelAdmin):
|
||||
list_display = ["position", "name"]
|
||||
list_display_links = ["name"]
|
||||
|
||||
|
||||
class CharacterInline(admin.TabularInline):
|
||||
|
@ -22,13 +28,13 @@ class UserAdmin(DjangoUserAdmin):
|
|||
("Important dates", {
|
||||
"fields": ("last_login", "date_joined")
|
||||
}),
|
||||
("Character", {
|
||||
"fields": ("main",)
|
||||
("World of Warcraft", {
|
||||
"fields": ("rank", "main")
|
||||
}),
|
||||
)
|
||||
inlines = [CharacterInline]
|
||||
ordering = None # use default model ordering
|
||||
list_display = ["username", "main", "avg_attendance", "is_active", "is_staff", "is_superuser"]
|
||||
list_display = ["username", "rank", "main", "is_active", "is_staff", "is_superuser"]
|
||||
list_filter = ["is_active", "is_staff", "is_superuser"]
|
||||
search_fields = ["username", "main__name"]
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
# Generated by Django 2.2.6 on 2019-10-25 13:23
|
||||
# Generated by Django 2.2.6 on 2019-11-19 03:10
|
||||
|
||||
from django.conf import settings
|
||||
import django.contrib.auth.models
|
||||
import django.contrib.auth.validators
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
import drakul.users.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -33,12 +33,23 @@ class Migration(migrations.Migration):
|
|||
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-is_superuser', '-is_staff', 'username'],
|
||||
'ordering': ['rank', 'username'],
|
||||
},
|
||||
managers=[
|
||||
('objects', drakul.users.models.UserManager()),
|
||||
('objects', django.contrib.auth.models.UserManager()),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Rank',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('position', models.PositiveSmallIntegerField(unique=True)),
|
||||
('name', models.CharField(max_length=30, unique=True)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['position'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Character',
|
||||
fields=[
|
||||
|
@ -57,6 +68,11 @@ class Migration(migrations.Migration):
|
|||
name='main',
|
||||
field=models.OneToOneField(blank=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='users.Character'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='rank',
|
||||
field=models.ForeignKey(blank=True, on_delete=django.db.models.deletion.PROTECT, to='users.Rank'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='user_permissions',
|
||||
|
|
26
drakul/users/migrations/0002_create_default_ranks.py
Normal file
26
drakul/users/migrations/0002_create_default_ranks.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
# Generated by Django 2.2.6 on 2019-11-18 22:56
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def create_default_ranks(apps, schema_editor):
|
||||
"""
|
||||
https://docs.djangoproject.com/en/2.2/topics/migrations/#data-migrations
|
||||
"""
|
||||
Rank = apps.get_model("users", "Rank")
|
||||
Rank.objects.create(position=1, name="Guild Master")
|
||||
Rank.objects.create(position=2, name="Officer")
|
||||
Rank.objects.create(position=3, name="Veteran")
|
||||
Rank.objects.create(position=4, name="Member")
|
||||
Rank.objects.create(position=5, name="Initiate")
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(create_default_ranks),
|
||||
]
|
|
@ -10,17 +10,19 @@ def create_admin(apps, schema_editor):
|
|||
# The docs says to use 'User = apps.get_model("users", "User")', but when doing that
|
||||
# 'self.model.normalize_username()', which is called from create_superuser(), cannot be accessed..
|
||||
from drakul.users.models import User
|
||||
Rank = apps.get_model("users", "Rank")
|
||||
User.objects.create_superuser(
|
||||
username="admin",
|
||||
email="admin@localhost",
|
||||
password="admin"
|
||||
password="admin",
|
||||
rank_id=Rank.objects.first().id
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0001_initial'),
|
||||
('users', '0002_create_default_ranks'),
|
||||
]
|
||||
|
||||
operations = [
|
|
@ -1,21 +1,26 @@
|
|||
from django.contrib.auth.models import AbstractUser, UserManager as DjangoUserManager
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import RegexValidator, MinLengthValidator
|
||||
from django.db import models, transaction
|
||||
from django.db.models import Avg
|
||||
|
||||
|
||||
class UserManager(DjangoUserManager):
|
||||
def get_queryset(self):
|
||||
qs = super().get_queryset()
|
||||
return qs.annotate(
|
||||
avg_attendance=Avg("characters__raid_responses__attendance")
|
||||
class Rank(models.Model):
|
||||
position = models.PositiveSmallIntegerField(
|
||||
unique=True
|
||||
)
|
||||
name = models.CharField(
|
||||
max_length=30,
|
||||
unique=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ["position"]
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class User(AbstractUser):
|
||||
objects = UserManager()
|
||||
|
||||
first_name = None
|
||||
last_name = None
|
||||
|
||||
|
@ -26,8 +31,14 @@ class User(AbstractUser):
|
|||
blank=True
|
||||
)
|
||||
|
||||
rank = models.ForeignKey(
|
||||
Rank,
|
||||
on_delete=models.PROTECT,
|
||||
blank=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ["-is_superuser", "-is_staff", "username"]
|
||||
ordering = ["rank", "username"]
|
||||
|
||||
def clean(self):
|
||||
if hasattr(self, "main") and self.main.user != self:
|
||||
|
@ -35,6 +46,9 @@ class User(AbstractUser):
|
|||
|
||||
@transaction.atomic
|
||||
def save(self, *args, **kwargs):
|
||||
if not hasattr(self, "rank"):
|
||||
self.rank = Rank.objects.last()
|
||||
|
||||
if not hasattr(self, "main"):
|
||||
self.main = Character.objects.create(
|
||||
user=None,
|
||||
|
@ -43,15 +57,11 @@ class User(AbstractUser):
|
|||
role=Character.DAMAGE
|
||||
)
|
||||
self.main.save()
|
||||
|
||||
user = super().save(*args, **kwargs)
|
||||
self.main.user = self
|
||||
self.main.save()
|
||||
return user
|
||||
|
||||
def avg_attendance(self):
|
||||
return self.avg_attendance
|
||||
|
||||
|
||||
class Character(models.Model):
|
||||
user = models.ForeignKey(
|
||||
|
|
|
@ -1,15 +1,34 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Users{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<h2>Users</h2>
|
||||
<ul>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<table class="table">
|
||||
<thead class="table-borderless">
|
||||
<tr>
|
||||
<th scope="col">Character Name</th>
|
||||
<th scope="col">Rank</th>
|
||||
<th scope="col">Class</th>
|
||||
<th scope="col">Role</th>
|
||||
<th scope="col">Avg Attendance (Month, Total)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in user_list %}
|
||||
<li>{{ user.username }} {{ user.avg_attendance | floatformat:2 }}</li>
|
||||
<ul>
|
||||
{% for character in user.characters.all %}
|
||||
<li>{{ character.name }} {{ character.get_klass_display }} {{ character.get_role_display }}</li>
|
||||
<tr>
|
||||
<th scope="row">{{ user.main.name }}</th>
|
||||
<td>{{ user.rank }}</td>
|
||||
<td>{{ user.main.get_klass_display }}</td>
|
||||
<td>{{ user.main.get_role_display }}</td>
|
||||
<td>{{ user.avg_attendance_month | floatformat:2 }}, {{ user.avg_attendance_total | floatformat:2 }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from django.db.models import Avg, Q
|
||||
from django.utils import timezone
|
||||
from django.views.generic import ListView
|
||||
|
||||
from .models import User
|
||||
|
@ -5,7 +7,19 @@ from .models import User
|
|||
|
||||
class UserListView(ListView):
|
||||
def get_queryset(self):
|
||||
return User.objects.prefetch_related("characters").all()
|
||||
return User.objects.select_related("main").annotate(
|
||||
avg_attendance_total=Avg(
|
||||
"characters__raid_responses__attendance",
|
||||
filter=Q(characters__raid_responses__raid__date__lte=timezone.now()) # only count past raids
|
||||
),
|
||||
avg_attendance_month=Avg(
|
||||
"characters__raid_responses__attendance",
|
||||
filter=Q(
|
||||
characters__raid_responses__raid__date__lte=timezone.now(),
|
||||
characters__raid_responses__raid__date__gte=timezone.now() - timezone.timedelta(days=31)
|
||||
)
|
||||
)
|
||||
).order_by("rank", "main__name")
|
||||
|
||||
|
||||
# CharacterDetailView:
|
||||
|
|
Loading…
Reference in a new issue