Improve activity interface

This commit is contained in:
Yohann D'ANELLO 2020-08-06 17:41:30 +02:00
parent dd4b24d999
commit 9c7cb07dec
13 changed files with 772 additions and 683 deletions

View File

@ -4,6 +4,7 @@
"pk": 1,
"fields": {
"name": "Pot",
"manage_entries": true,
"can_invite": true,
"guest_entry_fee": 500
}
@ -13,6 +14,7 @@
"pk": 2,
"fields": {
"name": "Soir\u00e9e de club",
"manage_entries": false,
"can_invite": false,
"guest_entry_fee": 0
}

View File

@ -43,7 +43,7 @@ class GuestForm(forms.ModelForm):
def clean(self):
cleaned_data = super().clean()
if self.activity.date_start > timezone.now():
if timezone.now() > timezone.localtime(self.activity.date_start):
self.add_error("inviter", _("You can't invite someone once the activity is started."))
if not self.activity.valid:
@ -52,20 +52,21 @@ class GuestForm(forms.ModelForm):
one_year = timedelta(days=365)
qs = Guest.objects.filter(
first_name=cleaned_data["first_name"],
last_name=cleaned_data["last_name"],
first_name__iexact=cleaned_data["first_name"],
last_name__iexact=cleaned_data["last_name"],
activity__date_start__gte=self.activity.date_start - one_year,
entry__isnull=False,
)
if len(qs) >= 5:
if qs.count() >= 5:
self.add_error("last_name", _("This person has been already invited 5 times this year."))
qs = qs.filter(activity=self.activity)
if qs.exists():
self.add_error("last_name", _("This person is already invited."))
qs = Guest.objects.filter(inviter=cleaned_data["inviter"], activity=self.activity)
if len(qs) >= 3:
self.add_error("inviter", _("You can't invite more than 3 people to this activity."))
if "inviter" in cleaned_data:
if Guest.objects.filter(inviter=cleaned_data["inviter"], activity=self.activity).count() >= 3:
self.add_error("inviter", _("You can't invite more than 3 people to this activity."))
return cleaned_data

View File

@ -27,11 +27,21 @@ class ActivityType(models.Model):
verbose_name=_('name'),
max_length=255,
)
manage_entries = models.BooleanField(
verbose_name=_('manage entries'),
help_text=_('Enable the support of entries for this activity.'),
default=False,
)
can_invite = models.BooleanField(
verbose_name=_('can invite'),
default=False,
)
guest_entry_fee = models.PositiveIntegerField(
verbose_name=_('guest entry fee'),
default=0,
)
class Meta:
@ -236,18 +246,18 @@ class Guest(models.Model):
one_year = timedelta(days=365)
if not force_insert:
if self.activity.date_start > timezone.now():
if timezone.now() > timezone.localtime(self.activity.date_start):
raise ValidationError(_("You can't invite someone once the activity is started."))
if not self.activity.valid:
raise ValidationError(_("This activity is not validated yet."))
qs = Guest.objects.filter(
first_name=self.first_name,
last_name=self.last_name,
first_name__iexact=self.first_name,
last_name__iexact=self.last_name,
activity__date_start__gte=self.activity.date_start - one_year,
)
if len(qs) >= 5:
if qs.count() >= 5:
raise ValidationError(_("This person has been already invited 5 times this year."))
qs = qs.filter(activity=self.activity)
@ -255,7 +265,7 @@ class Guest(models.Model):
raise ValidationError(_("This person is already invited."))
qs = Guest.objects.filter(inviter=self.inviter, activity=self.activity)
if len(qs) >= 3:
if qs.count() >= 3:
raise ValidationError(_("You can't invite more than 3 people to this activity."))
return super().save(force_insert, force_update, using, update_fields)

View File

@ -1,6 +1,6 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.utils import timezone
from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _
import django_tables2 as tables
@ -66,6 +66,10 @@ def get_row_class(record):
qs = Entry.objects.filter(note=record.note, activity=record.activity, guest=None)
if qs.exists():
c += " table-success"
elif not record.note.user.memberships.filter(club=record.activity.attendees_club,
date_start__lte=timezone.now(),
date_end__gte=timezone.now()).exists():
c += " table-info"
elif record.note.balance < 0:
c += " table-danger"
return c

