diff --git a/apps/note/api/views.py b/apps/note/api/views.py index f230a646dd3240c30c8ed60f5b0ec6011093fa25..fc4a0e8f56695fa9c18b5c5af937d79092e46894 100644 --- a/apps/note/api/views.py +++ b/apps/note/api/views.py @@ -5,6 +5,7 @@ from django.db.models import Q from django_filters.rest_framework import DjangoFilterBackend from rest_framework.filters import OrderingFilter, SearchFilter from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet +from rest_framework import viewsets from .serializers import NotePolymorphicSerializer, AliasSerializer, TemplateCategorySerializer, \ TransactionTemplateSerializer, TransactionPolymorphicSerializer @@ -81,7 +82,7 @@ class TemplateCategoryViewSet(ReadProtectedModelViewSet): search_fields = ['$name', ] -class TransactionTemplateViewSet(ReadProtectedModelViewSet): +class TransactionTemplateViewSet(viewsets.ModelViewSet): """ REST API View set. The djangorestframework plugin will get all `TransactionTemplate` objects, serialize it to JSON with the given serializer, @@ -89,8 +90,9 @@ class TransactionTemplateViewSet(ReadProtectedModelViewSet): """ queryset = TransactionTemplate.objects.all() serializer_class = TransactionTemplateSerializer - filter_backends = [DjangoFilterBackend] + filter_backends = [SearchFilter, DjangoFilterBackend] filterset_fields = ['name', 'amount', 'display', 'category', ] + search_fields = ['$name', ] class TransactionViewSet(ReadProtectedModelViewSet): diff --git a/apps/note/tables.py b/apps/note/tables.py index b9dac051f7bc902a232479b7bcef8fbd9c9cbb74..20054d2cf52fbb79a2dfd277b3bab0243dcd0c18 100644 --- a/apps/note/tables.py +++ b/apps/note/tables.py @@ -9,7 +9,7 @@ from django_tables2.utils import A from django.utils.translation import gettext_lazy as _ from .models.notes import Alias -from .models.transactions import Transaction +from .models.transactions import Transaction, TransactionTemplate from .templatetags.pretty_money import pretty_money @@ -57,6 +57,12 @@ class HistoryTable(tables.Table): return "✔" if value else "✖" +# function delete_button(id) provided in template file +DELETE_TEMPLATE = """ + <button id="{{ record.pk }}" class="btn btn-danger" onclick="delete_button(this.id)"> {{ delete_trans }}</button> +""" + + class AliasTable(tables.Table): class Meta: attrs = { @@ -69,9 +75,41 @@ class AliasTable(tables.Table): show_header = False name = tables.Column(attrs={'td': {'class': 'text-center'}}) + # delete = tables.TemplateColumn(template_code=delete_template, + # attrs={'td':{'class': 'col-sm-1'}}) + delete = tables.LinkColumn('member:user_alias_delete', args=[A('pk')], attrs={ 'td': {'class': 'col-sm-2'}, 'a': {'class': 'btn btn-danger'}}, text='delete', accessor='pk') + + +class ButtonTable(tables.Table): + class Meta: + attrs = { + 'class': + 'table table-bordered condensed table-hover' + } + row_attrs = { + 'class': lambda record: 'table-row ' + 'table-success' if record.display else 'table-danger', + 'id': lambda record: "row-" + str(record.pk), + 'data-href': lambda record: record.pk + } + + model = TransactionTemplate + + edit = tables.LinkColumn('note:template_update', + args=[A('pk')], + attrs={'td': {'class': 'col-sm-1'}, + 'a': {'class': 'btn btn-primary'}}, + text=_('edit'), + accessor='pk') + + delete = tables.TemplateColumn(template_code=DELETE_TEMPLATE, + extra_context={"delete_trans": _('delete')}, + attrs={'td': {'class': 'col-sm-1'}}) + + def render_amount(self, value): + return pretty_money(value) diff --git a/apps/note/urls.py b/apps/note/urls.py index fea911f6ac5ebdeaa871240fe56c764d024db675..59316069479d84f980aba8d64ae9fe206db10dbc 100644 --- a/apps/note/urls.py +++ b/apps/note/urls.py @@ -8,7 +8,7 @@ from .models import Note app_name = 'note' urlpatterns = [ - path('transfer/', views.TransactionCreate.as_view(), name='transfer'), + path('transfer/', views.TransactionCreateView.as_view(), name='transfer'), path('buttons/create/', views.TransactionTemplateCreateView.as_view(), name='template_create'), path('buttons/update/<int:pk>/', views.TransactionTemplateUpdateView.as_view(), name='template_update'), path('buttons/', views.TransactionTemplateListView.as_view(), name='template_list'), diff --git a/apps/note/views.py b/apps/note/views.py index 84df2bd7c7e57320c08fb3c80121fcde3489b8a0..ddf5ee6f10e0806b900ae6a415f10a26e501d9f1 100644 --- a/apps/note/views.py +++ b/apps/note/views.py @@ -6,22 +6,25 @@ from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.contenttypes.models import ContentType from django.db.models import Q from django.utils.translation import gettext_lazy as _ -from django.views.generic import CreateView, ListView, UpdateView +from django.views.generic import CreateView, UpdateView from django_tables2 import SingleTableView +from django.urls import reverse_lazy from permission.backends import PermissionBackend from .forms import TransactionTemplateForm from .models import Transaction, TransactionTemplate, Alias, RecurrentTransaction, NoteSpecial from .models.transactions import SpecialTransaction -from .tables import HistoryTable +from .tables import HistoryTable, ButtonTable -class TransactionCreate(LoginRequiredMixin, SingleTableView): +class TransactionCreateView(LoginRequiredMixin, SingleTableView): """ - Show transfer page + View for the creation of Transaction between two note which are not :models:`transactions.RecurrentTransaction`. + e.g. for donation/transfer between people and clubs or for credit/debit with :models:`note.NoteSpecial` """ template_name = "note/transaction_form.html" + model = Transaction # Transaction history table table_class = HistoryTable table_pagination = {"per_page": 50} @@ -46,13 +49,14 @@ class TransactionCreate(LoginRequiredMixin, SingleTableView): class NoteAutocomplete(autocomplete.Select2QuerySetView): """ - Auto complete note by aliases + Auto complete note by aliases. Used in every search field for note + ex: :view:`ConsoView`, :view:`TransactionCreateView` """ def get_queryset(self): """ - Quand une personne cherche un alias, une requête est envoyée sur l'API dédiée à l'auto-complétion. - Cette fonction récupère la requête, et renvoie la liste filtrée des aliases. + When someone look for an :models:`note.Alias`, a query is sent to the dedicated API. + This function handles the result and return a filtered list of aliases. """ # Un utilisateur non connecté n'a accès à aucune information if not self.request.user.is_authenticated: @@ -81,6 +85,10 @@ class NoteAutocomplete(autocomplete.Select2QuerySetView): return qs def get_result_label(self, result): + """ + Show the selected alias and the username associated + <Alias> (aka. <Username> ) + """ # Gère l'affichage de l'alias dans la recherche res = result.name note_name = str(result.note) @@ -89,7 +97,9 @@ class NoteAutocomplete(autocomplete.Select2QuerySetView): return res def get_result_value(self, result): - # Le résultat renvoyé doit être l'identifiant de la note, et non de l'alias + """ + The value used for the transactions will be the id of the Note. + """ return str(result.note.pk) @@ -99,14 +109,15 @@ class TransactionTemplateCreateView(LoginRequiredMixin, CreateView): """ model = TransactionTemplate form_class = TransactionTemplateForm + success_url = reverse_lazy('note:template_list') -class TransactionTemplateListView(LoginRequiredMixin, ListView): +class TransactionTemplateListView(LoginRequiredMixin, SingleTableView): """ List TransactionsTemplates """ model = TransactionTemplate - form_class = TransactionTemplateForm + table_class = ButtonTable class TransactionTemplateUpdateView(LoginRequiredMixin, UpdateView): @@ -114,11 +125,13 @@ class TransactionTemplateUpdateView(LoginRequiredMixin, UpdateView): """ model = TransactionTemplate form_class = TransactionTemplateForm + success_url = reverse_lazy('note:template_list') class ConsoView(LoginRequiredMixin, SingleTableView): """ - Consume + The Magic View that make people pay their beer and burgers. + (Most of the magic happens in the dark world of Javascript see consos.js) """ template_name = "note/conso_form.html" diff --git a/apps/permission/fixtures/initial.json b/apps/permission/fixtures/initial.json index 3ca2b2486f40906e3e3c686b43c7bf750f4c1398..4c7de16d5bf5bb5c046cff28546bb81b477ec9b9 100644 --- a/apps/permission/fixtures/initial.json +++ b/apps/permission/fixtures/initial.json @@ -650,4 +650,4 @@ ] } } -] \ No newline at end of file +] diff --git a/apps/permission/permissions.py b/apps/permission/permissions.py index d9816a63ad0d3bad0dc13ca3e5af8449fbc32f62..9f6d8cd22c6cc85f60b4c0dd005406d919283bcd 100644 --- a/apps/permission/permissions.py +++ b/apps/permission/permissions.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later from rest_framework.permissions import DjangoObjectPermissions +from .backends import PermissionBackend SAFE_METHODS = ('HEAD', 'OPTIONS', ) @@ -41,8 +42,8 @@ class StrongDjangoObjectPermissions(DjangoObjectPermissions): user = request.user perms = self.get_required_object_permissions(request.method, model_cls) - - if not user.has_perms(perms, obj): + # if not user.has_perms(perms, obj): + if not all(PermissionBackend().has_perm(user, perm, obj) for perm in perms): # If the user does not have permissions we need to determine if # they have read permissions to see 403, or not, and simply see # a 404 response. diff --git a/apps/scripts b/apps/scripts new file mode 160000 index 0000000000000000000000000000000000000000..b9fdced3c2ce34168b8f0d6004a20a69ca16e0de --- /dev/null +++ b/apps/scripts @@ -0,0 +1 @@ +Subproject commit b9fdced3c2ce34168b8f0d6004a20a69ca16e0de diff --git a/note_kfet/settings/base.py b/note_kfet/settings/base.py index 84bd324914b14a058ddb292d6e4e8da8d6e73482..d49b25424258dc73b5e2ae336b88727c0f50f3b9 100644 --- a/note_kfet/settings/base.py +++ b/note_kfet/settings/base.py @@ -128,7 +128,6 @@ PASSWORD_HASHERS = [ AUTHENTICATION_BACKENDS = ( 'permission.backends.PermissionBackend', # Custom role-based permission system - 'cas.backends.CASBackend', # For CAS connections ) REST_FRAMEWORK = { diff --git a/templates/base.html b/templates/base.html index 384535b2acdca1998c3d99faf25d85095d3259e7..62fc9c58c8732a692cfa201cc2145b0eece7940c 100644 --- a/templates/base.html +++ b/templates/base.html @@ -79,6 +79,9 @@ SPDX-License-Identifier: GPL-3.0-or-later <a class="nav-link" href="{% url 'note:consos' %}"><i class="fa fa-coffee"></i> {% trans 'Consumptions' %}</a> </li> {% endif %} + <li class="nav-item active"> + <a class="nav-link" href="{% url 'note:transfer' %}"><i class="fa fa-exchange"></i>{% trans 'Transfer' %} </a> + </li> {% if "member.club"|not_empty_model_list %} <li class="nav-item active"> <a class="nav-link" href="{% url 'member:club_list' %}"><i class="fa fa-users"></i> {% trans 'Clubs' %}</a> @@ -89,14 +92,6 @@ SPDX-License-Identifier: GPL-3.0-or-later <a class="nav-link" href="#"><i class="fa fa-calendar"></i> {% trans 'Activities' %}</a> </li> {% endif %} - {% if "note.transactiontemplate"|not_empty_model_change_list %} - <li class="nav-item active"> - <a class="nav-link" href="{% url 'note:template_list' %}"><i class="fa fa-coffee"></i> {% trans 'Buttons' %}</a> - </li> - {% endif %} - <li class="nav-item active"> - <a class="nav-link" href="{% url 'note:transfer' %}"><i class="fa fa-exchange"></i>{% trans 'Transfer' %} </a> - </li> {% if "treasury.invoice"|not_empty_model_change_list %} <li class="nav-item active"> <a class="nav-link" href="{% url 'treasury:invoice_list' %}"><i class="fa fa-money"></i>{% trans 'Treasury' %} </a> diff --git a/templates/note/transaction_form.html b/templates/note/transaction_form.html index 347db0564cc7609d9de55b373b4bbe81e161b62d..d2cd85e9e269ea4b1a5e0f7c5fdaa0a5760ce419 100644 --- a/templates/note/transaction_form.html +++ b/templates/note/transaction_form.html @@ -33,6 +33,16 @@ SPDX-License-Identifier: GPL-2.0-or-later </div> <div class="row"> + + <div class="col-xl-4" id="note_infos_div"> + <div class="card border-success shadow mb-4"> + <img src="/media/pic/default.png" + id="profile_pic" alt="" class="img-fluid rounded mx-auto d-block"> + <div class="card-body text-center"> + <span id="user_note"></span> + </div> + </div> + </div> <div class="col-md-4" id="emitters_div" style="display: none;"> <div class="card border-success shadow mb-4"> <div class="card-header"> @@ -50,16 +60,6 @@ SPDX-License-Identifier: GPL-2.0-or-later </div> </div> - <div class="col-xl-4" id="note_infos_div"> - <div class="card border-success shadow mb-4"> - <img src="/media/pic/default.png" - id="profile_pic" alt="" class="img-fluid rounded mx-auto d-block"> - <div class="card-body text-center"> - <span id="user_note"></span> - </div> - </div> - </div> - {% if "note.notespecial"|not_empty_model_list %} <div class="col-md-4" id="external_div" style="display: none;"> <div class="card border-success shadow mb-4"> @@ -170,8 +170,8 @@ SPDX-License-Identifier: GPL-2.0-or-later }); $("#type_transfer").click(function() { - $("#emitters_div").show(); $("#external_div").hide(); + $("#emitters_div").show(); $("#dests_div").attr('class', 'col-md-4'); $("#dest_title").text("{% trans "Select receivers" %}"); }); diff --git a/templates/note/transactiontemplate_list.html b/templates/note/transactiontemplate_list.html index 4960023694b30179bc3423c30c4a451fa3642a8b..043a06e990c92b99665e5bf9a025f114f3a07f05 100644 --- a/templates/note/transactiontemplate_list.html +++ b/templates/note/transactiontemplate_list.html @@ -1,23 +1,79 @@ {% extends "base.html" %} {% load pretty_money %} +{% load i18n %} +{% load render_table from django_tables2 %} {% block content %} +<div class="row justify-content-center mb-4"> + <div class="col-md-10 text-center"> + <h4> + {% trans "search button" %} + </h4> + <input class="form-control mx-auto w-25" type="text" onkeyup="search_field_moved();return(false);" id="search_field"/> + <hr> + <a class="btn btn-primary text-center my-4" href="{% url 'note:template_create' %}">Créer un bouton</a> + </div> +</div> +<div class="row justify-content-center"> + <div class="col-md-10"> + <div class="card card-border shadow"> + <div class="card-header text-center"> + <h5> {% trans "buttons listing "%}</h5> + </div> + <div class="card-body px-0 py-0" id="buttons_table"> + {% render_table table %} + </div> + </div> + </div> +</div> +{% endblock %} -<table class="table"> -<tr> -<td>ID</td><td>Nom</td> - <td>Destinataire</td> - <td>Montant</td> - <td>Catégorie</td> -</tr> -{% for object in object_list %} -<tr> - <td>{{object.pk}}</td> - <td><a href="{{object.get_absolute_url}}">{{ object.name }}</a></td> - <td>{{ object.destination }}</td> - <td>{{ object.amount | pretty_money }}</td> - <td>{{ object.category }}</td> -</tr> -{% endfor %} -</table> -<a class="btn btn-primary" href="{% url 'note:template_create' %}">Créer un bouton</a> +{% block extrajavascript %} +<script> +/* fonction appelée à la fin du timer */ +function getInfo() { + var asked = $("#search_field").val(); + /* on ne fait la requête que si on a au moins un caractère pour chercher */ + var sel = $(".table-row"); + if (asked.length >= 1) { + $.getJSON("/api/note/transaction/template/?format=json&search="+asked, function(buttons){ + let selected_id = buttons.results.map((a => "#row-"+a.id)); + console.log(selected_id.join()); + $(".table-row,"+selected_id.join()).show(); + $(".table-row").not(selected_id.join()).hide(); + + }); + }else{ + // show everything + $('table tr').show(); + } +} +var timer; +var timer_on; +/* Fontion appelée quand le texte change (délenche le timer) */ +function search_field_moved(secondfield) { + if (timer_on) { // Si le timer a déjà été lancé, on réinitialise le compteur. + clearTimeout(timer); + timer = setTimeout("getInfo(" + secondfield + ")", 300); + } + else { // Sinon, on le lance et on enregistre le fait qu'il tourne. + timer = setTimeout("getInfo(" + secondfield + ")", 300); + timer_on = true; + } +} +// on click of button "delete" , call the API + function delete_button(button_id){ + $.ajax({ + url:"/api/note/transaction/template/"+button_id+"/", + method:"DELETE", + headers: {"X-CSRFTOKEN": CSRF_TOKEN} + }) + .done(function(){ + addMsg('{% trans "button successfully deleted "%}','success'); + $("#buttons_table").load("{% url 'note:template_list' %} #buttons_table"); + }) + .fail(function(){ + addMsg(' {% trans "Unable to delete button "%} #' + button_id,'danger' ) + }); + } +</script> {% endblock %} diff --git a/templates/registration/login.html b/templates/registration/login.html index 5a4322d138ea110f8089d6c9a928d579740f476c..175d37e0d1a4260510c858b6693b4059bace23ea 100644 --- a/templates/registration/login.html +++ b/templates/registration/login.html @@ -16,11 +16,13 @@ SPDX-License-Identifier: GPL-2.0-or-later {% endblocktrans %} </p> {% endif %} - + {%url 'cas_login' as cas_url %} + {% if cas_url %} <div class="alert alert-info"> - Vous pouvez aussi vous connecter via l'authentification centralisée <a href="{% url 'cas_login' %}">en suivant ce lien.</a> + {% trans "You can also register via the central authentification server " %} + <a href="{{ cas_url }}"> {% trans "using this link "%}</a> </div> - + {%endif%} <form action="{{ app_path }}" method="post" id="login-form">{% csrf_token %} {{ form | crispy }} <input type="submit" value="{% trans 'Log in' %}" class="btn btn-primary">