nk20/apps/note/views.py

251 lines
11 KiB
Python
Raw Normal View History

Update 131 files - /apps/activity/api/serializers.py - /apps/activity/api/urls.py - /apps/activity/api/views.py - /apps/activity/tests/test_activities.py - /apps/activity/__init__.py - /apps/activity/admin.py - /apps/activity/apps.py - /apps/activity/forms.py - /apps/activity/tables.py - /apps/activity/urls.py - /apps/activity/views.py - /apps/api/__init__.py - /apps/api/apps.py - /apps/api/serializers.py - /apps/api/tests.py - /apps/api/urls.py - /apps/api/views.py - /apps/api/viewsets.py - /apps/logs/signals.py - /apps/logs/apps.py - /apps/logs/__init__.py - /apps/logs/api/serializers.py - /apps/logs/api/urls.py - /apps/logs/api/views.py - /apps/member/api/serializers.py - /apps/member/api/urls.py - /apps/member/api/views.py - /apps/member/templatetags/memberinfo.py - /apps/member/__init__.py - /apps/member/admin.py - /apps/member/apps.py - /apps/member/auth.py - /apps/member/forms.py - /apps/member/hashers.py - /apps/member/signals.py - /apps/member/tables.py - /apps/member/urls.py - /apps/member/views.py - /apps/note/api/serializers.py - /apps/note/api/urls.py - /apps/note/api/views.py - /apps/note/models/__init__.py - /apps/note/static/note/js/consos.js - /apps/note/templates/note/mails/negative_balance.txt - /apps/note/templatetags/getenv.py - /apps/note/templatetags/pretty_money.py - /apps/note/tests/test_transactions.py - /apps/note/__init__.py - /apps/note/admin.py - /apps/note/apps.py - /apps/note/forms.py - /apps/note/signals.py - /apps/note/tables.py - /apps/note/urls.py - /apps/note/views.py - /apps/permission/api/serializers.py - /apps/permission/api/urls.py - /apps/permission/api/views.py - /apps/permission/templatetags/perms.py - /apps/permission/tests/test_oauth2.py - /apps/permission/tests/test_permission_denied.py - /apps/permission/tests/test_permission_queries.py - /apps/permission/tests/test_rights_page.py - /apps/permission/__init__.py - /apps/permission/admin.py - /apps/permission/backends.py - /apps/permission/apps.py - /apps/permission/decorators.py - /apps/permission/permissions.py - /apps/permission/scopes.py - /apps/permission/signals.py - /apps/permission/tables.py - /apps/permission/urls.py - /apps/permission/views.py - /apps/registration/tests/test_registration.py - /apps/registration/__init__.py - /apps/registration/apps.py - /apps/registration/forms.py - /apps/registration/tables.py - /apps/registration/tokens.py - /apps/registration/urls.py - /apps/registration/views.py - /apps/treasury/api/serializers.py - /apps/treasury/api/urls.py - /apps/treasury/api/views.py - /apps/treasury/templatetags/escape_tex.py - /apps/treasury/tests/test_treasury.py - /apps/treasury/__init__.py - /apps/treasury/admin.py - /apps/treasury/apps.py - /apps/treasury/forms.py - /apps/treasury/signals.py - /apps/treasury/tables.py - /apps/treasury/urls.py - /apps/treasury/views.py - /apps/wei/api/serializers.py - /apps/wei/api/urls.py - /apps/wei/api/views.py - /apps/wei/forms/surveys/__init__.py - /apps/wei/forms/surveys/base.py - /apps/wei/forms/surveys/wei2021.py - /apps/wei/forms/surveys/wei2022.py - /apps/wei/forms/surveys/wei2023.py - /apps/wei/forms/__init__.py - /apps/wei/forms/registration.py - /apps/wei/management/commands/export_wei_registrations.py - /apps/wei/management/commands/import_scores.py - /apps/wei/management/commands/wei_algorithm.py - /apps/wei/templates/wei/weilist_sample.tex - /apps/wei/tests/test_wei_algorithm_2021.py - /apps/wei/tests/test_wei_algorithm_2022.py - /apps/wei/tests/test_wei_algorithm_2023.py - /apps/wei/tests/test_wei_registration.py - /apps/wei/__init__.py - /apps/wei/admin.py - /apps/wei/apps.py - /apps/wei/tables.py - /apps/wei/urls.py - /apps/wei/views.py - /note_kfet/settings/__init__.py - /note_kfet/settings/base.py - /note_kfet/settings/development.py - /note_kfet/settings/secrets_example.py - /note_kfet/static/js/base.js - /note_kfet/admin.py - /note_kfet/inputs.py - /note_kfet/middlewares.py - /note_kfet/urls.py - /note_kfet/views.py - /note_kfet/wsgi.py - /entrypoint.sh
2024-02-07 01:26:49 +00:00
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
2019-07-17 11:34:07 +00:00
# SPDX-License-Identifier: GPL-3.0-or-later
2020-04-27 01:21:13 +00:00
import json
2019-07-17 11:34:07 +00:00
from django.conf import settings
2019-07-17 11:53:58 +00:00
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied
from django.db.models import Q, F
2019-07-17 11:34:07 +00:00
from django.utils.translation import gettext_lazy as _
from django.views.generic import CreateView, UpdateView, DetailView
2020-03-23 19:21:25 +00:00
from django.urls import reverse_lazy
2023-10-25 18:01:48 +00:00
from django_tables2 import SingleTableView
2020-08-03 14:11:05 +00:00
from activity.models import Entry
2024-07-18 11:51:56 +00:00
from api.viewsets import is_regex
2020-03-20 13:43:35 +00:00
from permission.backends import PermissionBackend
from permission.views import ProtectQuerysetMixin
2023-10-25 18:01:48 +00:00
from note_kfet.inputs import AmountInput
2020-03-20 01:14:43 +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-02-18 11:31:15 +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
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, Transaction, "view")
2020-08-02 06:57:16 +00:00
).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)
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
context['special_types'] = NoteSpecial.objects\
.filter(PermissionBackend.filter_queryset(self.request, 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
if "activity" in settings.INSTALLED_APPS:
from activity.models import Activity
activities_open = Activity.objects.filter(open=True, activity_type__manage_entries=True).filter(
PermissionBackend.filter_queryset(self.request, Activity, "view")).distinct().all()
2020-08-03 14:11:05 +00:00
context["activities_open"] = [a for a in activities_open
if PermissionBackend.check_perm(self.request,
2020-08-03 14:11:05 +00:00
"activity.add_entry",
Entry(activity=a,
note=self.request.user.note, ))]
2019-07-17 11:53:58 +00:00
return context
2019-08-11 17:54:18 +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
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")}
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"]
2024-07-18 11:51:56 +00:00
# Check if this is a valid regex. If not, we won't check regex
valid_regex = is_regex(pattern)
suffix = "__iregex" if valid_regex else "__icontains"
qs = qs.filter(
2024-07-18 11:51:56 +00:00
Q(**{f"name{suffix}": pattern})
| Q(**{f"destination__club__name{suffix}": pattern})
| Q(**{f"category__name{suffix}": pattern})
| Q(**{f"description{suffix}": 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
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)
if "amount" not in old_dict:
# 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
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-09-09 14:42:45 +00:00
(Most of the magic happens in the dark world of Javascript see `static/note/js/consos.js`)
2020-02-04 00:18:03 +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
def dispatch(self, request, *args, **kwargs):
# Check that the user is authenticated
if not request.user.is_authenticated:
return self.handle_no_permission()
templates = TransactionTemplate.objects.filter(
PermissionBackend().filter_queryset(self.request, TransactionTemplate, "view")
)
if not templates.exists():
raise PermissionDenied(_("You can't see any button."))
return super().dispatch(request, *args, **kwargs)
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, Transaction, "view")
2020-08-02 06:57:16 +00:00
).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, TransactionTemplate, "view")
2020-07-25 15:25:57 +00:00
).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(
PermissionBackend().filter_queryset(self.request, TransactionTemplate, "view")
2020-07-25 15:25:57 +00:00
).order_by('name').all()
context['polymorphic_ctype'] = ContentType.objects.get_for_model(RecurrentTransaction).pk
2020-02-04 00:18:03 +00:00
2023-10-26 17:01:09 +00:00
context['all_buttons'] = TransactionTemplate.objects.filter(
PermissionBackend.filter_queryset(self.request, TransactionTemplate, "view")
).filter(display=True).order_by('name').all()
2023-10-25 18:01:48 +00:00
2020-02-04 00:18:03 +00:00
return context
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 {}
transactions = Transaction.objects.annotate(total_amount=F("quantity") * F("amount")).filter(
PermissionBackend.filter_queryset(self.request, 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"]:
2024-07-18 11:51:56 +00:00
# Check if this is a valid regex. If not, we won't check regex
valid_regex = is_regex(data["reason"])
suffix = "__iregex" if valid_regex else "__istartswith"
transactions = transactions.filter(Q(**{f"reason{suffix}": 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)
table.paginate(per_page=100, page=self.request.GET.get("page", 1))
context["table"] = table
return context