View File

@ -1,13 +1,12 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from datetime import datetime, timezone
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.contenttypes.models import ContentType
from django.db.models import F, Q
from django.urls import reverse_lazy
from django.utils import timezone
from django.views.generic import CreateView, DetailView, UpdateView, TemplateView
from django.utils.translation import gettext_lazy as _
from django_tables2.views import SingleTableView
@ -46,12 +45,17 @@ class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
upcoming_activities = Activity.objects.filter(date_end__gt=datetime.now())
upcoming_activities = Activity.objects.filter(date_end__gt=timezone.now())
context['upcoming'] = ActivityTable(
data=upcoming_activities.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view")),
prefix='upcoming-',
)
started_activities = Activity.objects\
.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view"))\
.filter(open=True, valid=True).all()
context["started_activities"] = started_activities
return context
@ -67,7 +71,7 @@ class ActivityDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
.filter(PermissionBackend.filter_queryset(self.request.user, Guest, "view")))
context["guests"] = table
context["activity_started"] = datetime.now(timezone.utc) > self.object.date_start
context["activity_started"] = timezone.now() > timezone.localtime(self.object.date_start)
return context
@ -148,7 +152,12 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
username=F("note__noteuser__user__username"),
note_name=F("name"),
balance=F("note__balance"))\
.filter(note__polymorphic_ctype__model="noteuser")\
.filter(note__noteuser__isnull=False)\
.filter(
note__noteuser__user__memberships__club=activity.attendees_club,
note__noteuser__user__memberships__date_start__lte=timezone.now(),
note__noteuser__user__memberships__date_end__gte=timezone.now(),
)\
.filter(PermissionBackend.filter_queryset(self.request.user, Alias, "view"))
if pattern:
note_qs = note_qs.filter(

@ -1 +1 @@
Subproject commit 3806feb67fcb1fe822cfdedddbbc4ca7eeef3829
Subproject commit 4984159a61642a0d3668e85daf39472b59b86447

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -7,70 +7,7 @@
{% block content %}
<div id="activity_info" class="card bg-light shadow">
<div class="card-header text-center">
<h4>{{ activity.name }}</h4>
</div>
<div class="card-body" id="profile_infos">
<dl class="row">
<dt class="col-xl-6">{% trans 'description'|capfirst %}</dt>
<dd class="col-xl-6"> {{ activity.description }}</dd>
<dt class="col-xl-6">{% trans 'type'|capfirst %}</dt>
<dd class="col-xl-6"> {{ activity.activity_type }}</dd>
<dt class="col-xl-6">{% trans 'start date'|capfirst %}</dt>
<dd class="col-xl-6">{{ activity.date_start }}</dd>
<dt class="col-xl-6">{% trans 'end date'|capfirst %}</dt>
<dd class="col-xl-6">{{ activity.date_end }}</dd>
{% if ".view_"|has_perm:activity.creater %}
<dt class="col-xl-6">{% trans 'creater'|capfirst %}</dt>
<dd class="col-xl-6"><a href="{% url "member:user_detail" pk=activity.creater.pk %}">{{ activity.creater }}</a></dd>
{% endif %}
<dt class="col-xl-6">{% trans 'organizer'|capfirst %}</dt>
<dd class="col-xl-6"><a href="{% url "member:club_detail" pk=activity.organizer.pk %}">{{ activity.organizer }}</a></dd>
<dt class="col-xl-6">{% trans 'attendees club'|capfirst %}</dt>
<dd class="col-xl-6"><a href="{% url "member:club_detail" pk=activity.attendees_club.pk %}">{{ activity.attendees_club }}</a></dd>
<dt class="col-xl-6">{% trans 'can invite'|capfirst %}</dt>
<dd class="col-xl-6">{{ activity.activity_type.can_invite|yesno }}</dd>
{% if activity.activity_type.can_invite %}
<dt class="col-xl-6">{% trans 'guest entry fee'|capfirst %}</dt>
<dd class="col-xl-6">{{ activity.activity_type.guest_entry_fee|pretty_money }}</dd>
{% endif %}
<dt class="col-xl-6">{% trans 'valid'|capfirst %}</dt>
<dd class="col-xl-6">{{ activity.valid|yesno }}</dd>
<dt class="col-xl-6">{% trans 'opened'|capfirst %}</dt>
<dd class="col-xl-6">{{ activity.open|yesno }}</dd>
</dl>
</div>
<div class="card-footer text-center">
{% if activity.open and ".change__open"|has_perm:activity %}
<a class="btn btn-warning btn-sm my-1" href="{% url 'activity:activity_entry' pk=activity.pk %}"> {% trans "Entry page" %}</a>
{% endif %}
{% if activity.valid and ".change__open"|has_perm:activity %}
<a class="btn btn-warning btn-sm my-1" id="open_activity"> {% if activity.open %}{% trans "close"|capfirst %}{% else %}{% trans "open"|capfirst %}{% endif %}</a>
{% endif %}
{% if not activity.open and ".change__valid"|has_perm:activity %}
<a class="btn btn-success btn-sm my-1" id="validate_activity"> {% if activity.valid %}{% trans "invalidate"|capfirst %}{% else %}{% trans "validate"|capfirst %}{% endif %}</a>
{% endif %}
{% if ".change_"|has_perm:activity %}
<a class="btn btn-primary btn-sm my-1" href="{% url 'activity:activity_update' pk=activity.pk %}"> {% trans "edit"|capfirst %}</a>
{% endif %}
{% if activity.activity_type.can_invite and not activity_started %}
<a class="btn btn-primary btn-sm my-1" href="{% url 'activity:activity_invite' pk=activity.pk %}"> {% trans "Invite" %}</a>
{% endif %}
</div>
</div>
{% include "activity/activity_info.html" %}
{% if guests.data %}
<hr>

View File

@ -76,7 +76,10 @@
note: id,
guest: null
}).done(function () {
addMsg("Entrée effectuée !", "success", 4000);
if (target.hasClass("table-info"))
addMsg("Entrée effectuée, mais attention : la personne n'est plus adhérente Kfet.", "warning", 10000);
else
addMsg("Entrée effectuée !", "success", 4000);
reloadTable(true);
}).fail(function(xhr) {
errMsg(xhr.responseJSON, 4000);
@ -104,7 +107,10 @@
note: target.attr("data-inviter"),
guest: id
}).done(function () {
addMsg("Entrée effectuée !", "success", 4000);
if (target.hasClass("table-info"))
addMsg("Entrée effectuée, mais attention : la personne n'est plus adhérente Kfet.", "warning", 10000);
else
addMsg("Entrée effectuée !", "success", 4000);
reloadTable(true);
}).fail(function (xhr) {
errMsg(xhr.responseJSON, 4000);

View File

@ -0,0 +1,78 @@
{% load i18n %}
{% load perms %}
{% load pretty_money %}
{% url 'activity:activity_detail' activity.pk as activity_detail_url %}
<div id="activity_info" class="card bg-light shadow">
<div class="card-header text-center">
<h4>
{% if request.path_info != activity_detail_url %}
<a href="{{ activity_detail_url }}">{{ activity.name }}</a>
{% else %}
{{ activity.name }}
{% endif %}
</h4>
</div>
<div class="card-body" id="profile_infos">
<dl class="row">
<dt class="col-xl-6">{% trans 'description'|capfirst %}</dt>
<dd class="col-xl-6"> {{ activity.description }}</dd>
<dt class="col-xl-6">{% trans 'type'|capfirst %}</dt>
<dd class="col-xl-6"> {{ activity.activity_type }}</dd>
<dt class="col-xl-6">{% trans 'start date'|capfirst %}</dt>
<dd class="col-xl-6">{{ activity.date_start }}</dd>
<dt class="col-xl-6">{% trans 'end date'|capfirst %}</dt>
<dd class="col-xl-6">{{ activity.date_end }}</dd>
{% if ".view_"|has_perm:activity.creater %}
<dt class="col-xl-6">{% trans 'creater'|capfirst %}</dt>
<dd class="col-xl-6"><a href="{% url "member:user_detail" pk=activity.creater.pk %}">{{ activity.creater }}</a></dd>
{% endif %}
<dt class="col-xl-6">{% trans 'organizer'|capfirst %}</dt>
<dd class="col-xl-6"><a href="{% url "member:club_detail" pk=activity.organizer.pk %}">{{ activity.organizer }}</a></dd>
<dt class="col-xl-6">{% trans 'attendees club'|capfirst %}</dt>
<dd class="col-xl-6"><a href="{% url "member:club_detail" pk=activity.attendees_club.pk %}">{{ activity.attendees_club }}</a></dd>
<dt class="col-xl-6">{% trans 'can invite'|capfirst %}</dt>
<dd class="col-xl-6">{{ activity.activity_type.can_invite|yesno }}</dd>
{% if activity.activity_type.can_invite %}
<dt class="col-xl-6">{% trans 'guest entry fee'|capfirst %}</dt>
<dd class="col-xl-6">{{ activity.activity_type.guest_entry_fee|pretty_money }}</dd>
{% endif %}
<dt class="col-xl-6">{% trans 'valid'|capfirst %}</dt>
<dd class="col-xl-6">{{ activity.valid|yesno }}</dd>
<dt class="col-xl-6">{% trans 'opened'|capfirst %}</dt>
<dd class="col-xl-6">{{ activity.open|yesno }}</dd>
</dl>
</div>
<div class="card-footer text-center">
{% if activity.open and activity.activity_type.manage_entries and ".change__open"|has_perm:activity %}
<a class="btn btn-warning btn-sm my-1" href="{% url 'activity:activity_entry' pk=activity.pk %}"> {% trans "Entry page" %}</a>
{% endif %}
{% if request.path_info == activity_detail_url %}
{% if activity.valid and ".change__open"|has_perm:activity %}
<a class="btn btn-warning btn-sm my-1" id="open_activity"> {% if activity.open %}{% trans "close"|capfirst %}{% else %}{% trans "open"|capfirst %}{% endif %}</a>
{% endif %}
{% if not activity.open and ".change__valid"|has_perm:activity %}
<a class="btn btn-success btn-sm my-1" id="validate_activity"> {% if activity.valid %}{% trans "invalidate"|capfirst %}{% else %}{% trans "validate"|capfirst %}{% endif %}</a>
{% endif %}
{% if ".change_"|has_perm:activity %}
<a class="btn btn-primary btn-sm my-1" href="{% url 'activity:activity_update' pk=activity.pk %}"> {% trans "edit"|capfirst %}</a>
{% endif %}
{% if activity.activity_type.can_invite and not activity_started %}
<a class="btn btn-primary btn-sm my-1" href="{% url 'activity:activity_invite' pk=activity.pk %}"> {% trans "Invite" %}</a>
{% endif %}
{% endif %}
</div>
</div>

View File

@ -2,6 +2,14 @@
{% load render_table from django_tables2 %}
{% load i18n crispy_forms_tags%}
{% block content %}
{% if started_activities %}
<h2>{% trans "Current activity" %}</h2>
{% for activity in started_activities %}
{% include "activity/activity_info.html" %}
<hr>
{% endfor %}
{% endif %}
<h2>{% trans "Upcoming activities" %}</h2>
{% if upcoming.data %}
{% render_table upcoming %}

View File

@ -19,10 +19,12 @@ SPDX-License-Identifier: GPL-2.0-or-later
<input type="radio" name="transaction_type" id="type_credit">
{% trans "Credit" %}
</label>
<label type="type_debit" class="btn btn-sm btn-outline-primary">
<input type="radio" name="transaction_type" id="type_debit">
{% trans "Debit" %}
</label>
{% if not activities_open %}
<label type="type_debit" class="btn btn-sm btn-outline-primary">
<input type="radio" name="transaction_type" id="type_debit">
{% trans "Debit" %}
</label>
{% endif %}
{% endif %}
{% for activity in activities_open %}
<a href="{% url "activity:activity_entry" pk=activity.pk %}" class="btn btn-sm btn-outline-primary">