More dynamic forms and templates

This commit is contained in:
Ceferino Patino 2022-09-03 04:18:47 -05:00
commit e5549cfc24
12 changed files with 159 additions and 109 deletions

View file

@ -1,6 +1,19 @@
from django.contrib import admin
from django.http import HttpResponse
from django.contrib import admin, messages
from django.utils.translation import ngettext
from ..models import Activity, UserEntry
from ..utils import get_random_string
from ..models import Activity, Award, Quota, UserEntry
class QuotaInline(admin.TabularInline):
model = Quota
extra = 0
class AwardsInline(admin.TabularInline):
model = Award
extra = 0
@admin.register(Activity)
@ -8,32 +21,48 @@ class ActivityAdmin(admin.ModelAdmin):
fieldsets = [
(None, {'fields': ['name']}),
('Activity Information', {
'fields': ['quota', 'time']}),
'fields': ['default_quota', 'time', 'password']}),
]
list_display = ['name', 'time', 'quota', 'teacher', 'password', 'hawk_signups',
'great_grey_signups', 'snowy_signups', 'eagle_signups']
list_display = ['name', 'time', 'teacher', 'password',
'default_quota', 'hawk_signups', 'great_grey_signups', 'snowy_signups', 'eagle_signups']
@admin.display(description='Hawk Signups')
inlines = [QuotaInline, AwardsInline]
actions = ['generate_passwords']
@admin.display(description='Hawk')
def hawk_signups(self, obj):
if (obj.time == None):
return "- | -"
return str(UserEntry.hawk.filterActivity1(obj).count()) + " | " + str((UserEntry.hawk.filterActivity2(obj).count(), "-")[obj.time == 60])
return str(UserEntry.objects.filter(house__name='Hawk', activity1=obj).count()) + ' | ' + str((UserEntry.objects.filter(house__name='Hawk', activity2=obj).count(), '-')[obj.time == 60])
@admin.display(description='Great Grey Signups')
@admin.display(description='Great Grey')
def great_grey_signups(self, obj):
if (obj.time == None):
return "- | -"
return str(UserEntry.great_grey.filterActivity1(obj).count()) + " | " + str((UserEntry.great_grey.filterActivity2(obj).count(), "-")[obj.time == 60])
return str(UserEntry.objects.filter(house__name='Great Grey', activity1=obj).count()) + ' | ' + str((UserEntry.objects.filter(house__name='Great Grey', activity2=obj).count(), '-')[obj.time == 60])
@admin.display(description='Snowy Signups')
@admin.display(description='Snowy')
def snowy_signups(self, obj):
if (obj.time == None):
return "- | -"
return str(UserEntry.snowy.filterActivity1(obj).count()) + " | " + str((UserEntry.snowy.filterActivity2(obj).count(), "-")[obj.time == 60])
return str(UserEntry.objects.filter(house__name='Snowy', activity1=obj).count()) + ' | ' + str((UserEntry.objects.filter(house__name='Snowy', activity2=obj).count(), '-')[obj.time == 60])
@admin.display(description='Eagle Signups')
@admin.display(description='Eagle')
def eagle_signups(self, obj):
if (obj.time == None):
return "- | -"
return str(UserEntry.eagle.filterActivity1(obj).count()) + " | " + str((UserEntry.eagle.filterActivity2(obj).count(), "-")[obj.time == 60])
return str(UserEntry.objects.filter(house__name='Eagle', activity1=obj).count()) + ' | ' + str((UserEntry.objects.filter(house__name='Eagle', activity2=obj).count(), '-')[obj.time == 60])
@admin.action(description='Generate passwords for selected activities')
def generate_passwords(self, request, queryset):
for item in queryset:
item.password = get_random_string(8)
item.save()
self.message_user(request, ngettext(
'Generated password for %d activity.',
'Generated passwords for %d activities.',
queryset.count(),
) % queryset.count(), messages.SUCCESS)

View file

@ -1,31 +1,17 @@
from django.contrib import admin, messages
from django.utils.translation import ngettext
from django.db.models import Sum, F
from django.db.models.functions import Coalesce
from django.contrib import admin
from ..models import House, PointsEntry
from ..models import House
@admin.register(House)
class HouseAdmin(admin.ModelAdmin):
list_display = ('name', 'points', 'current_points', 'total_points')
list_display = ('name', 'points')
actions = ['finalize_points']
@admin.display(description='Current Housewars Points')
def current_points(self, obj):
return obj.current_points
# @admin.display(description='Current Housewars Points')
# def current_points(self, obj):
# return obj.current_points
@admin.display(description='Total Points')
def total_points(self, obj):
return obj.total_points
@admin.action(description='Finalize selected house points')
def finalize_points(self, request, queryset):
updated = queryset.update(
points=queryset.aggregate(points__sum=Coalesce(Sum('pointsentry__points'), 0)).get('points__sum') + F('points'))
PointsEntry.objects.filter(pk__in=queryset).delete()
self.message_user(request, ngettext(
'Finalized house points for %d house.',
'Finalized house points for %d houses.',
updated,
) % updated, messages.SUCCESS)
# @admin.display(description='Total Points')
# def total_points(self, obj):
# return obj.total_points

