mirror of
https://gitlab.crans.org/bde/nk20
synced 2025-07-06 15:24:01 +02:00
Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
b2c6b0e85d |
@ -1,11 +0,0 @@
|
|||||||
# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class FamilyConfig(AppConfig):
|
|
||||||
name = 'family'
|
|
||||||
verbose_name = _('family')
|
|
@ -1,85 +0,0 @@
|
|||||||
# Generated by Django 4.2.21 on 2025-07-04 19:05
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
import django.utils.timezone
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='ChallengeCategory',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('name', models.CharField(max_length=255, unique=True, verbose_name='name')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'challenge category',
|
|
||||||
'verbose_name_plural': 'challenge categories',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Family',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('name', models.CharField(max_length=255, unique=True, verbose_name='name')),
|
|
||||||
('description', models.CharField(max_length=255, verbose_name='description')),
|
|
||||||
('score', models.PositiveIntegerField(verbose_name='score')),
|
|
||||||
('rank', models.PositiveIntegerField(verbose_name='rank')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Family',
|
|
||||||
'verbose_name_plural': 'Families',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Challenge',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('name', models.CharField(max_length=255, verbose_name='name')),
|
|
||||||
('description', models.CharField(max_length=255, verbose_name='description')),
|
|
||||||
('points', models.PositiveIntegerField(verbose_name='points')),
|
|
||||||
('obtained', models.PositiveIntegerField(verbose_name='obtained')),
|
|
||||||
('category', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='family.challengecategory', verbose_name='category')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'challenge',
|
|
||||||
'verbose_name_plural': 'challenges',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Achievement',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('obtained_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='obtained at')),
|
|
||||||
('challenge', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='family.challenge')),
|
|
||||||
('family', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='family.family', verbose_name='family')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'achievement',
|
|
||||||
'verbose_name_plural': 'achievements',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='FamilyMembership',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('year', models.PositiveIntegerField(default=2025, verbose_name='year')),
|
|
||||||
('family', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='members', to='family.family', verbose_name='family')),
|
|
||||||
('user', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='family_memberships', to=settings.AUTH_USER_MODEL, verbose_name='user')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'family membership',
|
|
||||||
'verbose_name_plural': 'family memberships',
|
|
||||||
'unique_together': {('user', 'year')},
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,202 +0,0 @@
|
|||||||
# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
from django.db import models, transaction
|
|
||||||
from django.utils import timezone
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
|
|
||||||
class Family(models.Model):
|
|
||||||
name = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
verbose_name=_('name'),
|
|
||||||
unique=True
|
|
||||||
)
|
|
||||||
|
|
||||||
description = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
verbose_name=_('description')
|
|
||||||
)
|
|
||||||
|
|
||||||
score = models.PositiveIntegerField(
|
|
||||||
verbose_name=_('score')
|
|
||||||
)
|
|
||||||
|
|
||||||
rank = models.PositiveIntegerField(
|
|
||||||
verbose_name=_('rank'),
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _('Family')
|
|
||||||
verbose_name_plural = _('Families')
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
|
|
||||||
class FamilyMembership(models.Model):
|
|
||||||
user = models.OneToOneField(
|
|
||||||
User,
|
|
||||||
on_delete=models.PROTECT,
|
|
||||||
related_name=_('family_memberships'),
|
|
||||||
verbose_name=_('user'),
|
|
||||||
)
|
|
||||||
|
|
||||||
family = models.ForeignKey(
|
|
||||||
Family,
|
|
||||||
on_delete=models.PROTECT,
|
|
||||||
related_name=_('members'),
|
|
||||||
verbose_name=_('family'),
|
|
||||||
)
|
|
||||||
|
|
||||||
year = models.PositiveIntegerField(
|
|
||||||
verbose_name=_('year'),
|
|
||||||
default=timezone.now().year,
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
unique_together = ('user', 'year',)
|
|
||||||
verbose_name = _('family membership')
|
|
||||||
verbose_name_plural = _('family memberships')
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return _('Family membership of {user} to {family}').format(user=self.user.username, family=self.family.name, )
|
|
||||||
|
|
||||||
|
|
||||||
class ChallengeCategory(models.Model):
|
|
||||||
name = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
verbose_name=_('name'),
|
|
||||||
unique=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _('challenge category')
|
|
||||||
verbose_name_plural = _('challenge categories')
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
|
|
||||||
class Challenge(models.Model):
|
|
||||||
name = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
verbose_name=_('name'),
|
|
||||||
)
|
|
||||||
|
|
||||||
description = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
verbose_name=_('description'),
|
|
||||||
)
|
|
||||||
|
|
||||||
points = models.PositiveIntegerField(
|
|
||||||
verbose_name=_('points'),
|
|
||||||
)
|
|
||||||
|
|
||||||
category = models.ForeignKey(
|
|
||||||
ChallengeCategory,
|
|
||||||
verbose_name=_('category'),
|
|
||||||
on_delete=models.PROTECT
|
|
||||||
)
|
|
||||||
|
|
||||||
obtained = models.PositiveIntegerField(
|
|
||||||
verbose_name=_('obtained')
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _('challenge')
|
|
||||||
verbose_name_plural = _('challenges')
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
|
|
||||||
class Achievement(models.Model):
|
|
||||||
challenge = models.ForeignKey(
|
|
||||||
Challenge,
|
|
||||||
on_delete=models.PROTECT,
|
|
||||||
|
|
||||||
)
|
|
||||||
family = models.ForeignKey(
|
|
||||||
Family,
|
|
||||||
on_delete=models.PROTECT,
|
|
||||||
verbose_name=_('family'),
|
|
||||||
)
|
|
||||||
|
|
||||||
obtained_at = models.DateTimeField(
|
|
||||||
verbose_name=_('obtained at'),
|
|
||||||
default=timezone.now,
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _('achievement')
|
|
||||||
verbose_name_plural = _('achievements')
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return _('Challenge {challenge} carried out by Family {family}').format(challenge=self.challenge.name, family=self.family.name, )
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def update_ranking(cls, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Update ranking when adding or removing points
|
|
||||||
"""
|
|
||||||
family_set = cls.objects.select_for_update().all().order_by("-score")
|
|
||||||
for i in range(family_set.count()):
|
|
||||||
if i == 0 or family_set[i].score != family_set[i - 1].score:
|
|
||||||
new_rank = i + 1
|
|
||||||
family = family_set[i]
|
|
||||||
family.rank = new_rank
|
|
||||||
family._force_save = True
|
|
||||||
family.save()
|
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
When saving, also grants points to the family
|
|
||||||
"""
|
|
||||||
self.family = Family.objects.select_for_update().get(pk=self.family_id)
|
|
||||||
self.challenge = Challenge.objects.select_for_update().get(pk=self.challenge_id)
|
|
||||||
challenge_points = self.challenge.points
|
|
||||||
is_new = self.pk is None
|
|
||||||
|
|
||||||
super.save(*args, **kwargs)
|
|
||||||
|
|
||||||
# Only grant points when getting a new achievement
|
|
||||||
if is_new:
|
|
||||||
self.family.refresh_from_db()
|
|
||||||
self.family.score += challenge_points
|
|
||||||
self.family._force_save = True
|
|
||||||
self.family.save()
|
|
||||||
|
|
||||||
self.challenge.refresh_from_db()
|
|
||||||
self.challenge.obtained += 1
|
|
||||||
self.challenge._force_save = True
|
|
||||||
self.challenge.save()
|
|
||||||
|
|
||||||
self.__class__.update_ranking()
|
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def delete(self, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
When deleting, also removes points from the family
|
|
||||||
"""
|
|
||||||
# Get the family and challenge before deletion
|
|
||||||
self.family = Family.objects.select_for_update().get(pk=self.family_id)
|
|
||||||
challenge_points = self.challenge.points
|
|
||||||
|
|
||||||
# Delete the achievement
|
|
||||||
super().delete(*args, **kwargs)
|
|
||||||
|
|
||||||
# Remove points from the family
|
|
||||||
self.family.refresh_from_db()
|
|
||||||
self.family.score -= challenge_points
|
|
||||||
self.family._force_save = True
|
|
||||||
self.family.save()
|
|
||||||
|
|
||||||
self.challenge.refresh_from_db()
|
|
||||||
self.challenge.obtained -= 1
|
|
||||||
self.challenge._force_save = True
|
|
||||||
self.challenge.save()
|
|
||||||
|
|
||||||
self.__class__.update_ranking()
|
|
@ -1,40 +0,0 @@
|
|||||||
# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
import django_tables2 as tables
|
|
||||||
from django_tables2 import A
|
|
||||||
|
|
||||||
from .models import Family, Challenge
|
|
||||||
|
|
||||||
|
|
||||||
class FamilyTable(tables.Table):
|
|
||||||
"""
|
|
||||||
List all families
|
|
||||||
"""
|
|
||||||
name = tables.LinkColumn(
|
|
||||||
"family:family_detail",
|
|
||||||
args=[A("pk")],
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
attrs = {
|
|
||||||
'class': 'table table-condensed table-striped table-hover'
|
|
||||||
}
|
|
||||||
model = Family
|
|
||||||
template_name = 'django_tables2/bootstrap4.html'
|
|
||||||
fields = ('name', 'score', 'rank',)
|
|
||||||
order_by = ('rank',)
|
|
||||||
|
|
||||||
|
|
||||||
class ChallengeTable(tables.Table):
|
|
||||||
"""
|
|
||||||
List all challenges
|
|
||||||
"""
|
|
||||||
class Meta:
|
|
||||||
attrs = {
|
|
||||||
'class': 'table table-condensed table-striped table-hover'
|
|
||||||
}
|
|
||||||
order_by = ('id',)
|
|
||||||
model = Challenge
|
|
||||||
template_name = 'django_tables2/bootstrap4.html'
|
|
||||||
fields = ('name', 'points', 'category',)
|
|
@ -1,30 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
{% comment %}
|
|
||||||
Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
|
|
||||||
SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
{% endcomment %}
|
|
||||||
{% load render_table from django_tables2 %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xl-12">
|
|
||||||
<div class="btn-group btn-group-toggle" style="width: 100%; padding: 0 0 2em 0">
|
|
||||||
<a href="{% url "family:family_list" %}" class="btn btn-sm btn-outline-primary">
|
|
||||||
{% trans "Families" %}
|
|
||||||
</a>
|
|
||||||
<a href="#" class="btn btn-sm btn-outline-primary active">
|
|
||||||
{% trans "Challenges" %}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<<div class="card bg-white mb-3">
|
|
||||||
<h3 class="card-header text-center">
|
|
||||||
{{ title }}
|
|
||||||
</h3>
|
|
||||||
{% render_table table %}
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
{% comment %}
|
|
||||||
Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
|
|
||||||
SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
{% endcomment %}
|
|
||||||
{% load render_table from django_tables2 %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xl-12">
|
|
||||||
<div class="btn-group btn-group-toggle" style="width: 100%; padding: 0 0 2em 0">
|
|
||||||
<a href="#" class="btn btn-sm btn-outline-primary active">
|
|
||||||
{% trans "Families" %}
|
|
||||||
</a>
|
|
||||||
<a href="{% url "family:challenge_list" %}" class="btn btn-sm btn-outline-primary">
|
|
||||||
{% trans "Challenges" %}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<<div class="card bg-white mb-3">
|
|
||||||
<h3 class="card-header text-center">
|
|
||||||
{{ title }}
|
|
||||||
</h3>
|
|
||||||
{% render_table table %}
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
|||||||
# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
from django.urls import path
|
|
||||||
|
|
||||||
from .views import FamilyListView, ChallengeListView
|
|
||||||
|
|
||||||
app_name = 'family'
|
|
||||||
urlpatterns = [
|
|
||||||
path('list/', FamilyListView.as_view(), name="family_list"),
|
|
||||||
path('challenge/list/', ChallengeListView.as_view(), name="challenge_list"),
|
|
||||||
]
|
|
@ -1,63 +0,0 @@
|
|||||||
# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
|
||||||
from django.views.generic import DetailView, UpdateView
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from django_tables2 import SingleTableView
|
|
||||||
from permission.views import ProtectQuerysetMixin, ProtectedCreateView
|
|
||||||
|
|
||||||
from .models import Family, Challenge
|
|
||||||
from .tables import FamilyTable, ChallengeTable
|
|
||||||
|
|
||||||
|
|
||||||
class FamilyCreateView(ProtectQuerysetMixin, ProtectedCreateView):
|
|
||||||
"""
|
|
||||||
Create family
|
|
||||||
"""
|
|
||||||
model = Family
|
|
||||||
extra_context = {"title": _('Create family')}
|
|
||||||
|
|
||||||
def get_sample_object(self):
|
|
||||||
return Family(
|
|
||||||
name="",
|
|
||||||
description="Sample family",
|
|
||||||
score=0,
|
|
||||||
rank=0,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class FamilyListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
|
||||||
"""
|
|
||||||
List existing Families
|
|
||||||
"""
|
|
||||||
model = Family
|
|
||||||
table_class = FamilyTable
|
|
||||||
extra_context = {"title": _('Families list')}
|
|
||||||
|
|
||||||
|
|
||||||
class FamilyDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
|
||||||
"""
|
|
||||||
Display details of a family
|
|
||||||
"""
|
|
||||||
model = Family
|
|
||||||
context_object_name = "family"
|
|
||||||
extra_context = {"title": _('Family detail')}
|
|
||||||
|
|
||||||
|
|
||||||
class FamilyUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
|
||||||
"""
|
|
||||||
Update the information of a family.
|
|
||||||
"""
|
|
||||||
model = Family
|
|
||||||
context_object_name = "family"
|
|
||||||
extra_context = {"title": _('Update family')}
|
|
||||||
|
|
||||||
|
|
||||||
class ChallengeListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
|
||||||
"""
|
|
||||||
List all challenges
|
|
||||||
"""
|
|
||||||
model = Challenge
|
|
||||||
table_class = ChallengeTable
|
|
||||||
extra_context = {"title": _('Challenges list')}
|
|
@ -5,7 +5,7 @@ from bootstrap_datepicker_plus.widgets import DatePickerInput
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.forms import CheckboxSelectMultiple
|
from django.forms import CheckboxSelectMultiple, RadioSelect
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from note.models import NoteSpecial, NoteUser
|
from note.models import NoteSpecial, NoteUser
|
||||||
from note_kfet.inputs import AmountInput, Autocomplete, ColorWidget
|
from note_kfet.inputs import AmountInput, Autocomplete, ColorWidget
|
||||||
@ -140,6 +140,19 @@ class WEIMembershipForm(forms.ModelForm):
|
|||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, wei=None, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
if 'bus' in self.fields:
|
||||||
|
if wei is not None:
|
||||||
|
self.fields['bus'].queryset = Bus.objects.filter(wei=wei)
|
||||||
|
else:
|
||||||
|
self.fields['bus'].queryset = Bus.objects.none()
|
||||||
|
if 'team' in self.fields:
|
||||||
|
if wei is not None:
|
||||||
|
self.fields['team'].queryset = BusTeam.objects.filter(bus__wei=wei)
|
||||||
|
else:
|
||||||
|
self.fields['team'].queryset = BusTeam.objects.none()
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
cleaned_data = super().clean()
|
cleaned_data = super().clean()
|
||||||
if 'team' in cleaned_data and cleaned_data["team"] is not None \
|
if 'team' in cleaned_data and cleaned_data["team"] is not None \
|
||||||
@ -151,21 +164,8 @@ class WEIMembershipForm(forms.ModelForm):
|
|||||||
model = WEIMembership
|
model = WEIMembership
|
||||||
fields = ('roles', 'bus', 'team',)
|
fields = ('roles', 'bus', 'team',)
|
||||||
widgets = {
|
widgets = {
|
||||||
"bus": Autocomplete(
|
"bus": RadioSelect(),
|
||||||
Bus,
|
"team": RadioSelect(),
|
||||||
attrs={
|
|
||||||
'api_url': '/api/wei/bus/',
|
|
||||||
'placeholder': 'Bus ...',
|
|
||||||
}
|
|
||||||
),
|
|
||||||
"team": Autocomplete(
|
|
||||||
BusTeam,
|
|
||||||
attrs={
|
|
||||||
'api_url': '/api/wei/team/',
|
|
||||||
'placeholder': 'Équipe ...',
|
|
||||||
},
|
|
||||||
resetable=True,
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -210,4 +210,27 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<script>
|
||||||
|
$(document).ready(function () {
|
||||||
|
function refreshTeams() {
|
||||||
|
let buses = [];
|
||||||
|
$("input[name='bus']:checked").each(function (ignored) {
|
||||||
|
buses.push($(this).parent().text().trim());
|
||||||
|
});
|
||||||
|
console.log(buses);
|
||||||
|
$("input[name='team']").each(function () {
|
||||||
|
let label = $(this).parent();
|
||||||
|
$(this).parent().addClass('d-none');
|
||||||
|
buses.forEach(function (bus) {
|
||||||
|
if (label.text().includes(bus))
|
||||||
|
label.removeClass('d-none');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$("input[name='bus']").change(refreshTeams);
|
||||||
|
|
||||||
|
refreshTeams();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -788,7 +788,8 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update
|
|||||||
return form
|
return form
|
||||||
|
|
||||||
def get_membership_form(self, data=None, instance=None):
|
def get_membership_form(self, data=None, instance=None):
|
||||||
membership_form = WEIMembershipForm(data if data else None, instance=instance)
|
registration = self.get_object()
|
||||||
|
membership_form = WEIMembershipForm(data if data else None, instance=instance, wei=registration.wei)
|
||||||
del membership_form.fields["credit_type"]
|
del membership_form.fields["credit_type"]
|
||||||
del membership_form.fields["credit_amount"]
|
del membership_form.fields["credit_amount"]
|
||||||
del membership_form.fields["first_name"]
|
del membership_form.fields["first_name"]
|
||||||
@ -969,6 +970,13 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView):
|
|||||||
return WEIMembership1AForm
|
return WEIMembership1AForm
|
||||||
return WEIMembershipForm
|
return WEIMembershipForm
|
||||||
|
|
||||||
|
def get_form_kwargs(self):
|
||||||
|
kwargs = super().get_form_kwargs()
|
||||||
|
registration = WEIRegistration.objects.get(pk=self.kwargs["pk"])
|
||||||
|
wei = registration.wei
|
||||||
|
kwargs['wei'] = wei
|
||||||
|
return kwargs
|
||||||
|
|
||||||
def get_form(self, form_class=None):
|
def get_form(self, form_class=None):
|
||||||
form = super().get_form(form_class)
|
form = super().get_form(form_class)
|
||||||
registration = WEIRegistration.objects.get(pk=self.kwargs["pk"])
|
registration = WEIRegistration.objects.get(pk=self.kwargs["pk"])
|
||||||
|
@ -70,7 +70,6 @@ INSTALLED_APPS = [
|
|||||||
# Note apps
|
# Note apps
|
||||||
'api',
|
'api',
|
||||||
'activity',
|
'activity',
|
||||||
'family',
|
|
||||||
'food',
|
'food',
|
||||||
'logs',
|
'logs',
|
||||||
'member',
|
'member',
|
||||||
@ -271,7 +270,7 @@ OAUTH2_PROVIDER = {
|
|||||||
'PKCE_REQUIRED': False, # PKCE (fix a breaking change of django-oauth-toolkit 2.0.0)
|
'PKCE_REQUIRED': False, # PKCE (fix a breaking change of django-oauth-toolkit 2.0.0)
|
||||||
'OIDC_ENABLED': True,
|
'OIDC_ENABLED': True,
|
||||||
'OIDC_RSA_PRIVATE_KEY':
|
'OIDC_RSA_PRIVATE_KEY':
|
||||||
os.getenv('OIDC_RSA_PRIVATE_KEY', 'CHANGE_ME_IN_ENV_SETTINGS').replace('\\n', '\n'), # for multilines
|
os.getenv('OIDC_RSA_PRIVATE_KEY', '/var/secrets/oidc.key'),
|
||||||
'SCOPES': { 'openid': "OpenID Connect scope" },
|
'SCOPES': { 'openid': "OpenID Connect scope" },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,13 +78,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-exchange"></i> {% trans 'Transfer' %}</a>
|
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-exchange"></i> {% trans 'Transfer' %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if user.is_authenticated %}
|
|
||||||
<li class="nav-item">
|
|
||||||
{% url 'family:family_list' as url %}
|
|
||||||
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-users"></i> {% trans 'Families' %}</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if "auth.user"|model_list_length >= 2 %}
|
{% if "auth.user"|model_list_length >= 2 %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
{% url 'member:user_list' as url %}
|
{% url 'member:user_list' as url %}
|
||||||
|
@ -23,7 +23,6 @@ urlpatterns = [
|
|||||||
path('wei/', include('wei.urls')),
|
path('wei/', include('wei.urls')),
|
||||||
path('food/',include('food.urls')),
|
path('food/',include('food.urls')),
|
||||||
path('wrapped/',include('wrapped.urls')),
|
path('wrapped/',include('wrapped.urls')),
|
||||||
path('family/', include('family.urls')),
|
|
||||||
|
|
||||||
# Include Django Contrib and Core routers
|
# Include Django Contrib and Core routers
|
||||||
path('i18n/', include('django.conf.urls.i18n')),
|
path('i18n/', include('django.conf.urls.i18n')),
|
||||||
|
Reference in New Issue
Block a user