mirror of https://gitlab.crans.org/bde/nk20
Merge branch 'manage_button' into 'master'
Meilleurs vues pour la gestion des boutons See merge request bde/nk20!54
This commit is contained in:
commit
0ac94547d1
|
@ -5,6 +5,7 @@ from django.db.models import Q
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||||
from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet
|
from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet
|
||||||
|
from rest_framework import viewsets
|
||||||
|
|
||||||
from .serializers import NotePolymorphicSerializer, AliasSerializer, TemplateCategorySerializer, \
|
from .serializers import NotePolymorphicSerializer, AliasSerializer, TemplateCategorySerializer, \
|
||||||
TransactionTemplateSerializer, TransactionPolymorphicSerializer
|
TransactionTemplateSerializer, TransactionPolymorphicSerializer
|
||||||
|
@ -81,7 +82,7 @@ class TemplateCategoryViewSet(ReadProtectedModelViewSet):
|
||||||
search_fields = ['$name', ]
|
search_fields = ['$name', ]
|
||||||
|
|
||||||
|
|
||||||
class TransactionTemplateViewSet(ReadProtectedModelViewSet):
|
class TransactionTemplateViewSet(viewsets.ModelViewSet):
|
||||||
"""
|
"""
|
||||||
REST API View set.
|
REST API View set.
|
||||||
The djangorestframework plugin will get all `TransactionTemplate` objects, serialize it to JSON with the given serializer,
|
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()
|
queryset = TransactionTemplate.objects.all()
|
||||||
serializer_class = TransactionTemplateSerializer
|
serializer_class = TransactionTemplateSerializer
|
||||||
filter_backends = [DjangoFilterBackend]
|
filter_backends = [SearchFilter, DjangoFilterBackend]
|
||||||
filterset_fields = ['name', 'amount', 'display', 'category', ]
|
filterset_fields = ['name', 'amount', 'display', 'category', ]
|
||||||
|
search_fields = ['$name', ]
|
||||||
|
|
||||||
|
|
||||||
class TransactionViewSet(ReadProtectedModelViewSet):
|
class TransactionViewSet(ReadProtectedModelViewSet):
|
||||||
|
|
|
@ -9,7 +9,7 @@ from django_tables2.utils import A
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from .models.notes import Alias
|
from .models.notes import Alias
|
||||||
from .models.transactions import Transaction
|
from .models.transactions import Transaction, TransactionTemplate
|
||||||
from .templatetags.pretty_money import pretty_money
|
from .templatetags.pretty_money import pretty_money
|
||||||
|
|
||||||
|
|
||||||
|
@ -57,6 +57,12 @@ class HistoryTable(tables.Table):
|
||||||
return "✔" if value else "✖"
|
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 AliasTable(tables.Table):
|
||||||
class Meta:
|
class Meta:
|
||||||
attrs = {
|
attrs = {
|
||||||
|
@ -69,9 +75,41 @@ class AliasTable(tables.Table):
|
||||||
|
|
||||||
show_header = False
|
show_header = False
|
||||||
name = tables.Column(attrs={'td': {'class': 'text-center'}})
|
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',
|
delete = tables.LinkColumn('member:user_alias_delete',
|
||||||
args=[A('pk')],
|
args=[A('pk')],
|
||||||
attrs={
|
attrs={
|
||||||
'td': {'class': 'col-sm-2'},
|
'td': {'class': 'col-sm-2'},
|
||||||
'a': {'class': 'btn btn-danger'}},
|
'a': {'class': 'btn btn-danger'}},
|
||||||
text='delete', accessor='pk')
|
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)
|
||||||
|
|
|
@ -8,7 +8,7 @@ from .models import Note
|
||||||
|
|
||||||
app_name = 'note'
|
app_name = 'note'
|
||||||
urlpatterns = [
|
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/create/', views.TransactionTemplateCreateView.as_view(), name='template_create'),
|
||||||
path('buttons/update/<int:pk>/', views.TransactionTemplateUpdateView.as_view(), name='template_update'),
|
path('buttons/update/<int:pk>/', views.TransactionTemplateUpdateView.as_view(), name='template_update'),
|
||||||
path('buttons/', views.TransactionTemplateListView.as_view(), name='template_list'),
|
path('buttons/', views.TransactionTemplateListView.as_view(), name='template_list'),
|
||||||
|
|
|
@ -6,22 +6,25 @@ from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.utils.translation import gettext_lazy as _
|
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_tables2 import SingleTableView
|
||||||
|
from django.urls import reverse_lazy
|
||||||
from permission.backends import PermissionBackend
|
from permission.backends import PermissionBackend
|
||||||
|
|
||||||
from .forms import TransactionTemplateForm
|
from .forms import TransactionTemplateForm
|
||||||
from .models import Transaction, TransactionTemplate, Alias, RecurrentTransaction, NoteSpecial
|
from .models import Transaction, TransactionTemplate, Alias, RecurrentTransaction, NoteSpecial
|
||||||
from .models.transactions import SpecialTransaction
|
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"
|
template_name = "note/transaction_form.html"
|
||||||
|
|
||||||
|
model = Transaction
|
||||||
# Transaction history table
|
# Transaction history table
|
||||||
table_class = HistoryTable
|
table_class = HistoryTable
|
||||||
table_pagination = {"per_page": 50}
|
table_pagination = {"per_page": 50}
|
||||||
|
@ -46,13 +49,14 @@ class TransactionCreate(LoginRequiredMixin, SingleTableView):
|
||||||
|
|
||||||
class NoteAutocomplete(autocomplete.Select2QuerySetView):
|
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):
|
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.
|
When someone look for an :models:`note.Alias`, a query is sent to the dedicated API.
|
||||||
Cette fonction récupère la requête, et renvoie la liste filtrée des aliases.
|
This function handles the result and return a filtered list of aliases.
|
||||||
"""
|
"""
|
||||||
# Un utilisateur non connecté n'a accès à aucune information
|
# Un utilisateur non connecté n'a accès à aucune information
|
||||||
if not self.request.user.is_authenticated:
|
if not self.request.user.is_authenticated:
|
||||||
|
@ -81,6 +85,10 @@ class NoteAutocomplete(autocomplete.Select2QuerySetView):
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
def get_result_label(self, result):
|
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
|
# Gère l'affichage de l'alias dans la recherche
|
||||||
res = result.name
|
res = result.name
|
||||||
note_name = str(result.note)
|
note_name = str(result.note)
|
||||||
|
@ -89,7 +97,9 @@ class NoteAutocomplete(autocomplete.Select2QuerySetView):
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def get_result_value(self, result):
|
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)
|
return str(result.note.pk)
|
||||||
|
|
||||||
|
|
||||||
|
@ -99,14 +109,15 @@ class TransactionTemplateCreateView(LoginRequiredMixin, CreateView):
|
||||||
"""
|
"""
|
||||||
model = TransactionTemplate
|
model = TransactionTemplate
|
||||||
form_class = TransactionTemplateForm
|
form_class = TransactionTemplateForm
|
||||||
|
success_url = reverse_lazy('note:template_list')
|
||||||
|
|
||||||
|
|
||||||
class TransactionTemplateListView(LoginRequiredMixin, ListView):
|
class TransactionTemplateListView(LoginRequiredMixin, SingleTableView):
|
||||||
"""
|
"""
|
||||||
List TransactionsTemplates
|
List TransactionsTemplates
|
||||||
"""
|
"""
|
||||||
model = TransactionTemplate
|
model = TransactionTemplate
|
||||||
form_class = TransactionTemplateForm
|
table_class = ButtonTable
|
||||||
|
|
||||||
|
|
||||||
class TransactionTemplateUpdateView(LoginRequiredMixin, UpdateView):
|
class TransactionTemplateUpdateView(LoginRequiredMixin, UpdateView):
|
||||||
|
@ -114,11 +125,13 @@ class TransactionTemplateUpdateView(LoginRequiredMixin, UpdateView):
|
||||||
"""
|
"""
|
||||||
model = TransactionTemplate
|
model = TransactionTemplate
|
||||||
form_class = TransactionTemplateForm
|
form_class = TransactionTemplateForm
|
||||||
|
success_url = reverse_lazy('note:template_list')
|
||||||
|
|
||||||
|
|
||||||
class ConsoView(LoginRequiredMixin, SingleTableView):
|
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"
|
template_name = "note/conso_form.html"
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from rest_framework.permissions import DjangoObjectPermissions
|
from rest_framework.permissions import DjangoObjectPermissions
|
||||||
|
from .backends import PermissionBackend
|
||||||
|
|
||||||
SAFE_METHODS = ('HEAD', 'OPTIONS', )
|
SAFE_METHODS = ('HEAD', 'OPTIONS', )
|
||||||
|
|
||||||
|
@ -41,8 +42,8 @@ class StrongDjangoObjectPermissions(DjangoObjectPermissions):
|
||||||
user = request.user
|
user = request.user
|
||||||
|
|
||||||
perms = self.get_required_object_permissions(request.method, model_cls)
|
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
|
# If the user does not have permissions we need to determine if
|
||||||
# they have read permissions to see 403, or not, and simply see
|
# they have read permissions to see 403, or not, and simply see
|
||||||
# a 404 response.
|
# a 404 response.
|
||||||
|
|
|
@ -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>
|
<a class="nav-link" href="{% url 'note:consos' %}"><i class="fa fa-coffee"></i> {% trans 'Consumptions' %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% 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 %}
|
{% if "member.club"|not_empty_model_list %}
|
||||||
<li class="nav-item active">
|
<li class="nav-item active">
|
||||||
<a class="nav-link" href="{% url 'member:club_list' %}"><i class="fa fa-users"></i> {% trans 'Clubs' %}</a>
|
<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>
|
<a class="nav-link" href="#"><i class="fa fa-calendar"></i> {% trans 'Activities' %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% 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>
|
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="navbar-nav ml-auto">
|
<ul class="navbar-nav ml-auto">
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
|
|
|
@ -1,23 +1,79 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load pretty_money %}
|
{% load pretty_money %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load render_table from django_tables2 %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<div class="row justify-content-center mb-4">
|
||||||
<table class="table">
|
<div class="col-md-10 text-center">
|
||||||
<tr>
|
<h4>
|
||||||
<td>ID</td><td>Nom</td>
|
{% trans "search button" %}
|
||||||
<td>Destinataire</td>
|
</h4>
|
||||||
<td>Montant</td>
|
<input class="form-control mx-auto w-25" type="text" onkeyup="search_field_moved();return(false);" id="search_field"/>
|
||||||
<td>Catégorie</td>
|
<hr>
|
||||||
</tr>
|
<a class="btn btn-primary text-center my-4" href="{% url 'note:template_create' %}">Créer un bouton</a>
|
||||||
{% for object in object_list %}
|
</div>
|
||||||
<tr>
|
</div>
|
||||||
<td>{{object.pk}}</td>
|
<div class="row justify-content-center">
|
||||||
<td><a href="{{object.get_absolute_url}}">{{ object.name }}</a></td>
|
<div class="col-md-10">
|
||||||
<td>{{ object.destination }}</td>
|
<div class="card card-border shadow">
|
||||||
<td>{{ object.amount | pretty_money }}</td>
|
<div class="card-header text-center">
|
||||||
<td>{{ object.category }}</td>
|
<h5> {% trans "buttons listing "%}</h5>
|
||||||
</tr>
|
</div>
|
||||||
{% endfor %}
|
<div class="card-body px-0 py-0" id="buttons_table">
|
||||||
</table>
|
{% render_table table %}
|
||||||
<a class="btn btn-primary" href="{% url 'note:template_create' %}">Créer un bouton</a>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% 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 %}
|
{% endblock %}
|
||||||
|
|
Loading…
Reference in New Issue