View file

@ -1,8 +1,32 @@
from django.contrib import admin
from django.db.models import F
from ..models import PointsEntry
class ValidatedPasswordFilter(admin.SimpleListFilter):
title = 'valid password'
parameter_name = 'valid'
def lookups(self, request, model_admin):
return (('True', True), ('False', False))
def queryset(self, request, queryset):
if (self.value() == 'True'):
return queryset.filter(password=F('activity__password'))
elif (self.value() == 'False'):
return queryset.exclude(password=F('activity__password'))
else:
return queryset
@admin.register(PointsEntry)
class PointsEntryAdmin(admin.ModelAdmin):
list_display = ('activity', 'name', 'house', 'points')
list_display = ('activity', 'award', 'house', 'validated')
list_filter = ('activity', ValidatedPasswordFilter)
@admin.display(description='Validated', boolean=True)
def validated(self, obj):
return obj.password == obj.activity.password

View file

@ -1,6 +1,9 @@
from django.contrib import admin
from django.db.models import F, Value
from django.db.models.functions import Concat
from django.http import HttpResponse
import csv
from ..models import UserEntry
@ -8,17 +11,9 @@ from ..models import UserEntry
class MentorListFilter(admin.SimpleListFilter):
title = 'mentor teacher'
# Parameter for the filter that will be used in the URL query.
parameter_name = 'mentor'
def lookups(self, request, model_admin):
"""
Returns a list of tuples. The first element in each
tuple is the coded value for the option that will
appear in the URL query. The second element is the
human-readable name for the option that will appear
in the right sidebar.
"""
qs = model_admin.get_queryset(request).annotate(full_name=Concat(F(
'house__teacher__first_name'), Value(' '), F('house__teacher__last_name'))).values_list('house__teacher', 'full_name').distinct()
@ -31,7 +26,7 @@ class MentorListFilter(admin.SimpleListFilter):
return queryset.filter(house__teacher=self.value(), grade=F('house__teacher__grade'))
@ admin.register(UserEntry)
@admin.register(UserEntry)
class UserEntryAdmin(admin.ModelAdmin):
fieldsets = [
('Personal Information', {'fields': [
@ -46,20 +41,49 @@ class UserEntryAdmin(admin.ModelAdmin):
list_filter = ['house', 'grade', MentorListFilter, 'activity1',
'activity1__teacher', 'activity2', 'activity2__teacher']
@ admin.display(description='Activity 1 Teacher')
actions = ['export_to_csv']
@admin.display(description='Activity 1 Teacher')
def activity1_teacher(self, obj):
if (obj.activity1 == None):
return None
return obj.activity1.teacher
@ admin.display(description='Mentor Teacher')
@admin.display(description='Mentor Teacher')
def mentor_teacher(self, obj):
return obj.mentor
@ admin.display(description='Activity 2 Teacher')
@admin.display(description='Activity 2 Teacher')
def activity2_teacher(self, obj):
if (obj.activity2 == None):
return None
return obj.activity2.teacher
@admin.action(description='Export selected to csv')
def export_to_csv(self, request, queryset):
field_names = [field.name for field in queryset.model._meta.fields]
response = HttpResponse(content_type='text/csv', headers={
'Content-Disposition': 'attachment; filename="export.csv"'},)
writer = csv.writer(response, delimiter=";")
# Write a first row with header information
writer.writerow(field_names)
# Write data rows
for row in queryset:
values = []
for field in field_names:
value = getattr(row, field)
if callable(value):
try:
value = value() or ''
except:
value = 'Error retrieving value'
if value is None:
value = ''
values.append(value)
writer.writerow(values)
return response

View file

@ -1,5 +1,5 @@
from .Activity import ActivityAdmin
from .House import HouseAdmin
from .Teacher import TeacherAdmin
from .UserEntry import UserEntryAdmin
from .PointsEntry import PointsEntryAdmin
from .House import House
from .PointsEntry import PointsEntry
from .Teacher import Teacher
from .UserEntry import UserEntry

View file

@ -1,6 +1,7 @@
from django.forms import (CharField, ChoiceField, EmailField, EmailInput, Form,
ModelChoiceField, ModelForm, NumberInput, Select,
Textarea, TextInput, ValidationError)
ModelChoiceField, ModelForm, Select,
TextInput, ValidationError)
from smart_selects.form_fields import ChainedModelChoiceField
from .models import Activity, House, PointsEntry, UserEntry
@ -32,6 +33,11 @@ class UserEntryForm(Form):
'placeholder': 'House'
}))
def clean(self):
email = self.cleaned_data['email']
if UserEntry.objects.filter(email=email).exists():
raise ValidationError("A signup with this email already exists")
class ActivityForm(Form):
activity1 = ModelChoiceField(Activity.objects.all(), widget=Select(attrs={
@ -69,30 +75,20 @@ class PointsEntryForm(ModelForm):
model = PointsEntry
fields = '__all__'
widgets = {
'name': TextInput(attrs={
'class': 'form-control',
'id': 'first-name',
'placeholder': 'Full Name'
}),
'activity': Select(attrs={
'class': "form-select",
'id': 'house',
'placeholder': 'Activity'
}),
'house': Select(attrs={
'class': "form-select",
'id': 'house',
'placeholder': 'House'
}),
'points': NumberInput(attrs={
'class': "form-control",
'id': 'last-name',
'placeholder': 'Number of Points'
}),
'comment': Textarea(attrs={
'password': TextInput(attrs={
'class': 'form-control',
'id': 'first-name',
'placeholder': 'Comments',
'style': 'height:10vh;'
'placeholder': 'Password',
'type': 'password'
})
}
def __init__(self, *args, **kwargs):
super(PointsEntryForm, self).__init__(*args, **kwargs)
self.fields['activity'].widget.attrs.update({'class': 'form-select'})
self.fields['award'].widget.attrs.update({'class': 'form-select'})

View file

@ -1,29 +0,0 @@
from django.db.models import Manager
class EntryManager(Manager):
def filterActivity1(self, act):
return self.filter(activity1=act)
def filterActivity2(self, act):
return self.filter(activity2=act)
class HawkEntryManager(EntryManager):
def get_queryset(self):
return super().get_queryset().filter(house__name='Hawk')
class EagleEntryManager(EntryManager):
def get_queryset(self):
return super().get_queryset().filter(house__name='Eagle')
class GreatGreyEntryManager(EntryManager):
def get_queryset(self):
return super().get_queryset().filter(house__name='Great Grey')
class SnowyEntryManager(EntryManager):
def get_queryset(self):
return super().get_queryset().filter(house__name='Snowy')

View file

@ -0,0 +1 @@
from .get_random_string import get_random_string

View file

@ -0,0 +1,9 @@
import random
import string
def get_random_string(length):
# choose from all lowercase letter
letters = string.ascii_lowercase
result_str = ''.join(random.choice(letters) for i in range(length))
return result_str

View file

@ -1,12 +1,13 @@
from django.contrib import messages
from django.db.models import Count, F, Q
from django.db.models import Count, F, Q, Subquery, OuterRef
from django.db.models.functions import Coalesce
from django.shortcuts import redirect
from django.urls import reverse
from django.views.generic import CreateView
from formtools.wizard.views import SessionWizardView
from .forms import ActivityForm, PointsEntryForm, UserEntryForm
from .models import Activity, PointsEntry, UserEntry
from .models import Activity, Quota, PointsEntry, UserEntry
FORMS = [("user", UserEntryForm),
("activity", ActivityForm)]
@ -37,10 +38,14 @@ class EntryCreateView(SessionWizardView):
if step == 'activity':
cd = self.storage.get_step_data('user')
filtered1 = Activity.objects.filter(time__isnull=False).annotate(count=Count(
'activity1', filter=Q(activity1__house=cd.get('user-house')))).filter(quota__gt=F('count'))
filtered2 = Activity.objects.filter(time__isnull=False).annotate(count=Count(
'activity2', filter=Q(activity2__house=cd.get('user-house')))).filter(quota__gt=F('count'), time=30)
quota = Quota.objects.filter(
house=cd.get('user-house'), activity=OuterRef('id')).values('quota')
filtered1 = Activity.objects.filter(time__isnull=False).annotate(final_quota=Coalesce(Subquery(quota[:1]), F('default_quota'))).annotate(count=Count(
'activity1', filter=Q(activity1__house=cd.get('user-house')))).filter(final_quota__gt=F('count'))
filtered2 = Activity.objects.filter(time__isnull=False).annotate(final_quota=Coalesce(Subquery(quota[:1]), F('default_quota'))).annotate(count=Count(
'activity1', filter=Q(activity1__house=cd.get('user-house')))).filter(final_quota__gt=F('count'), time=30)
form.fields['activity1'].queryset = filtered1
form.fields['activity2'].queryset = filtered2
@ -48,7 +53,7 @@ class EntryCreateView(SessionWizardView):
return form
def done(self, form_list, **kwargs):
UserEntry.objects.create(self.get_all_cleaned_data())
UserEntry.objects.create(**self.get_all_cleaned_data())
messages.success(self.request, 'Your entry has been submitted')
return redirect('housewars:signup')

View file

@ -2,6 +2,7 @@ asgiref==3.5.2
autopep8==1.6.0
Django==4.0.4
django-formtools==2.3
django-smart-selects==1.6.0
gunicorn==20.1.0
mysqlclient==2.1.1
pycodestyle==2.8.0

View file

@ -9,6 +9,10 @@
integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href='{% static "css/main.css" %}' />
<link rel="icon" type="image/x-icon" href='{% static "favicon.ico" %}'>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src=" {% static 'smart-selects/admin/js/chainedfk.js' %}"></script>
<script src="{% static 'smart-selects/admin/js/chainedm2m.js' %}"></script>
{% block title %}
<title>House Wars</title>