2020-02-18 20:30:26 +00:00
|
|
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
2019-07-17 11:34:07 +00:00
|
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
2020-08-13 13:20:15 +00:00
|
|
|
|
2020-04-27 01:21:13 +00:00
|
|
|
import json
|
2019-07-17 11:34:07 +00:00
|
|
|
|
2020-04-06 05:06:52 +00:00
|
|
|
from django.conf import settings
|
2019-07-17 11:53:58 +00:00
|
|
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
2020-03-11 20:47:04 +00:00
|
|
|
from django.contrib.contenttypes.models import ContentType
|
2020-08-13 13:20:15 +00:00
|
|
|
from django.core.exceptions import PermissionDenied
|
2020-08-03 16:49:15 +00:00
|
|
|
from django.db.models import Q, F
|
2019-07-17 11:34:07 +00:00
|
|
|
from django.utils.translation import gettext_lazy as _
|
2020-08-03 16:49:15 +00:00
|
|
|
from django.views.generic import CreateView, UpdateView, DetailView
|
2020-03-09 19:59:04 +00:00
|
|
|
from django_tables2 import SingleTableView
|
2020-03-23 19:21:25 +00:00
|
|
|
from django.urls import reverse_lazy
|
2020-08-03 14:11:05 +00:00
|
|
|
from activity.models import Entry
|
2020-03-27 12:50:02 +00:00
|
|
|
from note_kfet.inputs import AmountInput
|
2020-03-20 13:43:35 +00:00
|
|
|
from permission.backends import PermissionBackend
|
2020-03-31 02:16:30 +00:00
|
|
|
from permission.views import ProtectQuerysetMixin
|
2020-03-20 01:14:43 +00:00
|
|
|
|
2020-08-03 16:49:15 +00:00
|
|
|
from .forms import TransactionTemplateForm, SearchTransactionForm
|
|
|
|
from .models import TemplateCategory, Transaction, TransactionTemplate, RecurrentTransaction, NoteSpecial, Note
|
2020-03-14 14:13:58 +00:00
|
|
|
from .models.transactions import SpecialTransaction
|
2020-03-23 14:35:24 +00:00
|
|
|
from .tables import HistoryTable, ButtonTable
|
2020-03-09 19:59:04 +00:00
|
|
|
|
2020-02-18 11:31:15 +00:00
|
|
|
|
2020-03-31 02:16:30 +00:00
|
|
|
class TransactionCreateView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
2019-07-17 11:34:07 +00:00
|
|
|
"""
|
2020-03-23 14:28:09 +00:00
|
|
|
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`
|
2019-07-17 11:34:07 +00:00
|
|
|
"""
|
2020-03-23 14:28:09 +00:00
|
|
|
template_name = "note/transaction_form.html"
|
2020-08-18 12:27:04 +00:00
|
|
|
# SingleTableView creates `context["table"]` we will load it with transaction history
|
2019-07-17 11:53:58 +00:00
|
|
|
model = Transaction
|
2020-03-16 11:11:16 +00:00
|
|
|
# Transaction history table
|
|
|
|
table_class = HistoryTable
|
2020-07-30 15:30:21 +00:00
|
|
|
extra_context = {"title": _("Transfer money")}
|
2020-03-16 11:11:16 +00:00
|
|
|
|
2020-03-31 02:16:30 +00:00
|
|
|
def get_queryset(self, **kwargs):
|
2020-08-18 12:27:04 +00:00
|
|
|
# retrieves only Transaction that user has the right to see.
|
2020-08-02 06:57:16 +00:00
|
|
|
return Transaction.objects.filter(
|
|
|
|
PermissionBackend.filter_queryset(self.request.user, Transaction, "view")
|
|
|
|
).order_by("-created_at").all()[:20]
|
2019-07-17 11:53:58 +00:00
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
context = super().get_context_data(**kwargs)
|
2020-03-27 12:50:02 +00:00
|
|
|
context['amount_widget'] = AmountInput(attrs={"id": "amount"})
|
2020-03-12 22:12:49 +00:00
|
|
|
context['polymorphic_ctype'] = ContentType.objects.get_for_model(Transaction).pk
|
2020-03-14 14:13:58 +00:00
|
|
|
context['special_polymorphic_ctype'] = ContentType.objects.get_for_model(SpecialTransaction).pk
|
2020-03-31 02:16:30 +00:00
|
|
|
context['special_types'] = NoteSpecial.objects\
|
|
|
|
.filter(PermissionBackend.filter_queryset(self.request.user, NoteSpecial, "view"))\
|
|
|
|
.order_by("special_type").all()
|
2020-02-21 17:28:21 +00:00
|
|
|
|
2020-04-06 06:58:39 +00:00
|
|
|
# Add a shortcut for entry page for open activities
|
2020-04-06 05:06:52 +00:00
|
|
|
if "activity" in settings.INSTALLED_APPS:
|
|
|
|
from activity.models import Activity
|
2020-08-03 14:11:05 +00:00
|
|
|
activities_open = Activity.objects.filter(open=True).filter(
|
|
|
|
PermissionBackend.filter_queryset(self.request.user, Activity, "view")).distinct().all()
|
|
|
|
context["activities_open"] = [a for a in activities_open
|
|
|
|
if PermissionBackend.check_perm(self.request.user,
|
|
|
|
"activity.add_entry",
|
|
|
|
Entry(activity=a,
|
|
|
|
note=self.request.user.note, ))]
|
2020-04-06 05:06:52 +00:00
|
|
|
|
2019-07-17 11:53:58 +00:00
|
|
|
return context
|
2019-08-11 17:54:18 +00:00
|
|
|
|
2020-02-08 22:24:49 +00:00
|
|
|
|
2020-03-31 02:16:30 +00:00
|
|
|
class TransactionTemplateCreateView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
|
2019-08-11 17:54:18 +00:00
|
|
|
"""
|
2020-04-06 06:58:39 +00:00
|
|
|
Create Transaction template
|
2019-08-11 17:54:18 +00:00
|
|
|
"""
|
|
|
|
model = TransactionTemplate
|
|
|
|
form_class = TransactionTemplateForm
|
2020-03-24 21:13:15 +00:00
|
|
|
success_url = reverse_lazy('note:template_list')
|
2020-07-30 15:30:21 +00:00
|
|
|
extra_context = {"title": _("Create new button")}
|
2020-02-18 11:31:15 +00:00
|
|
|
|
2020-03-24 23:03:48 +00:00
|
|
|
|
2020-03-31 02:16:30 +00:00
|
|
|
class TransactionTemplateListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
2019-08-11 17:54:18 +00:00
|
|
|
"""
|
2020-04-06 06:58:39 +00:00
|
|
|
List Transaction templates
|
2019-08-11 17:54:18 +00:00
|
|
|
"""
|
|
|
|
model = TransactionTemplate
|
2020-03-10 19:17:56 +00:00
|
|
|
table_class = ButtonTable
|
2020-07-30 15:30:21 +00:00
|
|
|
extra_context = {"title": _("Search button")}
|
2019-08-11 21:24:54 +00:00
|
|
|
|
2020-07-30 13:49:59 +00:00
|
|
|
def get_queryset(self, **kwargs):
|
|
|
|
"""
|
|
|
|
Filter the user list with the given pattern.
|
|
|
|
"""
|
|
|
|
qs = super().get_queryset().distinct()
|
|
|
|
if "search" in self.request.GET:
|
|
|
|
pattern = self.request.GET["search"]
|
2020-08-15 17:47:29 +00:00
|
|
|
qs = qs.filter(
|
|
|
|
Q(name__iregex="^" + pattern)
|
|
|
|
| Q(destination__club__name__iregex="^" + pattern)
|
|
|
|
| Q(category__name__iregex="^" + pattern)
|
|
|
|
| Q(description__iregex=pattern)
|
|
|
|
)
|
2020-07-30 13:49:59 +00:00
|
|
|
|
|
|
|
qs = qs.order_by('-display', 'category__name', 'destination__club__name', 'name')
|
|
|
|
|
|
|
|
return qs
|
|
|
|
|
2020-02-18 11:31:15 +00:00
|
|
|
|
2020-03-31 02:16:30 +00:00
|
|
|
class TransactionTemplateUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
2019-08-11 17:54:18 +00:00
|
|
|
"""
|
2020-04-06 06:58:39 +00:00
|
|
|
Update Transaction template
|
2019-08-11 17:54:18 +00:00
|
|
|
"""
|
|
|
|
model = TransactionTemplate
|
2020-02-04 00:18:03 +00:00
|
|
|
form_class = TransactionTemplateForm
|
2020-03-24 22:32:48 +00:00
|
|
|
success_url = reverse_lazy('note:template_list')
|
2020-07-30 15:30:21 +00:00
|
|
|
extra_context = {"title": _("Update button")}
|
2020-02-18 11:31:15 +00:00
|
|
|
|
2020-04-27 01:21:13 +00:00
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
context = super().get_context_data(**kwargs)
|
|
|
|
|
|
|
|
if "logs" in settings.INSTALLED_APPS:
|
|
|
|
from logs.models import Changelog
|
|
|
|
update_logs = Changelog.objects.filter(
|
|
|
|
model=ContentType.objects.get_for_model(TransactionTemplate),
|
|
|
|
instance_pk=self.object.pk,
|
|
|
|
action="edit",
|
|
|
|
)
|
|
|
|
price_history = []
|
|
|
|
for log in update_logs.all():
|
|
|
|
old_dict = json.loads(log.previous)
|
|
|
|
new_dict = json.loads(log.data)
|
2020-08-13 18:13:00 +00:00
|
|
|
if "amount" not in old_dict:
|
2020-08-13 18:04:46 +00:00
|
|
|
# The amount price of the button was not modified in this changelog
|
|
|
|
continue
|
2020-04-27 01:21:13 +00:00
|
|
|
old_price = old_dict["amount"]
|
|
|
|
new_price = new_dict["amount"]
|
|
|
|
if old_price != new_price:
|
|
|
|
price_history.append(dict(price=old_price, time=log.timestamp))
|
|
|
|
|
|
|
|
price_history.append(dict(price=self.object.amount, time=None))
|
|
|
|
|
|
|
|
price_history.reverse()
|
|
|
|
|
|
|
|
context["price_history"] = price_history
|
|
|
|
|
|
|
|
return context
|
|
|
|
|
2020-03-24 23:03:48 +00:00
|
|
|
|
2020-03-31 02:16:30 +00:00
|
|
|
class ConsoView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
2020-02-04 00:18:03 +00:00
|
|
|
"""
|
2020-03-23 14:28:09 +00:00
|
|
|
The Magic View that make people pay their beer and burgers.
|
2020-08-18 12:27:04 +00:00
|
|
|
(Most of the magic happens in the dark world of Javascript see `note_kfet/static/js/consos.js`)
|
2020-02-04 00:18:03 +00:00
|
|
|
"""
|
2020-03-31 02:16:30 +00:00
|
|
|
model = Transaction
|
2020-02-04 00:18:03 +00:00
|
|
|
template_name = "note/conso_form.html"
|
2020-07-30 15:30:21 +00:00
|
|
|
extra_context = {"title": _("Consumptions")}
|
2020-03-10 07:07:09 +00:00
|
|
|
|
|
|
|
# Transaction history table
|
|
|
|
table_class = HistoryTable
|
2020-02-04 00:18:03 +00:00
|
|
|
|
2020-08-13 13:20:15 +00:00
|
|
|
def dispatch(self, request, *args, **kwargs):
|
2020-08-15 21:27:58 +00:00
|
|
|
# Check that the user is authenticated
|
|
|
|
if not request.user.is_authenticated:
|
|
|
|
return self.handle_no_permission()
|
|
|
|
|
2020-08-13 13:20:15 +00:00
|
|
|
templates = TransactionTemplate.objects.filter(
|
|
|
|
PermissionBackend().filter_queryset(self.request.user, TransactionTemplate, "view")
|
|
|
|
)
|
|
|
|
if not templates.exists():
|
|
|
|
raise PermissionDenied(_("You can't see any button."))
|
|
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
|
2020-03-31 02:16:30 +00:00
|
|
|
def get_queryset(self, **kwargs):
|
2020-08-18 12:27:04 +00:00
|
|
|
"""
|
|
|
|
restrict to the transaction history the user can see.
|
|
|
|
"""
|
2020-08-02 06:57:16 +00:00
|
|
|
return Transaction.objects.filter(
|
|
|
|
PermissionBackend.filter_queryset(self.request.user, Transaction, "view")
|
|
|
|
).order_by("-created_at").all()[:20]
|
2020-02-04 00:18:03 +00:00
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
context = super().get_context_data(**kwargs)
|
2020-08-18 12:27:04 +00:00
|
|
|
|
2020-07-25 15:25:57 +00:00
|
|
|
categories = TemplateCategory.objects.order_by('name').all()
|
2020-08-18 12:27:04 +00:00
|
|
|
# for each category, find which transaction templates the user can see.
|
2020-07-25 15:25:57 +00:00
|
|
|
for category in categories:
|
|
|
|
category.templates_filtered = category.templates.filter(
|
|
|
|
PermissionBackend().filter_queryset(self.request.user, TransactionTemplate, "view")
|
|
|
|
).filter(display=True).order_by('name').all()
|
2020-08-18 12:27:04 +00:00
|
|
|
|
2020-07-25 15:25:57 +00:00
|
|
|
context['categories'] = [cat for cat in categories if cat.templates_filtered]
|
2020-08-18 12:27:04 +00:00
|
|
|
# some transactiontemplate are put forward to find them easily
|
2020-07-25 15:25:57 +00:00
|
|
|
context['highlighted'] = TransactionTemplate.objects.filter(highlighted=True).filter(
|
2020-03-20 01:14:43 +00:00
|
|
|
PermissionBackend().filter_queryset(self.request.user, TransactionTemplate, "view")
|
2020-07-25 15:25:57 +00:00
|
|
|
).order_by('name').all()
|
2020-03-19 19:37:48 +00:00
|
|
|
context['polymorphic_ctype'] = ContentType.objects.get_for_model(RecurrentTransaction).pk
|
2020-02-04 00:18:03 +00:00
|
|
|
|
|
|
|
return context
|
2020-08-03 16:49:15 +00:00
|
|
|
|
|
|
|
|
|
|
|
class TransactionSearchView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
|
|
|
model = Note
|
|
|
|
context_object_name = "note"
|
|
|
|
template_name = "note/search_transactions.html"
|
|
|
|
extra_context = {"title": _("Search transactions")}
|
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
context = super().get_context_data(**kwargs)
|
|
|
|
|
|
|
|
form = SearchTransactionForm(data=self.request.GET if self.request.GET else None)
|
|
|
|
context["form"] = form
|
|
|
|
|
|
|
|
form.full_clean()
|
2020-09-01 13:54:56 +00:00
|
|
|
data = form.cleaned_data if form.is_valid() else {}
|
2020-08-03 16:49:15 +00:00
|
|
|
|
|
|
|
transactions = Transaction.objects.annotate(total_amount=F("quantity") * F("amount")).filter(
|
|
|
|
PermissionBackend.filter_queryset(self.request.user, Transaction, "view"))\
|
|
|
|
.filter(Q(source=self.object) | Q(destination=self.object)).order_by('-created_at')
|
|
|
|
|
|
|
|
if "source" in data and data["source"]:
|
|
|
|
transactions = transactions.filter(source_id=data["source"].note_id)
|
|
|
|
if "destination" in data and data["destination"]:
|
|
|
|
transactions = transactions.filter(destination_id=data["destination"].note_id)
|
|
|
|
if "type" in data and data["type"]:
|
|
|
|
transactions = transactions.filter(polymorphic_ctype__in=data["type"])
|
|
|
|
if "reason" in data and data["reason"]:
|
|
|
|
transactions = transactions.filter(reason__iregex=data["reason"])
|
|
|
|
if "valid" in data and data["valid"]:
|
|
|
|
transactions = transactions.filter(valid=data["valid"])
|
|
|
|
if "amount_gte" in data and data["amount_gte"]:
|
|
|
|
transactions = transactions.filter(total_amount__gte=data["amount_gte"])
|
|
|
|
if "amount_lte" in data and data["amount_lte"]:
|
|
|
|
transactions = transactions.filter(total_amount__lte=data["amount_lte"])
|
|
|
|
if "created_after" in data and data["created_after"]:
|
|
|
|
transactions = transactions.filter(created_at__gte=data["created_after"])
|
|
|
|
if "created_before" in data and data["created_before"]:
|
|
|
|
transactions = transactions.filter(created_at__lte=data["created_before"])
|
|
|
|
|
|
|
|
table = HistoryTable(transactions)
|
2020-08-07 10:55:07 +00:00
|
|
|
table.paginate(per_page=100, page=self.request.GET.get("page", 1))
|
2020-08-03 16:49:15 +00:00
|
|
|
context["table"] = table
|
|
|
|
|
|
|
|
return context
|