mirror of
				https://gitlab.crans.org/bde/nk20
				synced 2025-10-30 23:39:54 +01:00 
			
		
		
		
	Improve activity interface
This commit is contained in:
		| @@ -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 | ||||
|     } | ||||
|   | ||||
| @@ -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,19 +52,20 @@ 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: | ||||
|         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 | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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( | ||||
|   | ||||
 Submodule apps/scripts updated: 3806feb67f...4984159a61
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -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> | ||||
|   | ||||
| @@ -76,6 +76,9 @@ | ||||
|                         note: id, | ||||
|                         guest: null | ||||
|                     }).done(function () { | ||||
|                         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) { | ||||
| @@ -104,6 +107,9 @@ | ||||
|                             note: target.attr("data-inviter"), | ||||
|                             guest: id | ||||
|                         }).done(function () { | ||||
|                             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) { | ||||
|   | ||||
							
								
								
									
										78
									
								
								templates/activity/activity_info.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								templates/activity/activity_info.html
									
									
									
									
									
										Normal 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> | ||||
| @@ -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 %} | ||||
|   | ||||
| @@ -19,11 +19,13 @@ SPDX-License-Identifier: GPL-2.0-or-later | ||||
|                         <input type="radio" name="transaction_type" id="type_credit"> | ||||
|                         {% trans "Credit" %} | ||||
|                     </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"> | ||||
|                         {% trans "Entries" %} {{ activity.name }} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user