diff --git a/drakul/base/settings/common.py b/drakul/base/settings/common.py
index 6cc3556..884bda6 100755
--- a/drakul/base/settings/common.py
+++ b/drakul/base/settings/common.py
@@ -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
diff --git a/drakul/base/templates/base.html b/drakul/base/templates/base.html
index 6b08312..2c399f0 100644
--- a/drakul/base/templates/base.html
+++ b/drakul/base/templates/base.html
@@ -23,7 +23,7 @@
{% if user.is_authenticated %}
diff --git a/drakul/raids/migrations/0001_initial.py b/drakul/raids/migrations/0001_initial.py
index 29baeb3..46158e3 100644
--- a/drakul/raids/migrations/0001_initial.py
+++ b/drakul/raids/migrations/0001_initial.py
@@ -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'],
diff --git a/drakul/raids/migrations/0002_auto_20191025_1323.py b/drakul/raids/migrations/0002_auto_20191119_0310.py
similarity index 96%
rename from drakul/raids/migrations/0002_auto_20191025_1323.py
rename to drakul/raids/migrations/0002_auto_20191119_0310.py
index 312fce2..6130fda 100644
--- a/drakul/raids/migrations/0002_auto_20191025_1323.py
+++ b/drakul/raids/migrations/0002_auto_20191119_0310.py
@@ -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 = [
diff --git a/drakul/raids/models.py b/drakul/raids/models.py
index 7039987..45c7d93 100755
--- a/drakul/raids/models.py
+++ b/drakul/raids/models.py
@@ -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?
diff --git a/drakul/users/admin.py b/drakul/users/admin.py
index 1e0c4d3..6666ed7 100755
--- a/drakul/users/admin.py
+++ b/drakul/users/admin.py
@@ -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"]
diff --git a/drakul/users/migrations/0001_initial.py b/drakul/users/migrations/0001_initial.py
index 7ad08ca..937cadf 100644
--- a/drakul/users/migrations/0001_initial.py
+++ b/drakul/users/migrations/0001_initial.py
@@ -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',
diff --git a/drakul/users/migrations/0002_create_default_ranks.py b/drakul/users/migrations/0002_create_default_ranks.py
new file mode 100644
index 0000000..cf9bf0a
--- /dev/null
+++ b/drakul/users/migrations/0002_create_default_ranks.py
@@ -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),
+ ]
diff --git a/drakul/users/migrations/0002_create_admin.py b/drakul/users/migrations/0003_create_admin.py
similarity index 81%
rename from drakul/users/migrations/0002_create_admin.py
rename to drakul/users/migrations/0003_create_admin.py
index 2d19dbb..efc784d 100644
--- a/drakul/users/migrations/0002_create_admin.py
+++ b/drakul/users/migrations/0003_create_admin.py
@@ -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 = [
diff --git a/drakul/users/models.py b/drakul/users/models.py
index 29ba2dd..ae0a5d0 100644
--- a/drakul/users/models.py
+++ b/drakul/users/models.py
@@ -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(
diff --git a/drakul/users/templates/users/user_list.html b/drakul/users/templates/users/user_list.html
index 36369c3..2b67632 100644
--- a/drakul/users/templates/users/user_list.html
+++ b/drakul/users/templates/users/user_list.html
@@ -1,15 +1,34 @@
{% extends "base.html" %}
+{% block title %}Users{% endblock %}
+
+
{% block content %}
Users
-
- {% for user in user_list %}
- - {{ user.username }} {{ user.avg_attendance | floatformat:2 }}
-
- {% for character in user.characters.all %}
- - {{ character.name }} {{ character.get_klass_display }} {{ character.get_role_display }}
+
+
+
+
+
+ Character Name |
+ Rank |
+ Class |
+ Role |
+ Avg Attendance (Month, Total) |
+
+
+
+ {% for user in user_list %}
+
+ {{ user.main.name }} |
+ {{ user.rank }} |
+ {{ user.main.get_klass_display }} |
+ {{ user.main.get_role_display }} |
+ {{ user.avg_attendance_month | floatformat:2 }}, {{ user.avg_attendance_total | floatformat:2 }} |
+
{% endfor %}
-
- {% endfor %}
-
+
+
+
+
{% endblock %}
diff --git a/drakul/users/views.py b/drakul/users/views.py
index d327d7d..1adada2 100644
--- a/drakul/users/views.py
+++ b/drakul/users/views.py
@@ -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: