mirror of
				https://gitlab.crans.org/bde/nk20
				synced 2025-10-30 15:29:53 +01:00 
			
		
		
		
	Merge branch 'ouvreureuse' into 'main'
Ouvreureuse See merge request bde/nk20!256
This commit is contained in:
		| @@ -1,9 +1,11 @@ | |||||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||||
| # SPDX-License-Identifier: GPL-3.0-or-later | # SPDX-License-Identifier: GPL-3.0-or-later | ||||||
|  |  | ||||||
|  | from django.utils.translation import gettext_lazy as _ | ||||||
| from rest_framework import serializers | from rest_framework import serializers | ||||||
|  | from rest_framework.validators import UniqueTogetherValidator | ||||||
|  |  | ||||||
| from ..models import Activity, ActivityType, Entry, Guest, GuestTransaction | from ..models import Activity, ActivityType, Entry, Guest, GuestTransaction, Opener | ||||||
|  |  | ||||||
|  |  | ||||||
| class ActivityTypeSerializer(serializers.ModelSerializer): | class ActivityTypeSerializer(serializers.ModelSerializer): | ||||||
| @@ -59,3 +61,17 @@ class GuestTransactionSerializer(serializers.ModelSerializer): | |||||||
|     class Meta: |     class Meta: | ||||||
|         model = GuestTransaction |         model = GuestTransaction | ||||||
|         fields = '__all__' |         fields = '__all__' | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class OpenerSerializer(serializers.ModelSerializer): | ||||||
|  |     """ | ||||||
|  |     REST API Serializer for Openers. | ||||||
|  |     The djangorestframework plugin will analyse the model `Opener` and parse all fields in the API. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     class Meta: | ||||||
|  |         model = Opener | ||||||
|  |         fields = '__all__' | ||||||
|  |         validators = [UniqueTogetherValidator( | ||||||
|  |             queryset=Opener.objects.all(), fields=("opener", "activity"), | ||||||
|  |             message=_("This opener already exists"))] | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay | ||||||
| # SPDX-License-Identifier: GPL-3.0-or-later | # SPDX-License-Identifier: GPL-3.0-or-later | ||||||
|  |  | ||||||
| from .views import ActivityTypeViewSet, ActivityViewSet, EntryViewSet, GuestViewSet | from .views import ActivityTypeViewSet, ActivityViewSet, EntryViewSet, GuestViewSet, OpenerViewSet | ||||||
|  |  | ||||||
|  |  | ||||||
| def register_activity_urls(router, path): | def register_activity_urls(router, path): | ||||||
| @@ -12,3 +12,4 @@ def register_activity_urls(router, path): | |||||||
|     router.register(path + '/type', ActivityTypeViewSet) |     router.register(path + '/type', ActivityTypeViewSet) | ||||||
|     router.register(path + '/guest', GuestViewSet) |     router.register(path + '/guest', GuestViewSet) | ||||||
|     router.register(path + '/entry', EntryViewSet) |     router.register(path + '/entry', EntryViewSet) | ||||||
|  |     router.register(path + '/opener', OpenerViewSet) | ||||||
|   | |||||||
| @@ -2,11 +2,14 @@ | |||||||
| # SPDX-License-Identifier: GPL-3.0-or-later | # SPDX-License-Identifier: GPL-3.0-or-later | ||||||
|  |  | ||||||
| from api.viewsets import ReadProtectedModelViewSet | from api.viewsets import ReadProtectedModelViewSet | ||||||
|  | from django.core.exceptions import ValidationError | ||||||
| from django_filters.rest_framework import DjangoFilterBackend | from django_filters.rest_framework import DjangoFilterBackend | ||||||
| from rest_framework.filters import SearchFilter | from rest_framework.filters import SearchFilter | ||||||
|  | from rest_framework.response import Response | ||||||
|  | from rest_framework import status | ||||||
|  |  | ||||||
| from .serializers import ActivitySerializer, ActivityTypeSerializer, EntrySerializer, GuestSerializer | from .serializers import ActivitySerializer, ActivityTypeSerializer, EntrySerializer, GuestSerializer, OpenerSerializer | ||||||
| from ..models import Activity, ActivityType, Entry, Guest | from ..models import Activity, ActivityType, Entry, Guest, Opener | ||||||
|  |  | ||||||
|  |  | ||||||
| class ActivityTypeViewSet(ReadProtectedModelViewSet): | class ActivityTypeViewSet(ReadProtectedModelViewSet): | ||||||
| @@ -66,3 +69,32 @@ class EntryViewSet(ReadProtectedModelViewSet): | |||||||
|     filterset_fields = ['activity', 'time', 'note', 'guest', ] |     filterset_fields = ['activity', 'time', 'note', 'guest', ] | ||||||
|     search_fields = ['$activity__name', '$note__user__email', '$note__alias__name', '$note__alias__normalized_name', |     search_fields = ['$activity__name', '$note__user__email', '$note__alias__name', '$note__alias__normalized_name', | ||||||
|                      '$guest__last_name', '$guest__first_name', ] |                      '$guest__last_name', '$guest__first_name', ] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class OpenerViewSet(ReadProtectedModelViewSet): | ||||||
|  |     """ | ||||||
|  |     REST Opener View set. | ||||||
|  |     The djangorestframework plugin will get all `Opener` objects, serialize it to JSON with the given serializer, | ||||||
|  |     then render it on /api/activity/opener/ | ||||||
|  |     """ | ||||||
|  |     queryset = Opener.objects | ||||||
|  |     serializer_class = OpenerSerializer | ||||||
|  |     filter_backends = [SearchFilter, DjangoFilterBackend] | ||||||
|  |     search_fields = ['$opener__alias__name', '$opener__alias__normalized_name', | ||||||
|  |                      '$activity__name'] | ||||||
|  |     filterset_fields = ['opener', 'opener__noteuser__user', 'activity'] | ||||||
|  |  | ||||||
|  |     def get_serializer_class(self): | ||||||
|  |         serializer_class = self.serializer_class | ||||||
|  |         if self.request.method in ['PUT', 'PATCH']: | ||||||
|  |             # opener-activity can't change | ||||||
|  |             serializer_class.Meta.read_only_fields = ('opener', 'acitivity',) | ||||||
|  |         return serializer_class | ||||||
|  |  | ||||||
|  |     def destroy(self, request, *args, **kwargs): | ||||||
|  |         instance = self.get_object() | ||||||
|  |         try: | ||||||
|  |             self.perform_destroy(instance) | ||||||
|  |         except ValidationError as e: | ||||||
|  |             return Response({e.code: str(e)}, status.HTTP_400_BAD_REQUEST) | ||||||
|  |         return Response(status=status.HTTP_204_NO_CONTENT) | ||||||
|   | |||||||
| @@ -43,7 +43,7 @@ class ActivityForm(forms.ModelForm): | |||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|         model = Activity |         model = Activity | ||||||
|         exclude = ('creater', 'valid', 'open', ) |         exclude = ('creater', 'valid', 'open', 'opener', ) | ||||||
|         widgets = { |         widgets = { | ||||||
|             "organizer": Autocomplete( |             "organizer": Autocomplete( | ||||||
|                 model=Club, |                 model=Club, | ||||||
|   | |||||||
							
								
								
									
										28
									
								
								apps/activity/migrations/0004_opener.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								apps/activity/migrations/0004_opener.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | # Generated by Django 2.2.28 on 2024-08-01 12:36 | ||||||
|  |  | ||||||
|  | from django.db import migrations, models | ||||||
|  | import django.db.models.deletion | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |         ('note', '0006_trust'), | ||||||
|  |         ('activity', '0003_auto_20240323_1422'), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='Opener', | ||||||
|  |             fields=[ | ||||||
|  |                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||||||
|  |                 ('activity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='opener', to='activity.Activity', verbose_name='activity')), | ||||||
|  |                 ('opener', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='activity_responsible', to='note.Note', verbose_name='opener')), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 'verbose_name': 'opener', | ||||||
|  |                 'verbose_name_plural': 'openers', | ||||||
|  |                 'unique_together': {('opener', 'activity')}, | ||||||
|  |             }, | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
| @@ -11,7 +11,7 @@ from django.db import models, transaction | |||||||
| from django.db.models import Q | from django.db.models import Q | ||||||
| from django.utils import timezone | from django.utils import timezone | ||||||
| from django.utils.translation import gettext_lazy as _ | from django.utils.translation import gettext_lazy as _ | ||||||
| from note.models import NoteUser, Transaction | from note.models import NoteUser, Transaction, Note | ||||||
| from rest_framework.exceptions import ValidationError | from rest_framework.exceptions import ValidationError | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -310,3 +310,31 @@ class GuestTransaction(Transaction): | |||||||
|     @property |     @property | ||||||
|     def type(self): |     def type(self): | ||||||
|         return _('Invitation') |         return _('Invitation') | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Opener(models.Model): | ||||||
|  |     """ | ||||||
|  |     Allow the user to make activity entries without more rights | ||||||
|  |     """ | ||||||
|  |     activity = models.ForeignKey( | ||||||
|  |         Activity, | ||||||
|  |         on_delete=models.CASCADE, | ||||||
|  |         related_name='opener', | ||||||
|  |         verbose_name=_('activity') | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     opener = models.ForeignKey( | ||||||
|  |         Note, | ||||||
|  |         on_delete=models.CASCADE, | ||||||
|  |         related_name='activity_responsible', | ||||||
|  |         verbose_name=_('Opener') | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     class Meta: | ||||||
|  |         verbose_name = _("Opener") | ||||||
|  |         verbose_name_plural = _("Openers") | ||||||
|  |         unique_together = ("opener", "activity") | ||||||
|  |  | ||||||
|  |     def __str__(self): | ||||||
|  |         return _("{opener} is opener of activity {acivity}").format( | ||||||
|  |             opener=str(self.opener), acivity=str(self.activity)) | ||||||
|   | |||||||
							
								
								
									
										78
									
								
								apps/activity/static/activity/js/opener.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								apps/activity/static/activity/js/opener.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | |||||||
|  | /** | ||||||
|  |  * On form submit, create a new friendship | ||||||
|  |  */ | ||||||
|  | function form_create_opener (e) { | ||||||
|  |   // Do not submit HTML form | ||||||
|  |   e.preventDefault() | ||||||
|  |  | ||||||
|  |   // Get data and send to API | ||||||
|  |   const formData = new FormData(e.target) | ||||||
|  |   $.getJSON('/api/note/alias/'+formData.get('opener') + '/', | ||||||
|  |     function (opener_alias) { | ||||||
|  |       create_opener(formData.get('activity'), opener_alias.note) | ||||||
|  |     }).fail(function (xhr, _textStatus, _error) { | ||||||
|  |         errMsg(xhr.responseJSON) | ||||||
|  |     }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Create a trust between users | ||||||
|  |  * @param trusting:Integer trusting note id | ||||||
|  |  * @param trusted:Integer trusted note id | ||||||
|  |  */ | ||||||
|  | function create_opener(activity, opener) { | ||||||
|  |   $.post('/api/activity/opener/', { | ||||||
|  |       activity: activity, | ||||||
|  |       opener: opener, | ||||||
|  |       csrfmiddlewaretoken: CSRF_TOKEN | ||||||
|  |   }).done(function () { | ||||||
|  |   // Reload tables | ||||||
|  |   $('#opener_table').load(location.pathname + ' #opener_table') | ||||||
|  |     addMsg(gettext('Friendship successfully added'), 'success') | ||||||
|  |   }).fail(function (xhr, _textStatus, _error) { | ||||||
|  |     errMsg(xhr.responseJSON) | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * On form submit, create a new friendship | ||||||
|  | function create_opener (e) { | ||||||
|  |   // Do not submit HTML form | ||||||
|  |   e.preventDefault() | ||||||
|  |  | ||||||
|  |   // Get data and send to API | ||||||
|  |   const formData = new FormData(e.target) | ||||||
|  |   $.post('/api/activity/opener/', { | ||||||
|  |     csrfmiddlewaretoken: formData.get('csrfmiddlewaretoken'), | ||||||
|  |     activity: formData.get('activity'), | ||||||
|  |     opener: formData.get('opener') | ||||||
|  |   }).done(function () { | ||||||
|  |     // Reload table | ||||||
|  |     $('#opener_table').load(location.pathname + ' #opener_table') | ||||||
|  |     addMsg(gettext('Alias successfully added'), 'success') | ||||||
|  |   }).fail(function (xhr, _textStatus, _error) { | ||||||
|  |     errMsg(xhr.responseJSON) | ||||||
|  |   }) | ||||||
|  | }*/ | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * On click of "delete", delete the trust | ||||||
|  |  * @param button_id:Integer Trust id to remove | ||||||
|  |  */ | ||||||
|  | function delete_button (button_id) { | ||||||
|  |   $.ajax({ | ||||||
|  |     url: '/api/activity/opener/' + button_id + '/', | ||||||
|  |     method: 'DELETE', | ||||||
|  |     headers: { 'X-CSRFTOKEN': CSRF_TOKEN } | ||||||
|  |   }).done(function () { | ||||||
|  |     addMsg(gettext('Friendship successfully deleted'), 'success') | ||||||
|  |     $('#opener_table').load(location.pathname + ' #opener_table') | ||||||
|  |   }).fail(function (xhr, _textStatus, _error) { | ||||||
|  |     errMsg(xhr.responseJSON) | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | $(document).ready(function () { | ||||||
|  |   // Attach event | ||||||
|  |   document.getElementById('form_opener').addEventListener('submit', form_create_opener) | ||||||
|  | }) | ||||||
| @@ -5,11 +5,13 @@ from django.utils import timezone | |||||||
| from django.utils.html import escape | from django.utils.html import escape | ||||||
| from django.utils.safestring import mark_safe | from django.utils.safestring import mark_safe | ||||||
| from django.utils.translation import gettext_lazy as _ | from django.utils.translation import gettext_lazy as _ | ||||||
|  | from note_kfet.middlewares import get_current_request | ||||||
| import django_tables2 as tables | import django_tables2 as tables | ||||||
| from django_tables2 import A | from django_tables2 import A | ||||||
|  | from permission.backends import PermissionBackend | ||||||
| from note.templatetags.pretty_money import pretty_money | from note.templatetags.pretty_money import pretty_money | ||||||
|  |  | ||||||
| from .models import Activity, Entry, Guest | from .models import Activity, Entry, Guest, Opener | ||||||
|  |  | ||||||
|  |  | ||||||
| class ActivityTable(tables.Table): | class ActivityTable(tables.Table): | ||||||
| @@ -113,3 +115,34 @@ class EntryTable(tables.Table): | |||||||
|             'data-last-name': lambda record: record.last_name, |             'data-last-name': lambda record: record.last_name, | ||||||
|             'data-first-name': lambda record: record.first_name, |             'data-first-name': lambda record: record.first_name, | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # function delete_button(id) provided in template file | ||||||
|  | DELETE_TEMPLATE = """ | ||||||
|  |     <button id="{{ record.pk }}" class="btn btn-danger btn-sm" onclick="delete_button(this.id)"> {{ delete_trans }}</button> | ||||||
|  | """ | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class OpenerTable(tables.Table): | ||||||
|  |     class Meta: | ||||||
|  |         attrs = { | ||||||
|  |             'class': 'table table condensed table-striped', | ||||||
|  |             'id': "opener_table" | ||||||
|  |         } | ||||||
|  |         model = Opener | ||||||
|  |         fields = ("opener",) | ||||||
|  |         template_name = 'django_tables2/bootstrap4.html' | ||||||
|  |  | ||||||
|  |     show_header = False | ||||||
|  |     opener = tables.Column(attrs={'td': {'class': 'text-center'}}) | ||||||
|  |  | ||||||
|  |     delete_col = tables.TemplateColumn( | ||||||
|  |         template_code=DELETE_TEMPLATE, | ||||||
|  |         extra_context={"delete_trans": _('Delete')}, | ||||||
|  |         attrs={ | ||||||
|  |             'td': { | ||||||
|  |                 'class': lambda record: 'col-sm-1' | ||||||
|  |                 + (' d-none' if not PermissionBackend.check_perm( | ||||||
|  |                     get_current_request(), "activity.delete_opener", record) | ||||||
|  |                    else '')}}, | ||||||
|  |         verbose_name=_("Delete"),) | ||||||
|   | |||||||
| @@ -4,11 +4,31 @@ SPDX-License-Identifier: GPL-3.0-or-later | |||||||
| {% endcomment %} | {% endcomment %} | ||||||
| {% load i18n perms %} | {% load i18n perms %} | ||||||
| {% load render_table from django_tables2 %} | {% load render_table from django_tables2 %} | ||||||
|  | {% load static django_tables2 i18n %} | ||||||
|  |  | ||||||
| {% block content %} | {% block content %} | ||||||
| <h1 class="text-white">{{ title }}</h1> | <h1 class="text-white">{{ title }}</h1> | ||||||
| {% include "activity/includes/activity_info.html" %} | {% include "activity/includes/activity_info.html" %} | ||||||
|  |  | ||||||
|  | {% if activity.activity_type.manage_entries and ".change__opener"|has_perm:activity %} | ||||||
|  |     <div class="card bg-white mb-3"> | ||||||
|  |         <h3 class="card-header text-center"> | ||||||
|  |             {% trans "Openers" %} | ||||||
|  |         </h3> | ||||||
|  |         <div class="card-body"> | ||||||
|  |             <form class="input-group" method="POST" id="form_opener"> | ||||||
|  |                 {% csrf_token %} | ||||||
|  |                 <input type="hidden" name="activity" value="{{ object.pk }}"> | ||||||
|  |                 {%include "autocomplete_model.html" %} | ||||||
|  |                 <div class="input-group-append"> | ||||||
|  |                     <input type="submit" class="btn btn-success" value="{% trans "Add" %}"> | ||||||
|  |                 </div> | ||||||
|  |             </form> | ||||||
|  |         </div> | ||||||
|  |         {% render_table opener %} | ||||||
|  |     </div> | ||||||
|  | {% endif %} | ||||||
|  |  | ||||||
| {% if guests.data %} | {% if guests.data %} | ||||||
| <div class="card bg-white mb-3"> | <div class="card bg-white mb-3"> | ||||||
|     <h3 class="card-header text-center"> |     <h3 class="card-header text-center"> | ||||||
| @@ -22,6 +42,8 @@ SPDX-License-Identifier: GPL-3.0-or-later | |||||||
| {% endblock %} | {% endblock %} | ||||||
|  |  | ||||||
| {% block extrajavascript %} | {% block extrajavascript %} | ||||||
|  | <script src="{% static "activity/js/opener.js" %}"></script> | ||||||
|  | <script src="{% static "js/autocomplete_model.js" %}"></script> | ||||||
| <script> | <script> | ||||||
|     function remove_guest(guest_id) { |     function remove_guest(guest_id) { | ||||||
|         $.ajax({ |         $.ajax({ | ||||||
|   | |||||||
| @@ -24,8 +24,8 @@ from permission.backends import PermissionBackend | |||||||
| from permission.views import ProtectQuerysetMixin, ProtectedCreateView | from permission.views import ProtectQuerysetMixin, ProtectedCreateView | ||||||
|  |  | ||||||
| from .forms import ActivityForm, GuestForm | from .forms import ActivityForm, GuestForm | ||||||
| from .models import Activity, Entry, Guest | from .models import Activity, Entry, Guest, Opener | ||||||
| from .tables import ActivityTable, EntryTable, GuestTable | from .tables import ActivityTable, EntryTable, GuestTable, OpenerTable | ||||||
|  |  | ||||||
|  |  | ||||||
| class ActivityCreateView(ProtectQuerysetMixin, ProtectedCreateView): | class ActivityCreateView(ProtectQuerysetMixin, ProtectedCreateView): | ||||||
| @@ -114,8 +114,24 @@ class ActivityDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): | |||||||
|                            .filter(PermissionBackend.filter_queryset(self.request, Guest, "view"))) |                            .filter(PermissionBackend.filter_queryset(self.request, Guest, "view"))) | ||||||
|         context["guests"] = table |         context["guests"] = table | ||||||
|  |  | ||||||
|  |         table = OpenerTable(data=self.object.opener.filter(activity=self.object) | ||||||
|  |                             .filter(PermissionBackend.filter_queryset(self.request, Opener, "view"))) | ||||||
|  |         context["opener"] = table | ||||||
|  |  | ||||||
|         context["activity_started"] = timezone.now() > timezone.localtime(self.object.date_start) |         context["activity_started"] = timezone.now() > timezone.localtime(self.object.date_start) | ||||||
|  |  | ||||||
|  |         context["widget"] = { | ||||||
|  |             "name": "opener", | ||||||
|  |             "resetable": True, | ||||||
|  |             "attrs": { | ||||||
|  |                 "class": "autocomplete form-control", | ||||||
|  |                 "id": "opener", | ||||||
|  |                 "api_url": "/api/note/alias/?note__polymorphic_ctype__model=noteuser", | ||||||
|  |                 "name_field": "name", | ||||||
|  |                 "placeholder": "" | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         return context |         return context | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										18
									
								
								apps/member/migrations/0013_auto_20240801_1436.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								apps/member/migrations/0013_auto_20240801_1436.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | # Generated by Django 2.2.28 on 2024-08-01 12:36 | ||||||
|  |  | ||||||
|  | from django.db import migrations, models | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |         ('member', '0012_club_add_registration_form'), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name='profile', | ||||||
|  |             name='promotion', | ||||||
|  |             field=models.PositiveSmallIntegerField(default=2024, help_text='Year of entry to the school (None if not ENS student)', null=True, verbose_name='promotion'), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
| @@ -18,6 +18,7 @@ def create_special_notes(apps, schema_editor): | |||||||
| class Migration(migrations.Migration): | class Migration(migrations.Migration): | ||||||
|     dependencies = [ |     dependencies = [ | ||||||
|         ('note', '0001_initial'), |         ('note', '0001_initial'), | ||||||
|  |         ('logs', '0001_initial'), | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|     operations = [ |     operations = [ | ||||||
|   | |||||||
| @@ -3111,6 +3111,199 @@ | |||||||
| 			"description": "Voir ceux nous ayant pour ami, pour toujours" | 			"description": "Voir ceux nous ayant pour ami, pour toujours" | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"model": "permission.permission", | ||||||
|  | 		"pk": 199, | ||||||
|  | 		"fields": { | ||||||
|  | 			"model": [ | ||||||
|  | 				"activity", | ||||||
|  | 				"activity" | ||||||
|  | 			], | ||||||
|  | 			"query": "{\"opener__in\": [\"user\", \"note\", \"activity_responsible\", [\"all\"]], \"open\": true, \"activity_type__manage_entries\":true}", | ||||||
|  | 			"type": "view", | ||||||
|  | 			"mask": 2, | ||||||
|  | 			"field": "", | ||||||
|  | 			"permanent": false, | ||||||
|  | 			"description": "Voir les activités ouvertes dont l'utilisateur⋅rice est ouvreur⋅se" | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"model": "permission.permission", | ||||||
|  | 		"pk": 200, | ||||||
|  | 		"fields": { | ||||||
|  | 			"model": [ | ||||||
|  | 				"activity", | ||||||
|  | 				"activity" | ||||||
|  | 			], | ||||||
|  | 			"query": "{\"opener__in\": [\"user\", \"note\", \"activity_responsible\", [\"all\"]], \"open\": true, \"activity_type__manage_entries\":true}", | ||||||
|  | 			"type": "change", | ||||||
|  | 			"mask": 2, | ||||||
|  | 			"field": "open", | ||||||
|  | 			"permanent": false, | ||||||
|  | 			"description": "Fermer les activités ouvertes dont l'utilisateur⋅rice est ouvreur⋅se" | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"model": "permission.permission", | ||||||
|  | 		"pk": 201, | ||||||
|  | 		"fields": { | ||||||
|  | 			"model": [ | ||||||
|  | 				"activity", | ||||||
|  | 				"entry" | ||||||
|  | 			], | ||||||
|  | 			"query": "{\"activity__opener__in\": [\"user\", \"note\", \"activity_responsible\", [\"all\"]], \"activity__open\": true, \"activity__activity_type__manage_entries\":true}", | ||||||
|  | 			"type": "add", | ||||||
|  | 			"mask": 2, | ||||||
|  | 			"field": "", | ||||||
|  | 			"permanent": false, | ||||||
|  | 			"description": "Faire les entrées des activités ouvertes dont l'utilisateur⋅rice est ouvreur⋅se" | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"model": "permission.permission", | ||||||
|  | 		"pk": 202, | ||||||
|  | 		"fields": { | ||||||
|  | 			"model": [ | ||||||
|  | 				"activity", | ||||||
|  | 				"entry" | ||||||
|  | 			], | ||||||
|  | 			"query": "{\"activity__opener__in\": [\"user\", \"note\", \"activity_responsible\", [\"all\"]], \"activity__open\": true, \"activity__activity_type__manage_entries\":true}", | ||||||
|  | 			"type": "view", | ||||||
|  | 			"mask": 2, | ||||||
|  | 			"field": "", | ||||||
|  | 			"permanent": false, | ||||||
|  | 			"description": "Voir les entrées des activités ouvertes dont l'utilisateur⋅rice est ouvreur⋅se" | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"model": "permission.permission", | ||||||
|  | 		"pk": 203, | ||||||
|  | 		"fields": { | ||||||
|  | 			"model": [ | ||||||
|  | 				"activity", | ||||||
|  | 				"guest" | ||||||
|  | 			], | ||||||
|  | 			"query": "{\"activity__pk__in\": [\"user\", \"note\", \"activity_responsible\", [\"all\"]], \"activity__open\": true, \"activity__activity_type__manage_entries\":true}", | ||||||
|  | 			"type": "view", | ||||||
|  | 			"mask": 2, | ||||||
|  | 			"field": "", | ||||||
|  | 			"permanent": false, | ||||||
|  | 			"description": "Voir les invité⋅es des activités ouvertes dont l'utilisateur⋅rice est ouvreur⋅se" | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"model": "permission.permission", | ||||||
|  | 		"pk": 204, | ||||||
|  | 		"fields": { | ||||||
|  | 			"model": [ | ||||||
|  | 				"activity", | ||||||
|  | 				"guesttransaction" | ||||||
|  | 			], | ||||||
|  | 			"query": "[\"NOT\", {\"pk__isnull\": [\"user\", \"note\", \"activity_responsible\", [\"filter\", {\"activity__open\": true, \"activity__activity_type__manage_entries\":true}], [\"exists\"]]}]", | ||||||
|  | 			"type": "add", | ||||||
|  | 			"mask": 2, | ||||||
|  | 			"field": "", | ||||||
|  | 			"permanent": false, | ||||||
|  | 			"description": "Créer une transaction d'invitation lorsque l'utilisateur⋅rice est ouvreur⋅se d'une activité ouverte" | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  |  | ||||||
|  | 		"model": "permission.permission", | ||||||
|  | 		"pk": 205, | ||||||
|  | 		"fields": { | ||||||
|  | 			"model": [ | ||||||
|  | 				"note", | ||||||
|  | 				"specialtransaction" | ||||||
|  | 			], | ||||||
|  | 			"query": "[\"NOT\", {\"pk__isnull\": [\"user\", \"note\", \"activity_responsible\", [\"filter\", {\"activity__open\": true, \"activity__activity_type__manage_entries\":true}], [\"exists\"]]}]", | ||||||
|  | 			"type": "add", | ||||||
|  | 			"mask": 2, | ||||||
|  | 			"field": "", | ||||||
|  | 			"permanent": false, | ||||||
|  | 			"description": "Créer un crédit ou un retrait quelconque lorsque l'utilisateur⋅rice est ouvreur⋅se d'une activité ouverte" | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"model": "permission.permission", | ||||||
|  | 		"pk": 206, | ||||||
|  | 		"fields": { | ||||||
|  | 			"model": [ | ||||||
|  | 				"note", | ||||||
|  | 				"notespecial" | ||||||
|  | 			], | ||||||
|  | 			"query": "[\"NOT\", {\"pk__isnull\": [\"user\", \"note\", \"activity_responsible\", [\"filter\", {\"activity__open\": true, \"activity__activity_type__manage_entries\":true}], [\"exists\"]]}]", | ||||||
|  | 			"type": "view", | ||||||
|  | 			"mask": 2, | ||||||
|  | 			"field": "", | ||||||
|  | 			"permanent": false, | ||||||
|  | 			"description": "Afficher l'interface crédit/retrait lorsque l'utilisateur⋅rice est ouvreur⋅se d'une activité ouverte" | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"model": "permission.permission", | ||||||
|  | 		"pk": 207, | ||||||
|  | 		"fields": { | ||||||
|  | 			"model": [ | ||||||
|  | 				"activity", | ||||||
|  | 				"opener" | ||||||
|  | 			], | ||||||
|  | 			"query": "{}", | ||||||
|  | 			"type": "view", | ||||||
|  | 			"mask": 2, | ||||||
|  | 			"field": "", | ||||||
|  | 			"permanent": false, | ||||||
|  | 			"description": "Voir les ouvreur⋅ses des activités" | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"model": "permission.permission", | ||||||
|  | 		"pk": 208, | ||||||
|  | 		"fields": { | ||||||
|  | 			"model": [ | ||||||
|  | 				"activity", | ||||||
|  | 				"opener" | ||||||
|  | 			], | ||||||
|  | 			"query": "{}", | ||||||
|  | 			"type": "add", | ||||||
|  | 			"mask": 2, | ||||||
|  | 			"field": "", | ||||||
|  | 			"permanent": false, | ||||||
|  | 			"description": "Ajouter des ouvreur⋅ses aux activités" | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"model": "permission.permission", | ||||||
|  | 		"pk": 209, | ||||||
|  | 		"fields": { | ||||||
|  | 			"model": [ | ||||||
|  | 				"activity", | ||||||
|  | 				"opener" | ||||||
|  | 			], | ||||||
|  | 			"query": "{}", | ||||||
|  | 			"type": "delete", | ||||||
|  | 			"mask": 2, | ||||||
|  | 			"field": "", | ||||||
|  | 			"permanent": false, | ||||||
|  | 			"description": "Supprimer des ouvreur⋅ses aux activités" | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"model": "permission.permission", | ||||||
|  | 		"pk": 210, | ||||||
|  | 		"fields": { | ||||||
|  | 			"model": [ | ||||||
|  | 				"activity", | ||||||
|  | 				"activity" | ||||||
|  | 			], | ||||||
|  | 			"query": "{}", | ||||||
|  | 			"type": "change", | ||||||
|  | 			"mask": 2, | ||||||
|  | 			"field": "opener", | ||||||
|  | 			"permanent": false, | ||||||
|  | 			"description": "Voir le tableau des ouvreur⋅ses" | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"model": "permission.role", | 		"model": "permission.role", | ||||||
| 		"pk": 1, | 		"pk": 1, | ||||||
| @@ -3148,11 +3341,19 @@ | |||||||
| 				187, | 				187, | ||||||
| 				188, | 				188, | ||||||
| 				189, | 				189, | ||||||
|                 190, | 				190, | ||||||
|                 191, | 				191, | ||||||
|                 195, | 				195, | ||||||
|                 196, | 				196, | ||||||
|                 198 | 				198, | ||||||
|  | 				199, | ||||||
|  | 				200, | ||||||
|  | 				201, | ||||||
|  | 				202, | ||||||
|  | 				203, | ||||||
|  | 				204, | ||||||
|  | 				205, | ||||||
|  | 				206 | ||||||
| 			] | 			] | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
| @@ -3414,7 +3615,11 @@ | |||||||
| 				46, | 				46, | ||||||
| 				148, | 				148, | ||||||
| 				149, | 				149, | ||||||
| 				182 | 				182, | ||||||
|  | 				207, | ||||||
|  | 				208, | ||||||
|  | 				209, | ||||||
|  | 				210 | ||||||
| 			] | 			] | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ peuvent être diffusées via des calendriers ou la mailing list d'événements. | |||||||
| Modèles | Modèles | ||||||
| ------- | ------- | ||||||
|  |  | ||||||
| L'application comporte 5 modèles : activités, types d'activité, invités, entrées et transactions d'invitation. | L'application comporte 6 modèles : activités, types d'activité, invités, entrées, transactions d'invitation et les ouvreur⋅ses. | ||||||
|  |  | ||||||
| Types d'activité | Types d'activité | ||||||
| ~~~~~~~~~~~~~~~~ | ~~~~~~~~~~~~~~~~ | ||||||
| @@ -71,6 +71,17 @@ comportent qu'un champ supplémentaire, de type ``OneToOneField`` vers ``Guest`` | |||||||
| Ce modèle aurait pu appartenir à l'application ``note``, mais afin de rester modulaire et que l'application ``note`` | Ce modèle aurait pu appartenir à l'application ``note``, mais afin de rester modulaire et que l'application ``note`` | ||||||
| ne dépende pas de cette application, on procède de cette manière. | ne dépende pas de cette application, on procède de cette manière. | ||||||
|  |  | ||||||
|  | Ouvreur⋅ses | ||||||
|  | ~~~~~~~~~~~ | ||||||
|  |  | ||||||
|  | Depuis la page d'une activité, il est possible d'ajouter des personnes en tant qu'« ouvreur⋅se ». Cela permet à une | ||||||
|  | personne sans aucun droit note de pouvoir faire les entrées d'une ``Activity``. Ce rôle n'est valable que pendant que | ||||||
|  | l'activité est ouverte et sur aucune autre activité. Les ouvreur⋅ses ont aussi accès à l'interface des transactions. | ||||||
|  |  | ||||||
|  | Ce modèle regroupe : | ||||||
|  | * Activité (clé étrangère) | ||||||
|  | * Note (clé étrangère) | ||||||
|  |  | ||||||
| Graphe | Graphe | ||||||
| ~~~~~~ | ~~~~~~ | ||||||
|  |  | ||||||
| @@ -108,3 +119,6 @@ apparaîssent, afin de régler la taxe d'invitation : l'un prélève directement | |||||||
| permettent un paiement par espèces ou par carte bancaire. En réalité, les deux derniers boutons enregistrent | permettent un paiement par espèces ou par carte bancaire. En réalité, les deux derniers boutons enregistrent | ||||||
| automatiquement un crédit sur la note de l'hôte, puis une transaction (de type ``GuestTransaction``) est faite depuis | automatiquement un crédit sur la note de l'hôte, puis une transaction (de type ``GuestTransaction``) est faite depuis | ||||||
| la note de l'hôte vers la note de l'organisateur de l'événement. | la note de l'hôte vers la note de l'organisateur de l'événement. | ||||||
|  |  | ||||||
|  | Si une personne souhaite faire les entrées, il est possible de l'ajouter dans la liste des ouvreur⋅ses depuis la page | ||||||
|  | de l'activité. | ||||||
|   | |||||||
| @@ -20,6 +20,7 @@ msgstr "" | |||||||
|  |  | ||||||
| #: apps/activity/apps.py:10 apps/activity/models.py:127 | #: apps/activity/apps.py:10 apps/activity/models.py:127 | ||||||
| #: apps/activity/models.py:167 | #: apps/activity/models.py:167 | ||||||
|  | #: apps/activity/models.py:323 | ||||||
| msgid "activity" | msgid "activity" | ||||||
| msgstr "activité" | msgstr "activité" | ||||||
|  |  | ||||||
| @@ -238,6 +239,16 @@ msgstr "invité·e·s" | |||||||
| msgid "Invitation" | msgid "Invitation" | ||||||
| msgstr "Invitation" | msgstr "Invitation" | ||||||
|  |  | ||||||
|  | #: apps/activity/models.py:330 | ||||||
|  | #: apps/activity/models.py:334 | ||||||
|  | msgid "Opener" | ||||||
|  | msgstr "Ouvreur⋅se" | ||||||
|  |  | ||||||
|  | #: apps/activity/models.py:335 | ||||||
|  | #: apps/activity/templates/activity_detail.html:16 | ||||||
|  | msgid "Openers" | ||||||
|  | msgstr "Ouvreur⋅ses" | ||||||
|  |  | ||||||
| #: apps/activity/tables.py:27 | #: apps/activity/tables.py:27 | ||||||
| msgid "The activity is currently open." | msgid "The activity is currently open." | ||||||
| msgstr "Cette activité est actuellement ouverte." | msgstr "Cette activité est actuellement ouverte." | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user