mirror of
https://github.com/ae-utbm/sith.git
synced 2024-12-23 00:01:16 +00:00
Mise à jour de février (#581)
Co-authored-by: Thomas Girod <thgirod@hotmail.com> Co-authored-by: Julien Constant <julienconstant190@gmail.com> Co-authored-by: Skia <skia@hya.sk>
This commit is contained in:
parent
b7f20fed6c
commit
dd3ad42eb5
@ -22,133 +22,127 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.urls import re_path
|
from django.urls import path
|
||||||
|
|
||||||
from accounting.views import *
|
from accounting.views import *
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# Accounting types
|
# Accounting types
|
||||||
re_path(
|
path(
|
||||||
r"^simple_type$",
|
"simple_type/",
|
||||||
SimplifiedAccountingTypeListView.as_view(),
|
SimplifiedAccountingTypeListView.as_view(),
|
||||||
name="simple_type_list",
|
name="simple_type_list",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^simple_type/create$",
|
"simple_type/create/",
|
||||||
SimplifiedAccountingTypeCreateView.as_view(),
|
SimplifiedAccountingTypeCreateView.as_view(),
|
||||||
name="simple_type_new",
|
name="simple_type_new",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^simple_type/(?P<type_id>[0-9]+)/edit$",
|
"simple_type/<int:type_id>/edit/",
|
||||||
SimplifiedAccountingTypeEditView.as_view(),
|
SimplifiedAccountingTypeEditView.as_view(),
|
||||||
name="simple_type_edit",
|
name="simple_type_edit",
|
||||||
),
|
),
|
||||||
# Accounting types
|
# Accounting types
|
||||||
re_path(r"^type$", AccountingTypeListView.as_view(), name="type_list"),
|
path("type/", AccountingTypeListView.as_view(), name="type_list"),
|
||||||
re_path(r"^type/create$", AccountingTypeCreateView.as_view(), name="type_new"),
|
path("type/create/", AccountingTypeCreateView.as_view(), name="type_new"),
|
||||||
re_path(
|
path(
|
||||||
r"^type/(?P<type_id>[0-9]+)/edit$",
|
"type/<int:type_id>/edit/",
|
||||||
AccountingTypeEditView.as_view(),
|
AccountingTypeEditView.as_view(),
|
||||||
name="type_edit",
|
name="type_edit",
|
||||||
),
|
),
|
||||||
# Bank accounts
|
# Bank accounts
|
||||||
re_path(r"^$", BankAccountListView.as_view(), name="bank_list"),
|
path("", BankAccountListView.as_view(), name="bank_list"),
|
||||||
re_path(r"^bank/create$", BankAccountCreateView.as_view(), name="bank_new"),
|
path("bank/create", BankAccountCreateView.as_view(), name="bank_new"),
|
||||||
re_path(
|
path(
|
||||||
r"^bank/(?P<b_account_id>[0-9]+)$",
|
"bank/<int:b_account_id>/",
|
||||||
BankAccountDetailView.as_view(),
|
BankAccountDetailView.as_view(),
|
||||||
name="bank_details",
|
name="bank_details",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^bank/(?P<b_account_id>[0-9]+)/edit$",
|
"bank/<int:b_account_id>/edit/",
|
||||||
BankAccountEditView.as_view(),
|
BankAccountEditView.as_view(),
|
||||||
name="bank_edit",
|
name="bank_edit",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^bank/(?P<b_account_id>[0-9]+)/delete$",
|
"bank/<int:b_account_id>/delete/",
|
||||||
BankAccountDeleteView.as_view(),
|
BankAccountDeleteView.as_view(),
|
||||||
name="bank_delete",
|
name="bank_delete",
|
||||||
),
|
),
|
||||||
# Club accounts
|
# Club accounts
|
||||||
re_path(r"^club/create$", ClubAccountCreateView.as_view(), name="club_new"),
|
path("club/create/", ClubAccountCreateView.as_view(), name="club_new"),
|
||||||
re_path(
|
path(
|
||||||
r"^club/(?P<c_account_id>[0-9]+)$",
|
"club/<int:c_account_id>/",
|
||||||
ClubAccountDetailView.as_view(),
|
ClubAccountDetailView.as_view(),
|
||||||
name="club_details",
|
name="club_details",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^club/(?P<c_account_id>[0-9]+)/edit$",
|
"club/<int:c_account_id>/edit/",
|
||||||
ClubAccountEditView.as_view(),
|
ClubAccountEditView.as_view(),
|
||||||
name="club_edit",
|
name="club_edit",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^club/(?P<c_account_id>[0-9]+)/delete$",
|
"club/<int:c_account_id>/delete/",
|
||||||
ClubAccountDeleteView.as_view(),
|
ClubAccountDeleteView.as_view(),
|
||||||
name="club_delete",
|
name="club_delete",
|
||||||
),
|
),
|
||||||
# Journals
|
# Journals
|
||||||
re_path(r"^journal/create$", JournalCreateView.as_view(), name="journal_new"),
|
path("journal/create/", JournalCreateView.as_view(), name="journal_new"),
|
||||||
re_path(
|
path(
|
||||||
r"^journal/(?P<j_id>[0-9]+)$",
|
"journal/<int:j_id>/",
|
||||||
JournalDetailView.as_view(),
|
JournalDetailView.as_view(),
|
||||||
name="journal_details",
|
name="journal_details",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^journal/(?P<j_id>[0-9]+)/edit$",
|
"journal/<int:j_id>/edit/",
|
||||||
JournalEditView.as_view(),
|
JournalEditView.as_view(),
|
||||||
name="journal_edit",
|
name="journal_edit",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^journal/(?P<j_id>[0-9]+)/delete$",
|
"journal/<int:j_id>/delete/",
|
||||||
JournalDeleteView.as_view(),
|
JournalDeleteView.as_view(),
|
||||||
name="journal_delete",
|
name="journal_delete",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^journal/(?P<j_id>[0-9]+)/statement/nature$",
|
"journal/<int:j_id>/statement/nature/",
|
||||||
JournalNatureStatementView.as_view(),
|
JournalNatureStatementView.as_view(),
|
||||||
name="journal_nature_statement",
|
name="journal_nature_statement",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^journal/(?P<j_id>[0-9]+)/statement/person$",
|
"journal/<int:j_id>/statement/person/",
|
||||||
JournalPersonStatementView.as_view(),
|
JournalPersonStatementView.as_view(),
|
||||||
name="journal_person_statement",
|
name="journal_person_statement",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^journal/(?P<j_id>[0-9]+)/statement/accounting$",
|
"journal/<int:j_id>/statement/accounting/",
|
||||||
JournalAccountingStatementView.as_view(),
|
JournalAccountingStatementView.as_view(),
|
||||||
name="journal_accounting_statement",
|
name="journal_accounting_statement",
|
||||||
),
|
),
|
||||||
# Operations
|
# Operations
|
||||||
re_path(
|
path(
|
||||||
r"^operation/create/(?P<j_id>[0-9]+)$",
|
"operation/create/<int:j_id>/",
|
||||||
OperationCreateView.as_view(),
|
OperationCreateView.as_view(),
|
||||||
name="op_new",
|
name="op_new",
|
||||||
),
|
),
|
||||||
re_path(
|
path("operation/<int:op_id>/", OperationEditView.as_view(), name="op_edit"),
|
||||||
r"^operation/(?P<op_id>[0-9]+)$", OperationEditView.as_view(), name="op_edit"
|
path("operation/<int:op_id>/pdf/", OperationPDFView.as_view(), name="op_pdf"),
|
||||||
),
|
|
||||||
re_path(
|
|
||||||
r"^operation/(?P<op_id>[0-9]+)/pdf$", OperationPDFView.as_view(), name="op_pdf"
|
|
||||||
),
|
|
||||||
# Companies
|
# Companies
|
||||||
re_path(r"^company/list$", CompanyListView.as_view(), name="co_list"),
|
path("company/list/", CompanyListView.as_view(), name="co_list"),
|
||||||
re_path(r"^company/create$", CompanyCreateView.as_view(), name="co_new"),
|
path("company/create/", CompanyCreateView.as_view(), name="co_new"),
|
||||||
re_path(r"^company/(?P<co_id>[0-9]+)$", CompanyEditView.as_view(), name="co_edit"),
|
path("company/<int:co_id>/", CompanyEditView.as_view(), name="co_edit"),
|
||||||
# Labels
|
# Labels
|
||||||
re_path(r"^label/new$", LabelCreateView.as_view(), name="label_new"),
|
path("label/new/", LabelCreateView.as_view(), name="label_new"),
|
||||||
re_path(
|
path(
|
||||||
r"^label/(?P<clubaccount_id>[0-9]+)$",
|
"label/<int:clubaccount_id>/",
|
||||||
LabelListView.as_view(),
|
LabelListView.as_view(),
|
||||||
name="label_list",
|
name="label_list",
|
||||||
),
|
),
|
||||||
re_path(
|
path("label/<int:label_id>/edit/", LabelEditView.as_view(), name="label_edit"),
|
||||||
r"^label/(?P<label_id>[0-9]+)/edit$", LabelEditView.as_view(), name="label_edit"
|
path(
|
||||||
),
|
"label/<int:label_id>/delete/",
|
||||||
re_path(
|
|
||||||
r"^label/(?P<label_id>[0-9]+)/delete$",
|
|
||||||
LabelDeleteView.as_view(),
|
LabelDeleteView.as_view(),
|
||||||
name="label_delete",
|
name="label_delete",
|
||||||
),
|
),
|
||||||
# User account
|
# User account
|
||||||
re_path(r"^refound/account$", RefoundAccountView.as_view(), name="refound_account"),
|
path("refound/account/", RefoundAccountView.as_view(), name="refound_account"),
|
||||||
]
|
]
|
||||||
|
86
club/urls.py
86
club/urls.py
@ -23,94 +23,84 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.urls import re_path
|
from django.urls import path
|
||||||
|
|
||||||
from club.views import *
|
from club.views import *
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
re_path(r"^$", ClubListView.as_view(), name="club_list"),
|
path("", ClubListView.as_view(), name="club_list"),
|
||||||
re_path(r"^new$", ClubCreateView.as_view(), name="club_new"),
|
path("new/", ClubCreateView.as_view(), name="club_new"),
|
||||||
re_path(r"^stats$", ClubStatView.as_view(), name="club_stats"),
|
path("stats/", ClubStatView.as_view(), name="club_stats"),
|
||||||
re_path(r"^(?P<club_id>[0-9]+)/$", ClubView.as_view(), name="club_view"),
|
path("<int:club_id>/", ClubView.as_view(), name="club_view"),
|
||||||
re_path(
|
path(
|
||||||
r"^(?P<club_id>[0-9]+)/rev/(?P<rev_id>[0-9]+)/$",
|
"<int:club_id>/rev/<int:rev_id>/",
|
||||||
ClubRevView.as_view(),
|
ClubRevView.as_view(),
|
||||||
name="club_view_rev",
|
name="club_view_rev",
|
||||||
),
|
),
|
||||||
re_path(
|
path("<int:club_id>/hist/", ClubPageHistView.as_view(), name="club_hist"),
|
||||||
r"^(?P<club_id>[0-9]+)/hist$", ClubPageHistView.as_view(), name="club_hist"
|
path("<int:club_id>/edit/", ClubEditView.as_view(), name="club_edit"),
|
||||||
),
|
path(
|
||||||
re_path(r"^(?P<club_id>[0-9]+)/edit$", ClubEditView.as_view(), name="club_edit"),
|
"<int:club_id>/edit/page/",
|
||||||
re_path(
|
|
||||||
r"^(?P<club_id>[0-9]+)/edit/page$",
|
|
||||||
ClubPageEditView.as_view(),
|
ClubPageEditView.as_view(),
|
||||||
name="club_edit_page",
|
name="club_edit_page",
|
||||||
),
|
),
|
||||||
re_path(
|
path("<int:club_id>/members/", ClubMembersView.as_view(), name="club_members"),
|
||||||
r"^(?P<club_id>[0-9]+)/members$", ClubMembersView.as_view(), name="club_members"
|
path(
|
||||||
),
|
"<int:club_id>/elderlies/",
|
||||||
re_path(
|
|
||||||
r"^(?P<club_id>[0-9]+)/elderlies$",
|
|
||||||
ClubOldMembersView.as_view(),
|
ClubOldMembersView.as_view(),
|
||||||
name="club_old_members",
|
name="club_old_members",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^(?P<club_id>[0-9]+)/sellings$",
|
"<int:club_id>/sellings/",
|
||||||
ClubSellingView.as_view(),
|
ClubSellingView.as_view(),
|
||||||
name="club_sellings",
|
name="club_sellings",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^(?P<club_id>[0-9]+)/sellings/csv$",
|
"<int:club_id>/sellings/csv/",
|
||||||
ClubSellingCSVView.as_view(),
|
ClubSellingCSVView.as_view(),
|
||||||
name="sellings_csv",
|
name="sellings_csv",
|
||||||
),
|
),
|
||||||
re_path(
|
path("<int:club_id>/prop/", ClubEditPropView.as_view(), name="club_prop"),
|
||||||
r"^(?P<club_id>[0-9]+)/prop$", ClubEditPropView.as_view(), name="club_prop"
|
path("<int:club_id>/tools/", ClubToolsView.as_view(), name="tools"),
|
||||||
),
|
path("<int:club_id>/mailing/", ClubMailingView.as_view(), name="mailing"),
|
||||||
re_path(r"^(?P<club_id>[0-9]+)/tools$", ClubToolsView.as_view(), name="tools"),
|
path(
|
||||||
re_path(
|
"<int:mailing_id>/mailing/generate/",
|
||||||
r"^(?P<club_id>[0-9]+)/mailing$", ClubMailingView.as_view(), name="mailing"
|
|
||||||
),
|
|
||||||
re_path(
|
|
||||||
r"^(?P<mailing_id>[0-9]+)/mailing/generate$",
|
|
||||||
MailingAutoGenerationView.as_view(),
|
MailingAutoGenerationView.as_view(),
|
||||||
name="mailing_generate",
|
name="mailing_generate",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^(?P<mailing_id>[0-9]+)/mailing/delete$",
|
"<int:mailing_id>/mailing/delete/",
|
||||||
MailingDeleteView.as_view(),
|
MailingDeleteView.as_view(),
|
||||||
name="mailing_delete",
|
name="mailing_delete",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^(?P<mailing_subscription_id>[0-9]+)/mailing/delete/subscription$",
|
"<int:mailing_subscription_id>/mailing/delete/subscription/",
|
||||||
MailingSubscriptionDeleteView.as_view(),
|
MailingSubscriptionDeleteView.as_view(),
|
||||||
name="mailing_subscription_delete",
|
name="mailing_subscription_delete",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^membership/(?P<membership_id>[0-9]+)/set_old$",
|
"membership/<int:membership_id>/set_old/",
|
||||||
MembershipSetOldView.as_view(),
|
MembershipSetOldView.as_view(),
|
||||||
name="membership_set_old",
|
name="membership_set_old",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^membership/(?P<membership_id>[0-9]+)/delete$",
|
"membership/<int:membership_id>/delete/",
|
||||||
MembershipDeleteView.as_view(),
|
MembershipDeleteView.as_view(),
|
||||||
name="membership_delete",
|
name="membership_delete",
|
||||||
),
|
),
|
||||||
re_path(
|
path("<int:club_id>/poster/", PosterListView.as_view(), name="poster_list"),
|
||||||
r"^(?P<club_id>[0-9]+)/poster$", PosterListView.as_view(), name="poster_list"
|
path(
|
||||||
),
|
"<int:club_id>/poster/create/",
|
||||||
re_path(
|
|
||||||
r"^(?P<club_id>[0-9]+)/poster/create$",
|
|
||||||
PosterCreateView.as_view(),
|
PosterCreateView.as_view(),
|
||||||
name="poster_create",
|
name="poster_create",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^(?P<club_id>[0-9]+)/poster/(?P<poster_id>[0-9]+)/edit$",
|
"<int:club_id>/poster/<int:poster_id>/edit/",
|
||||||
PosterEditView.as_view(),
|
PosterEditView.as_view(),
|
||||||
name="poster_edit",
|
name="poster_edit",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^(?P<club_id>[0-9]+)/poster/(?P<poster_id>[0-9]+)/delete$",
|
"<int:club_id>/poster/<int:poster_id>/delete/",
|
||||||
PosterDeleteView.as_view(),
|
PosterDeleteView.as_view(),
|
||||||
name="poster_delete",
|
name="poster_delete",
|
||||||
),
|
),
|
||||||
|
98
com/urls.py
98
com/urls.py
@ -22,104 +22,98 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.urls import re_path
|
from django.urls import path
|
||||||
|
|
||||||
from com.views import *
|
|
||||||
from club.views import MailingDeleteView
|
from club.views import MailingDeleteView
|
||||||
|
from com.views import *
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
re_path(r"^sith/edit/alert$", AlertMsgEditView.as_view(), name="alert_edit"),
|
path("sith/edit/alert/", AlertMsgEditView.as_view(), name="alert_edit"),
|
||||||
re_path(r"^sith/edit/info$", InfoMsgEditView.as_view(), name="info_edit"),
|
path("sith/edit/info/", InfoMsgEditView.as_view(), name="info_edit"),
|
||||||
re_path(
|
path(
|
||||||
r"^sith/edit/weekmail_destinations$",
|
"sith/edit/weekmail_destinations/",
|
||||||
WeekmailDestinationEditView.as_view(),
|
WeekmailDestinationEditView.as_view(),
|
||||||
name="weekmail_destinations",
|
name="weekmail_destinations",
|
||||||
),
|
),
|
||||||
re_path(r"^weekmail$", WeekmailEditView.as_view(), name="weekmail"),
|
path("weekmail/", WeekmailEditView.as_view(), name="weekmail"),
|
||||||
re_path(
|
path("weekmail/preview/", WeekmailPreviewView.as_view(), name="weekmail_preview"),
|
||||||
r"^weekmail/preview$", WeekmailPreviewView.as_view(), name="weekmail_preview"
|
path(
|
||||||
),
|
"weekmail/new_article/",
|
||||||
re_path(
|
|
||||||
r"^weekmail/new_article$",
|
|
||||||
WeekmailArticleCreateView.as_view(),
|
WeekmailArticleCreateView.as_view(),
|
||||||
name="weekmail_article",
|
name="weekmail_article",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^weekmail/article/(?P<article_id>[0-9]+)/delete$",
|
"weekmail/article/<int:article_id>/delete/",
|
||||||
WeekmailArticleDeleteView.as_view(),
|
WeekmailArticleDeleteView.as_view(),
|
||||||
name="weekmail_article_delete",
|
name="weekmail_article_delete",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^weekmail/article/(?P<article_id>[0-9]+)/edit$",
|
"weekmail/article/<int:article_id>/edit/",
|
||||||
WeekmailArticleEditView.as_view(),
|
WeekmailArticleEditView.as_view(),
|
||||||
name="weekmail_article_edit",
|
name="weekmail_article_edit",
|
||||||
),
|
),
|
||||||
re_path(r"^news$", NewsListView.as_view(), name="news_list"),
|
path("news/", NewsListView.as_view(), name="news_list"),
|
||||||
re_path(r"^news/admin$", NewsAdminListView.as_view(), name="news_admin_list"),
|
path("news/admin/", NewsAdminListView.as_view(), name="news_admin_list"),
|
||||||
re_path(r"^news/create$", NewsCreateView.as_view(), name="news_new"),
|
path("news/create/", NewsCreateView.as_view(), name="news_new"),
|
||||||
re_path(
|
path(
|
||||||
r"^news/(?P<news_id>[0-9]+)/delete$",
|
"news/<int:news_id>/delete/",
|
||||||
NewsDeleteView.as_view(),
|
NewsDeleteView.as_view(),
|
||||||
name="news_delete",
|
name="news_delete",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^news/(?P<news_id>[0-9]+)/moderate$",
|
"news/<int:news_id>/moderate/",
|
||||||
NewsModerateView.as_view(),
|
NewsModerateView.as_view(),
|
||||||
name="news_moderate",
|
name="news_moderate",
|
||||||
),
|
),
|
||||||
re_path(
|
path("news/<int:news_id>/edit/", NewsEditView.as_view(), name="news_edit"),
|
||||||
r"^news/(?P<news_id>[0-9]+)/edit$", NewsEditView.as_view(), name="news_edit"
|
path("news/<int:news_id>/", NewsDetailView.as_view(), name="news_detail"),
|
||||||
),
|
path("mailings/", MailingListAdminView.as_view(), name="mailing_admin"),
|
||||||
re_path(
|
path(
|
||||||
r"^news/(?P<news_id>[0-9]+)$", NewsDetailView.as_view(), name="news_detail"
|
"mailings/<int:mailing_id>/moderate/",
|
||||||
),
|
|
||||||
re_path(r"^mailings$", MailingListAdminView.as_view(), name="mailing_admin"),
|
|
||||||
re_path(
|
|
||||||
r"^mailings/(?P<mailing_id>[0-9]+)/moderate$",
|
|
||||||
MailingModerateView.as_view(),
|
MailingModerateView.as_view(),
|
||||||
name="mailing_moderate",
|
name="mailing_moderate",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^mailings/(?P<mailing_id>[0-9]+)/delete$",
|
"mailings/<int:mailing_id>/delete/",
|
||||||
MailingDeleteView.as_view(redirect_page="com:mailing_admin"),
|
MailingDeleteView.as_view(redirect_page="com:mailing_admin"),
|
||||||
name="mailing_delete",
|
name="mailing_delete",
|
||||||
),
|
),
|
||||||
re_path(r"^poster$", PosterListView.as_view(), name="poster_list"),
|
path("poster/", PosterListView.as_view(), name="poster_list"),
|
||||||
re_path(r"^poster/create$", PosterCreateView.as_view(), name="poster_create"),
|
path("poster/create/", PosterCreateView.as_view(), name="poster_create"),
|
||||||
re_path(
|
path(
|
||||||
r"^poster/(?P<poster_id>[0-9]+)/edit$",
|
"poster/<int:poster_id>/edit/",
|
||||||
PosterEditView.as_view(),
|
PosterEditView.as_view(),
|
||||||
name="poster_edit",
|
name="poster_edit",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^poster/(?P<poster_id>[0-9]+)/delete$",
|
"poster/<int:poster_id>/delete/",
|
||||||
PosterDeleteView.as_view(),
|
PosterDeleteView.as_view(),
|
||||||
name="poster_delete",
|
name="poster_delete",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^poster/moderate$",
|
"poster/moderate/",
|
||||||
PosterModerateListView.as_view(),
|
PosterModerateListView.as_view(),
|
||||||
name="poster_moderate_list",
|
name="poster_moderate_list",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^poster/(?P<object_id>[0-9]+)/moderate$",
|
"poster/<int:object_id>/moderate/",
|
||||||
PosterModerateView.as_view(),
|
PosterModerateView.as_view(),
|
||||||
name="poster_moderate",
|
name="poster_moderate",
|
||||||
),
|
),
|
||||||
re_path(r"^screen$", ScreenListView.as_view(), name="screen_list"),
|
path("screen/", ScreenListView.as_view(), name="screen_list"),
|
||||||
re_path(r"^screen/create$", ScreenCreateView.as_view(), name="screen_create"),
|
path("screen/create/", ScreenCreateView.as_view(), name="screen_create"),
|
||||||
re_path(
|
path(
|
||||||
r"^screen/(?P<screen_id>[0-9]+)/slideshow$",
|
"screen/<int:screen_id>/slideshow/",
|
||||||
ScreenSlideshowView.as_view(),
|
ScreenSlideshowView.as_view(),
|
||||||
name="screen_slideshow",
|
name="screen_slideshow",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^screen/(?P<screen_id>[0-9]+)/edit$",
|
"screen/<int:screen_id>/edit/",
|
||||||
ScreenEditView.as_view(),
|
ScreenEditView.as_view(),
|
||||||
name="screen_edit",
|
name="screen_edit",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^screen/(?P<screen_id>[0-9]+)/delete$",
|
"screen/<int:screen_id>/delete/",
|
||||||
ScreenDeleteView.as_view(),
|
ScreenDeleteView.as_view(),
|
||||||
name="screen_delete",
|
name="screen_delete",
|
||||||
),
|
),
|
||||||
|
35
core/converters.py
Normal file
35
core/converters.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
from core.models import Page
|
||||||
|
|
||||||
|
|
||||||
|
class FourDigitYearConverter:
|
||||||
|
regex = "[0-9]{4}"
|
||||||
|
|
||||||
|
def to_python(self, value):
|
||||||
|
return int(value)
|
||||||
|
|
||||||
|
def to_url(self, value):
|
||||||
|
return str(value).zfill(4)
|
||||||
|
|
||||||
|
|
||||||
|
class TwoDigitMonthConverter:
|
||||||
|
regex = "[0-9]{2}"
|
||||||
|
|
||||||
|
def to_python(self, value):
|
||||||
|
return int(value)
|
||||||
|
|
||||||
|
def to_url(self, value):
|
||||||
|
return str(value).zfill(2)
|
||||||
|
|
||||||
|
|
||||||
|
class BooleanStringConverter:
|
||||||
|
"""
|
||||||
|
Converter whose regex match either True or False
|
||||||
|
"""
|
||||||
|
|
||||||
|
regex = r"(True)|(False)"
|
||||||
|
|
||||||
|
def to_python(self, value):
|
||||||
|
return str(value) == "True"
|
||||||
|
|
||||||
|
def to_url(self, value):
|
||||||
|
return str(value)
|
File diff suppressed because one or more lines are too long
@ -1263,7 +1263,7 @@ u,
|
|||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
#bar_ui {
|
#bar-ui {
|
||||||
padding: 0.4em;
|
padding: 0.4em;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
@ -4,9 +4,6 @@
|
|||||||
|
|
||||||
<div id="page">
|
<div id="page">
|
||||||
<h3>{% trans %}404, Not Found{% endtrans %}</h3>
|
<h3>{% trans %}404, Not Found{% endtrans %}</h3>
|
||||||
<p class="alert alert-red">
|
|
||||||
{{ exception }}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -65,9 +65,15 @@
|
|||||||
<div id="header_bar">
|
<div id="header_bar">
|
||||||
<ul id="header_bars_infos">
|
<ul id="header_bars_infos">
|
||||||
{% cache 100 "counters_activity" %}
|
{% cache 100 "counters_activity" %}
|
||||||
{% for bar in Counter.objects.filter(type="BAR").all() %}
|
{% for bar in Counter.objects.annotate_has_barman(user).filter(type="BAR") %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ url('counter:activity', counter_id=bar.id) }}" style="padding: 0px">
|
{# If the user is a barman, we redirect him directly to the barman page
|
||||||
|
else we redirect him to the activity page #}
|
||||||
|
{% if bar.has_annotated_barman %}
|
||||||
|
<a href="{{ url('counter:details', counter_id=bar.id) }}" style="padding: 0">
|
||||||
|
{% else %}
|
||||||
|
<a href="{{ url('counter:activity', counter_id=bar.id) }}" style="padding: 0">
|
||||||
|
{% endif %}
|
||||||
{% if bar.is_inactive(): %}
|
{% if bar.is_inactive(): %}
|
||||||
<i class="fa fa-question" style="color: #f39c12"></i>
|
<i class="fa fa-question" style="color: #f39c12"></i>
|
||||||
{% elif bar.is_open(): %}
|
{% elif bar.is_open(): %}
|
||||||
|
@ -290,8 +290,7 @@ class MarkdownTest(TestCase):
|
|||||||
|
|
||||||
class PageHandlingTest(TestCase):
|
class PageHandlingTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
try:
|
self.root_group = Group.objects.create(name="root")
|
||||||
Group.objects.create(name="root")
|
|
||||||
u = User(
|
u = User(
|
||||||
username="root",
|
username="root",
|
||||||
last_name="",
|
last_name="",
|
||||||
@ -304,19 +303,27 @@ class PageHandlingTest(TestCase):
|
|||||||
u.set_password("plop")
|
u.set_password("plop")
|
||||||
u.save()
|
u.save()
|
||||||
self.client.login(username="root", password="plop")
|
self.client.login(username="root", password="plop")
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
|
|
||||||
def test_create_page_ok(self):
|
def test_create_page_ok(self):
|
||||||
"""
|
"""
|
||||||
Should create a page correctly
|
Should create a page correctly
|
||||||
"""
|
"""
|
||||||
self.client.post(
|
|
||||||
reverse("core:page_new"), {"parent": "", "name": "guy", "owner_group": 1}
|
response = self.client.post(
|
||||||
|
reverse("core:page_new"),
|
||||||
|
{"parent": "", "name": "guy", "owner_group": self.root_group.id},
|
||||||
)
|
)
|
||||||
|
self.assertRedirects(
|
||||||
|
response, reverse("core:page", kwargs={"page_name": "guy"})
|
||||||
|
)
|
||||||
|
self.assertTrue(Page.objects.filter(name="guy").exists())
|
||||||
|
|
||||||
response = self.client.get(reverse("core:page", kwargs={"page_name": "guy"}))
|
response = self.client.get(reverse("core:page", kwargs={"page_name": "guy"}))
|
||||||
self.assertTrue(response.status_code == 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertTrue('<a href="/page/guy/hist">' in str(response.content))
|
html = response.content.decode()
|
||||||
|
self.assertIn('<a href="/page/guy/hist/">', html)
|
||||||
|
self.assertIn('<a href="/page/guy/edit/">', html)
|
||||||
|
self.assertIn('<a href="/page/guy/prop/">', html)
|
||||||
|
|
||||||
def test_create_child_page_ok(self):
|
def test_create_child_page_ok(self):
|
||||||
"""
|
"""
|
||||||
@ -339,29 +346,25 @@ class PageHandlingTest(TestCase):
|
|||||||
"""
|
"""
|
||||||
Should display a page correctly
|
Should display a page correctly
|
||||||
"""
|
"""
|
||||||
parent = Page(name="guy", owner_group=Group.objects.filter(id=1).first())
|
parent = Page(name="guy", owner_group=self.root_group)
|
||||||
parent.save(force_lock=True)
|
parent.save(force_lock=True)
|
||||||
page = Page(
|
page = Page(name="bibou", owner_group=self.root_group, parent=parent)
|
||||||
name="bibou", owner_group=Group.objects.filter(id=1).first(), parent=parent
|
|
||||||
)
|
|
||||||
page.save(force_lock=True)
|
page.save(force_lock=True)
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
reverse("core:page", kwargs={"page_name": "guy/bibou"})
|
reverse("core:page", kwargs={"page_name": "guy/bibou"})
|
||||||
)
|
)
|
||||||
self.assertTrue(response.status_code == 200)
|
self.assertTrue(response.status_code == 200)
|
||||||
self.assertTrue(
|
html = response.content.decode()
|
||||||
'<a href="/page/guy/bibou/edit">\\xc3\\x89diter</a>'
|
self.assertIn('<a href="/page/guy/bibou/edit/">', html)
|
||||||
in str(response.content)
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_access_page_not_found(self):
|
def test_access_page_not_found(self):
|
||||||
"""
|
"""
|
||||||
Should not display a page correctly
|
Should not display a page correctly
|
||||||
"""
|
"""
|
||||||
response = self.client.get(reverse("core:page", kwargs={"page_name": "swagg"}))
|
response = self.client.get(reverse("core:page", kwargs={"page_name": "swagg"}))
|
||||||
response = self.client.get("/page/swagg/")
|
|
||||||
self.assertTrue(response.status_code == 200)
|
self.assertTrue(response.status_code == 200)
|
||||||
self.assertTrue('<a href="/page/create?page=swagg">' in str(response.content))
|
html = response.content.decode()
|
||||||
|
self.assertIn('<a href="/page/create/?page=swagg">', html)
|
||||||
|
|
||||||
def test_create_page_markdown_safe(self):
|
def test_create_page_markdown_safe(self):
|
||||||
"""
|
"""
|
||||||
|
181
core/urls.py
181
core/urls.py
@ -23,40 +23,46 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.urls import re_path, path
|
from django.urls import path, re_path, register_converter
|
||||||
|
|
||||||
from core.views import *
|
from core.views import *
|
||||||
|
from core.converters import (
|
||||||
|
FourDigitYearConverter,
|
||||||
|
TwoDigitMonthConverter,
|
||||||
|
BooleanStringConverter,
|
||||||
|
)
|
||||||
|
|
||||||
|
register_converter(FourDigitYearConverter, "yyyy")
|
||||||
|
register_converter(TwoDigitMonthConverter, "mm")
|
||||||
|
register_converter(BooleanStringConverter, "bool")
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
re_path(r"^$", index, name="index"),
|
path("", index, name="index"),
|
||||||
re_path(r"^to_markdown$", ToMarkdownView.as_view(), name="to_markdown"),
|
path("to_markdown/", ToMarkdownView.as_view(), name="to_markdown"),
|
||||||
re_path(r"^notifications$", NotificationList.as_view(), name="notification_list"),
|
path("notifications/", NotificationList.as_view(), name="notification_list"),
|
||||||
re_path(r"^notification/(?P<notif_id>[0-9]+)$", notification, name="notification"),
|
path("notification/<int:notif_id>/", notification, name="notification"),
|
||||||
# Search
|
# Search
|
||||||
re_path(r"^search/$", search_view, name="search"),
|
path("search/", search_view, name="search"),
|
||||||
re_path(r"^search_json/$", search_json, name="search_json"),
|
path("search_json/", search_json, name="search_json"),
|
||||||
re_path(r"^search_user/$", search_user_json, name="search_user"),
|
path("search_user/", search_user_json, name="search_user"),
|
||||||
# Login and co
|
# Login and co
|
||||||
re_path(r"^login/$", SithLoginView.as_view(), name="login"),
|
path("login/", SithLoginView.as_view(), name="login"),
|
||||||
re_path(r"^logout/$", logout, name="logout"),
|
path("logout/", logout, name="logout"),
|
||||||
re_path(
|
path("password_change/", SithPasswordChangeView.as_view(), name="password_change"),
|
||||||
r"^password_change/$", SithPasswordChangeView.as_view(), name="password_change"
|
path(
|
||||||
),
|
"password_change/<int:user_id>/",
|
||||||
re_path(
|
|
||||||
r"^password_change/(?P<user_id>[0-9]+)$",
|
|
||||||
password_root_change,
|
password_root_change,
|
||||||
name="password_root_change",
|
name="password_root_change",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^password_change/done$",
|
"password_change/done/",
|
||||||
SithPasswordChangeDoneView.as_view(),
|
SithPasswordChangeDoneView.as_view(),
|
||||||
name="password_change_done",
|
name="password_change_done",
|
||||||
),
|
),
|
||||||
re_path(
|
path("password_reset/", SithPasswordResetView.as_view(), name="password_reset"),
|
||||||
r"^password_reset/$", SithPasswordResetView.as_view(), name="password_reset"
|
path(
|
||||||
),
|
"password_reset/done/",
|
||||||
re_path(
|
|
||||||
r"^password_reset/done$",
|
|
||||||
SithPasswordResetDoneView.as_view(),
|
SithPasswordResetDoneView.as_view(),
|
||||||
name="password_reset_done",
|
name="password_reset_done",
|
||||||
),
|
),
|
||||||
@ -65,110 +71,103 @@ urlpatterns = [
|
|||||||
SithPasswordResetConfirmView.as_view(),
|
SithPasswordResetConfirmView.as_view(),
|
||||||
name="password_reset_confirm",
|
name="password_reset_confirm",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^reset/done/$",
|
"reset/done/",
|
||||||
SithPasswordResetCompleteView.as_view(),
|
SithPasswordResetCompleteView.as_view(),
|
||||||
name="password_reset_complete",
|
name="password_reset_complete",
|
||||||
),
|
),
|
||||||
re_path(r"^register$", register, name="register"),
|
path("register/", register, name="register"),
|
||||||
# Group handling
|
# Group handling
|
||||||
re_path(r"^group/$", GroupListView.as_view(), name="group_list"),
|
path("group/", GroupListView.as_view(), name="group_list"),
|
||||||
re_path(r"^group/new/$", GroupCreateView.as_view(), name="group_new"),
|
path("group/new/", GroupCreateView.as_view(), name="group_new"),
|
||||||
re_path(
|
path("group/<int:group_id>/", GroupEditView.as_view(), name="group_edit"),
|
||||||
r"^group/(?P<group_id>[0-9]+)/$", GroupEditView.as_view(), name="group_edit"
|
path(
|
||||||
),
|
"group/<int:group_id>/delete/",
|
||||||
re_path(
|
|
||||||
r"^group/(?P<group_id>[0-9]+)/delete$",
|
|
||||||
GroupDeleteView.as_view(),
|
GroupDeleteView.as_view(),
|
||||||
name="group_delete",
|
name="group_delete",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^group/(?P<group_id>[0-9]+)/detail$",
|
"group/<int:group_id>/detail/",
|
||||||
GroupTemplateView.as_view(),
|
GroupTemplateView.as_view(),
|
||||||
name="group_detail",
|
name="group_detail",
|
||||||
),
|
),
|
||||||
# User views
|
# User views
|
||||||
re_path(r"^user/$", UserListView.as_view(), name="user_list"),
|
path("user/", UserListView.as_view(), name="user_list"),
|
||||||
re_path(
|
path(
|
||||||
r"^user/(?P<user_id>[0-9]+)/mini$",
|
"user/<int:user_id>/mini/",
|
||||||
UserMiniView.as_view(),
|
UserMiniView.as_view(),
|
||||||
name="user_profile_mini",
|
name="user_profile_mini",
|
||||||
),
|
),
|
||||||
re_path(r"^user/(?P<user_id>[0-9]+)/$", UserView.as_view(), name="user_profile"),
|
path("user/<int:user_id>/", UserView.as_view(), name="user_profile"),
|
||||||
re_path(
|
path(
|
||||||
r"^user/(?P<user_id>[0-9]+)/pictures$",
|
"user/<int:user_id>/pictures/",
|
||||||
UserPicturesView.as_view(),
|
UserPicturesView.as_view(),
|
||||||
name="user_pictures",
|
name="user_pictures",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^user/(?P<user_id>[0-9]+)/godfathers$",
|
"user/<int:user_id>/godfathers/",
|
||||||
UserGodfathersView.as_view(),
|
UserGodfathersView.as_view(),
|
||||||
name="user_godfathers",
|
name="user_godfathers",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^user/(?P<user_id>[0-9]+)/godfathers/tree$",
|
"user/<int:user_id>/godfathers/tree/",
|
||||||
UserGodfathersTreeView.as_view(),
|
UserGodfathersTreeView.as_view(),
|
||||||
name="user_godfathers_tree",
|
name="user_godfathers_tree",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^user/(?P<user_id>[0-9]+)/godfathers/tree/pict$",
|
"user/<int:user_id>/godfathers/tree/pict/",
|
||||||
UserGodfathersTreePictureView.as_view(),
|
UserGodfathersTreePictureView.as_view(),
|
||||||
name="user_godfathers_tree_pict",
|
name="user_godfathers_tree_pict",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^user/(?P<user_id>[0-9]+)/godfathers/(?P<godfather_id>[0-9]+)/(?P<is_father>(True)|(False))/delete$",
|
"user/<int:user_id>/godfathers/<int:godfather_id>/<bool:is_father>/delete/",
|
||||||
DeleteUserGodfathers,
|
delete_user_godfather,
|
||||||
name="user_godfathers_delete",
|
name="user_godfathers_delete",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^user/(?P<user_id>[0-9]+)/edit$",
|
"user/<int:user_id>/edit/",
|
||||||
UserUpdateProfileView.as_view(),
|
UserUpdateProfileView.as_view(),
|
||||||
name="user_edit",
|
name="user_edit",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^user/(?P<user_id>[0-9]+)/profile_upload$",
|
"user/<int:user_id>/profile_upload/",
|
||||||
UserUploadProfilePictView.as_view(),
|
UserUploadProfilePictView.as_view(),
|
||||||
name="user_profile_upload",
|
name="user_profile_upload",
|
||||||
),
|
),
|
||||||
re_path(
|
path("user/<int:user_id>/clubs/", UserClubView.as_view(), name="user_clubs"),
|
||||||
r"^user/(?P<user_id>[0-9]+)/clubs$", UserClubView.as_view(), name="user_clubs"
|
path(
|
||||||
),
|
"user/<int:user_id>/prefs/",
|
||||||
re_path(
|
|
||||||
r"^user/(?P<user_id>[0-9]+)/prefs$",
|
|
||||||
UserPreferencesView.as_view(),
|
UserPreferencesView.as_view(),
|
||||||
name="user_prefs",
|
name="user_prefs",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^user/(?P<user_id>[0-9]+)/groups$",
|
"user/<int:user_id>/groups/",
|
||||||
UserUpdateGroupView.as_view(),
|
UserUpdateGroupView.as_view(),
|
||||||
name="user_groups",
|
name="user_groups",
|
||||||
),
|
),
|
||||||
re_path(r"^user/tools/$", UserToolsView.as_view(), name="user_tools"),
|
path("user/tools/", UserToolsView.as_view(), name="user_tools"),
|
||||||
re_path(
|
path(
|
||||||
r"^user/(?P<user_id>[0-9]+)/account$",
|
"user/<int:user_id>/account/",
|
||||||
UserAccountView.as_view(),
|
UserAccountView.as_view(),
|
||||||
name="user_account",
|
name="user_account",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^user/(?P<user_id>[0-9]+)/account/(?P<year>[0-9]+)/(?P<month>[0-9]+)$",
|
"user/<int:user_id>/account/<yyyy:year>/<mm:month>/",
|
||||||
UserAccountDetailView.as_view(),
|
UserAccountDetailView.as_view(),
|
||||||
name="user_account_detail",
|
name="user_account_detail",
|
||||||
),
|
),
|
||||||
re_path(
|
path("user/<int:user_id>/stats/", UserStatsView.as_view(), name="user_stats"),
|
||||||
r"^user/(?P<user_id>[0-9]+)/stats$", UserStatsView.as_view(), name="user_stats"
|
path(
|
||||||
),
|
"user/<int:user_id>/gift/create/",
|
||||||
re_path(
|
|
||||||
r"^user/(?P<user_id>[0-9]+)/gift/create$",
|
|
||||||
GiftCreateView.as_view(),
|
GiftCreateView.as_view(),
|
||||||
name="user_gift_create",
|
name="user_gift_create",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^user/(?P<user_id>[0-9]+)/gift/delete/(?P<gift_id>[0-9]+)/$",
|
"user/<int:user_id>/gift/delete/<int:gift_id>/",
|
||||||
GiftDeleteView.as_view(),
|
GiftDeleteView.as_view(),
|
||||||
name="user_gift_delete",
|
name="user_gift_delete",
|
||||||
),
|
),
|
||||||
# File views
|
# File views
|
||||||
# re_path(r'^file/add/(?P<popup>popup)?$', FileCreateView.as_view(), name='file_new'),
|
|
||||||
re_path(r"^file/(?P<popup>popup)?$", FileListView.as_view(), name="file_list"),
|
re_path(r"^file/(?P<popup>popup)?$", FileListView.as_view(), name="file_list"),
|
||||||
re_path(
|
re_path(
|
||||||
r"^file/(?P<file_id>[0-9]+)/(?P<popup>popup)?$",
|
r"^file/(?P<file_id>[0-9]+)/(?P<popup>popup)?$",
|
||||||
@ -190,43 +189,43 @@ urlpatterns = [
|
|||||||
FileDeleteView.as_view(),
|
FileDeleteView.as_view(),
|
||||||
name="file_delete",
|
name="file_delete",
|
||||||
),
|
),
|
||||||
re_path(r"^file/moderation$", FileModerationView.as_view(), name="file_moderation"),
|
path("file/moderation/", FileModerationView.as_view(), name="file_moderation"),
|
||||||
re_path(
|
path(
|
||||||
r"^file/(?P<file_id>[0-9]+)/moderate$",
|
"file/<int:file_id>/moderate/",
|
||||||
FileModerateView.as_view(),
|
FileModerateView.as_view(),
|
||||||
name="file_moderate",
|
name="file_moderate",
|
||||||
),
|
),
|
||||||
re_path(r"^file/(?P<file_id>[0-9]+)/download$", send_file, name="download"),
|
path("file/<int:file_id>/download/", send_file, name="download"),
|
||||||
# Page views
|
# Page views
|
||||||
re_path(r"^page/$", PageListView.as_view(), name="page_list"),
|
path("page/", PageListView.as_view(), name="page_list"),
|
||||||
re_path(r"^page/create$", PageCreateView.as_view(), name="page_new"),
|
path("page/create/", PageCreateView.as_view(), name="page_new"),
|
||||||
re_path(
|
path(
|
||||||
r"^page/(?P<page_id>[0-9]*)/delete$",
|
"page/<int:page_id>/delete/",
|
||||||
PageDeleteView.as_view(),
|
PageDeleteView.as_view(),
|
||||||
name="page_delete",
|
name="page_delete",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^page/(?P<page_name>([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/edit$",
|
"page/<path:page_name>/edit/",
|
||||||
PageEditView.as_view(),
|
PageEditView.as_view(),
|
||||||
name="page_edit",
|
name="page_edit",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^page/(?P<page_name>([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/prop$",
|
"page/<path:page_name>/prop/",
|
||||||
PagePropView.as_view(),
|
PagePropView.as_view(),
|
||||||
name="page_prop",
|
name="page_prop",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^page/(?P<page_name>([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/hist$",
|
"page/<path:page_name>/hist/",
|
||||||
PageHistView.as_view(),
|
PageHistView.as_view(),
|
||||||
name="page_hist",
|
name="page_hist",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^page/(?P<page_name>([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/rev/(?P<rev>[0-9]+)/",
|
"page/<path:page_name>/rev/<int:rev>/",
|
||||||
PageRevView.as_view(),
|
PageRevView.as_view(),
|
||||||
name="page_rev",
|
name="page_rev",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^page/(?P<page_name>([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/$",
|
"page/<path:page_name>/",
|
||||||
PageView.as_view(),
|
PageView.as_view(),
|
||||||
name="page",
|
name="page",
|
||||||
),
|
),
|
||||||
|
@ -207,9 +207,7 @@ class UserTabsMixin(TabedViewMixin):
|
|||||||
"name": _("Pictures"),
|
"name": _("Pictures"),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
if (
|
if settings.SITH_ENABLE_GALAXY and self.request.user.was_subscribed:
|
||||||
False and self.request.user.was_subscribed
|
|
||||||
): # TODO: display galaxy once it's ready
|
|
||||||
tab_list.append(
|
tab_list.append(
|
||||||
{
|
{
|
||||||
"url": reverse("galaxy:user", kwargs={"user_id": user.id}),
|
"url": reverse("galaxy:user", kwargs={"user_id": user.id}),
|
||||||
@ -330,16 +328,16 @@ class UserPicturesView(UserTabsMixin, CanViewMixin, DetailView):
|
|||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
|
|
||||||
def DeleteUserGodfathers(request, user_id, godfather_id, is_father):
|
def delete_user_godfather(request, user_id, godfather_id, is_father):
|
||||||
user = User.objects.get(id=user_id)
|
user_is_admin = request.user.is_root or request.user.is_board_member
|
||||||
if (user == request.user) or request.user.is_root or request.user.is_board_member:
|
if user_id != request.user.id and not user_is_admin:
|
||||||
ud = get_object_or_404(User, id=godfather_id)
|
raise PermissionDenied()
|
||||||
if is_father == "True":
|
user = get_object_or_404(User, id=user_id)
|
||||||
user.godfathers.remove(ud)
|
to_remove = get_object_or_404(User, id=godfather_id)
|
||||||
|
if is_father:
|
||||||
|
user.godfathers.remove(to_remove)
|
||||||
else:
|
else:
|
||||||
user.godchildren.remove(ud)
|
user.godchildren.remove(to_remove)
|
||||||
else:
|
|
||||||
raise PermissionDenied
|
|
||||||
return redirect("core:user_godfathers", user_id=user_id)
|
return redirect("core:user_godfathers", user_id=user_id)
|
||||||
|
|
||||||
|
|
||||||
|
21
counter/migrations/0020_auto_20221215_1709.py
Normal file
21
counter/migrations/0020_auto_20221215_1709.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Generated by Django 3.2.16 on 2022-12-15 16:09
|
||||||
|
|
||||||
|
import accounting.models
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("counter", "0019_billinginfo"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="customer",
|
||||||
|
name="amount",
|
||||||
|
field=accounting.models.CurrencyField(
|
||||||
|
decimal_places=2, default=0, max_digits=12, verbose_name="amount"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -21,8 +21,13 @@
|
|||||||
# Place - Suite 330, Boston, MA 02111-1307, USA.
|
# Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
from __future__ import annotations
|
||||||
|
from django.db.models import Sum, F
|
||||||
|
|
||||||
|
from typing import Tuple
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.db.models import OuterRef, Exists
|
||||||
from django.db.models.functions import Length
|
from django.db.models.functions import Length
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
@ -57,7 +62,7 @@ class Customer(models.Model):
|
|||||||
|
|
||||||
user = models.OneToOneField(User, primary_key=True, on_delete=models.CASCADE)
|
user = models.OneToOneField(User, primary_key=True, on_delete=models.CASCADE)
|
||||||
account_id = models.CharField(_("account id"), max_length=10, unique=True)
|
account_id = models.CharField(_("account id"), max_length=10, unique=True)
|
||||||
amount = CurrencyField(_("amount"))
|
amount = CurrencyField(_("amount"), default=0)
|
||||||
recorded_products = models.IntegerField(_("recorded product"), default=0)
|
recorded_products = models.IntegerField(_("recorded product"), default=0)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -86,20 +91,32 @@ class Customer(models.Model):
|
|||||||
about the relation between a User (not a Customer,
|
about the relation between a User (not a Customer,
|
||||||
don't mix them) and a Product.
|
don't mix them) and a Product.
|
||||||
"""
|
"""
|
||||||
return self.user.subscriptions.last() and (
|
subscription = self.user.subscriptions.order_by("subscription_end").last()
|
||||||
date.today()
|
time_diff = date.today() - subscription.subscription_end
|
||||||
- self.user.subscriptions.order_by("subscription_end")
|
return subscription is not None and time_diff < timedelta(days=90)
|
||||||
.last()
|
|
||||||
.subscription_end
|
|
||||||
) < timedelta(days=90)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def new_for_user(cls, user: User):
|
def get_or_create(cls, user: User) -> Tuple[Customer, bool]:
|
||||||
"""
|
"""
|
||||||
Create a new Customer instance for the user given in parameter without saving it
|
Work in pretty much the same way as the usual get_or_create method,
|
||||||
The account if is automatically generated and the amount set at 0
|
but with the default field replaced by some under the hood.
|
||||||
|
|
||||||
|
If the user has an account, return it as is.
|
||||||
|
Else create a new account with no money on it and a new unique account id
|
||||||
|
|
||||||
|
Example : ::
|
||||||
|
|
||||||
|
user = User.objects.get(pk=1)
|
||||||
|
account, created = Customer.get_or_create(user)
|
||||||
|
if created:
|
||||||
|
print(f"created a new account with id {account.id}")
|
||||||
|
else:
|
||||||
|
print(f"user has already an account, with {account.id} € on it"
|
||||||
"""
|
"""
|
||||||
# account_id are number with a letter appended
|
if hasattr(user, "customer"):
|
||||||
|
return user.customer, False
|
||||||
|
|
||||||
|
# account_id are always a number with a letter appended
|
||||||
account_id = (
|
account_id = (
|
||||||
Customer.objects.order_by(Length("account_id"), "account_id")
|
Customer.objects.order_by(Length("account_id"), "account_id")
|
||||||
.values("account_id")
|
.values("account_id")
|
||||||
@ -107,14 +124,19 @@ class Customer(models.Model):
|
|||||||
)
|
)
|
||||||
if account_id is None:
|
if account_id is None:
|
||||||
# legacy from the old site
|
# legacy from the old site
|
||||||
return cls(user=user, account_id="1504a", amount=0)
|
account = cls.objects.create(user=user, account_id="1504a")
|
||||||
account_id = account_id["account_id"]
|
return account, True
|
||||||
num = int(account_id[:-1])
|
|
||||||
while Customer.objects.filter(account_id=account_id).exists():
|
|
||||||
num += 1
|
|
||||||
account_id = str(num) + random.choice(string.ascii_lowercase)
|
|
||||||
|
|
||||||
return cls(user=user, account_id=account_id, amount=0)
|
account_id = account_id["account_id"]
|
||||||
|
account_num = int(account_id[:-1])
|
||||||
|
while Customer.objects.filter(account_id=account_id).exists():
|
||||||
|
# when entering the first iteration, we are using an already existing account id
|
||||||
|
# so the loop should always execute at least one time
|
||||||
|
account_num += 1
|
||||||
|
account_id = f"{account_num}{random.choice(string.ascii_lowercase)}"
|
||||||
|
|
||||||
|
account = cls.objects.create(user=user, account_id=account_id)
|
||||||
|
return account, True
|
||||||
|
|
||||||
def save(self, allow_negative=False, is_selling=False, *args, **kwargs):
|
def save(self, allow_negative=False, is_selling=False, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
@ -127,11 +149,15 @@ class Customer(models.Model):
|
|||||||
super(Customer, self).save(*args, **kwargs)
|
super(Customer, self).save(*args, **kwargs)
|
||||||
|
|
||||||
def recompute_amount(self):
|
def recompute_amount(self):
|
||||||
self.amount = 0
|
refillings = self.refillings.aggregate(sum=Sum(F("amount")))["sum"]
|
||||||
for r in self.refillings.all():
|
self.amount = refillings if refillings is not None else 0
|
||||||
self.amount += r.amount
|
purchases = (
|
||||||
for s in self.buyings.filter(payment_method="SITH_ACCOUNT"):
|
self.buyings.filter(payment_method="SITH_ACCOUNT")
|
||||||
self.amount -= s.quantity * s.unit_price
|
.annotate(amount=F("quantity") * F("unit_price"))
|
||||||
|
.aggregate(sum=Sum(F("amount")))
|
||||||
|
)["sum"]
|
||||||
|
if purchases is not None:
|
||||||
|
self.amount -= purchases
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
@ -313,6 +339,32 @@ class Product(models.Model):
|
|||||||
return "%s (%s)" % (self.name, self.code)
|
return "%s (%s)" % (self.name, self.code)
|
||||||
|
|
||||||
|
|
||||||
|
class CounterQuerySet(models.QuerySet):
|
||||||
|
def annotate_has_barman(self, user: User) -> CounterQuerySet:
|
||||||
|
"""
|
||||||
|
Annotate the queryset with the `user_is_barman` field.
|
||||||
|
For each counter, this field has value True if the user
|
||||||
|
is a barman of this counter, else False.
|
||||||
|
|
||||||
|
:param user: the user we want to check if he is a barman
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
sli = User.objects.get(username="sli")
|
||||||
|
counters = (
|
||||||
|
Counter.objects
|
||||||
|
.annotate_has_barman(sli) # add the user_has_barman boolean field
|
||||||
|
.filter(has_annotated_barman=True) # keep only counters where this user is barman
|
||||||
|
)
|
||||||
|
print("Sli est barman dans les comptoirs suivants :")
|
||||||
|
for counter in counters:
|
||||||
|
print(f"- {counter.name}")
|
||||||
|
"""
|
||||||
|
subquery = user.counters.filter(pk=OuterRef("pk"))
|
||||||
|
# noinspection PyTypeChecker
|
||||||
|
return self.annotate(has_annotated_barman=Exists(subquery))
|
||||||
|
|
||||||
|
|
||||||
class Counter(models.Model):
|
class Counter(models.Model):
|
||||||
name = models.CharField(_("name"), max_length=30)
|
name = models.CharField(_("name"), max_length=30)
|
||||||
club = models.ForeignKey(
|
club = models.ForeignKey(
|
||||||
@ -337,6 +389,8 @@ class Counter(models.Model):
|
|||||||
)
|
)
|
||||||
token = models.CharField(_("token"), max_length=30, null=True, blank=True)
|
token = models.CharField(_("token"), max_length=30, null=True, blank=True)
|
||||||
|
|
||||||
|
objects = CounterQuerySet.as_manager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("counter")
|
verbose_name = _("counter")
|
||||||
|
|
||||||
@ -451,11 +505,11 @@ class Counter(models.Model):
|
|||||||
Show if the counter authorize the refilling with physic money
|
Show if the counter authorize the refilling with physic money
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if (
|
if self.type != "BAR":
|
||||||
self.id in SITH_COUNTER_OFFICES
|
return False
|
||||||
): # If the counter is the counters 'AE' or 'BdF', the refiling are authorized
|
if self.id in SITH_COUNTER_OFFICES:
|
||||||
|
# If the counter is either 'AE' or 'BdF', refills are authorized
|
||||||
return True
|
return True
|
||||||
|
|
||||||
is_ae_member = False
|
is_ae_member = False
|
||||||
ae = Club.objects.get(unix_name=SITH_MAIN_CLUB["unix_name"])
|
ae = Club.objects.get(unix_name=SITH_MAIN_CLUB["unix_name"])
|
||||||
for barman in self.get_barmen_list():
|
for barman in self.get_barmen_list():
|
||||||
|
78
counter/static/counter/js/counter_click.js
Normal file
78
counter/static/counter/js/counter_click.js
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
document.addEventListener('alpine:init', () => {
|
||||||
|
Alpine.data('counter', () => ({
|
||||||
|
basket: basket,
|
||||||
|
errors: [],
|
||||||
|
|
||||||
|
sum_basket() {
|
||||||
|
if (!this.basket || Object.keys(this.basket).length === 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const total = Object.values(this.basket)
|
||||||
|
.reduce((acc, cur) => acc + cur["qty"] * cur["price"], 0);
|
||||||
|
return total / 100;
|
||||||
|
},
|
||||||
|
|
||||||
|
async handle_code(event) {
|
||||||
|
const code = $(event.target).find("#code_field").val().toUpperCase();
|
||||||
|
if(["FIN", "ANN"].includes(code)) {
|
||||||
|
$(event.target).submit();
|
||||||
|
} else {
|
||||||
|
await this.handle_action(event);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async handle_action(event) {
|
||||||
|
const payload = $(event.target).serialize();
|
||||||
|
let request = new Request(click_api_url, {
|
||||||
|
method: "POST",
|
||||||
|
body: payload,
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'X-CSRFToken': csrf_token,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const response = await fetch(request);
|
||||||
|
const json = await response.json();
|
||||||
|
this.basket = json["basket"]
|
||||||
|
this.errors = json["errors"]
|
||||||
|
$('form.code_form #code_field').val("").focus();
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
$(function () {
|
||||||
|
/* Autocompletion in the code field */
|
||||||
|
const code_field = $("#code_field");
|
||||||
|
|
||||||
|
let quantity = "";
|
||||||
|
let search = "";
|
||||||
|
code_field.autocomplete({
|
||||||
|
select: function (event, ui) {
|
||||||
|
event.preventDefault();
|
||||||
|
code_field.val(quantity + ui.item.value);
|
||||||
|
},
|
||||||
|
focus: function (event, ui) {
|
||||||
|
event.preventDefault();
|
||||||
|
code_field.val(quantity + ui.item.value);
|
||||||
|
},
|
||||||
|
source: function (request, response) {
|
||||||
|
// by the dark magic of JS, parseInt("123abc") === 123
|
||||||
|
quantity = parseInt(request.term);
|
||||||
|
search = request.term.slice(quantity.toString().length)
|
||||||
|
let matcher = new RegExp($.ui.autocomplete.escapeRegex(search), "i");
|
||||||
|
response($.grep(products_autocomplete, function (value) {
|
||||||
|
value = value.tags;
|
||||||
|
return matcher.test(value);
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Accordion UI between basket and refills */
|
||||||
|
$("#click_form").accordion({
|
||||||
|
heightStyle: "content",
|
||||||
|
activate: () => $(".focus").focus(),
|
||||||
|
});
|
||||||
|
$("#products").tabs();
|
||||||
|
|
||||||
|
code_field.focus();
|
||||||
|
});
|
@ -5,16 +5,22 @@
|
|||||||
{{ counter }}
|
{{ counter }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block additional_js %}
|
||||||
|
<script src="{{ static('counter/js/counter_click.js') }}" defer></script>
|
||||||
|
<script src="{{ static('core/js/alpinejs.min.js') }}" defer></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block info_boxes %}
|
{% block info_boxes %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
{% block nav %}
|
{% block nav %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h4 id="click_interface">{{ counter }}</h4>
|
<h4 id="click_interface">{{ counter }}</h4>
|
||||||
|
|
||||||
<div id="bar_ui">
|
<div id="bar-ui" x-data="counter">
|
||||||
<noscript>
|
<noscript>
|
||||||
<p class="important">Javascript is required for the counter UI.</p>
|
<p class="important">Javascript is required for the counter UI.</p>
|
||||||
</noscript>
|
</noscript>
|
||||||
@ -28,7 +34,7 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="action" value="add_student_card">
|
<input type="hidden" name="action" value="add_student_card">
|
||||||
{% trans %}Add a student card{% endtrans %}
|
{% trans %}Add a student card{% endtrans %}
|
||||||
<input type="input" name="student_card_uid" />
|
<input type="text" name="student_card_uid"/>
|
||||||
{% if request.session['not_valid_student_card_uid'] %}
|
{% if request.session['not_valid_student_card_uid'] %}
|
||||||
<p><strong>{% trans %}This is not a valid student card UID{% endtrans %}</strong></p>
|
<p><strong>{% trans %}This is not a valid student card UID{% endtrans %}</strong></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -49,59 +55,67 @@
|
|||||||
<div id="click_form">
|
<div id="click_form">
|
||||||
<h5>{% trans %}Selling{% endtrans %}</h5>
|
<h5>{% trans %}Selling{% endtrans %}</h5>
|
||||||
<div>
|
<div>
|
||||||
|
{% set counter_click_url = url('counter:click', counter_id=counter.id, user_id=customer.user.id) %}
|
||||||
|
|
||||||
{% raw %}
|
{# Formulaire pour rechercher un produit en tapant son code dans une barre de recherche #}
|
||||||
<div class="important">
|
<form method="post" action=""
|
||||||
<p v-for="error in errors"><strong>{{ error }}</strong></p>
|
class="code_form" @submit.prevent="handle_code">
|
||||||
</div>
|
|
||||||
{% endraw %}
|
|
||||||
|
|
||||||
<form method="post" action="{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}" class="code_form" @submit.prevent="handle_code">
|
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="action" value="code">
|
<input type="hidden" name="action" value="code">
|
||||||
<input type="input" name="code" value="" class="focus" id="code_field"/>
|
<label for="code_field"></label>
|
||||||
|
<input type="text" name="code" value="" class="focus" id="code_field"/>
|
||||||
<input type="submit" value="{% trans %}Go{% endtrans %}"/>
|
<input type="submit" value="{% trans %}Go{% endtrans %}"/>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<template x-for="error in errors">
|
||||||
|
<div class="alert alert-red" x-text="error">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<p>{% trans %}Basket: {% endtrans %}</p>
|
<p>{% trans %}Basket: {% endtrans %}</p>
|
||||||
|
|
||||||
{% raw %}
|
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="p_info,p_id in basket">
|
<template x-for="[id, item] in Object.entries(basket)" :key="id">
|
||||||
|
<div>
|
||||||
<form method="post" action="" class="inline del_product_form" @submit.prevent="handle_action">
|
<form method="post" action="" class="inline del_product_form"
|
||||||
<input type="hidden" name="csrfmiddlewaretoken" v-bind:value="js_csrf_token">
|
@submit.prevent="handle_action">
|
||||||
|
{% csrf_token %}
|
||||||
<input type="hidden" name="action" value="del_product">
|
<input type="hidden" name="action" value="del_product">
|
||||||
<input type="hidden" name="product_id" v-bind:value="p_id">
|
<input type="hidden" name="product_id" :value="id">
|
||||||
<button type="submit"> - </button>
|
<input type="submit" value="-"/>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{{ p_info["qty"] + p_info["bonus_qty"] }}
|
<span x-text="item['qty'] + item['bonus_qty']"></span>
|
||||||
|
|
||||||
<form method="post" action="" class="inline add_product_form" @submit.prevent="handle_action">
|
<form method="post" action="" class="inline add_product_form"
|
||||||
<input type="hidden" name="csrfmiddlewaretoken" v-bind:value="js_csrf_token">
|
@submit.prevent="handle_action">
|
||||||
|
{% csrf_token %}
|
||||||
<input type="hidden" name="action" value="add_product">
|
<input type="hidden" name="action" value="add_product">
|
||||||
<input type="hidden" name="product_id" v-bind:value="p_id">
|
<input type="hidden" name="product_id" :value="id">
|
||||||
<button type="submit"> + </button>
|
<input type="submit" value="+">
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{{ products[p_id].name }}: {{ (p_info["qty"]*p_info["price"]/100).toLocaleString(undefined, { minimumFractionDigits: 2 }) }} € <span v-if="p_info['bonus_qty'] > 0">P</span>
|
<span x-text="products[id].name"></span> :
|
||||||
</li>
|
<span x-text="(item['qty'] * item['price'] / 100)
|
||||||
|
.toLocaleString(undefined, { minimumFractionDigits: 2 })">
|
||||||
|
</span> €
|
||||||
|
<template x-if="item['bonus_qty'] > 0">P</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</ul>
|
</ul>
|
||||||
<p>
|
<p>
|
||||||
<strong>Total: {{ sum_basket().toLocaleString(undefined, { minimumFractionDigits: 2 }) }} €</strong>
|
<strong>Total: </strong>
|
||||||
|
<strong x-text="sum_basket().toLocaleString(undefined, { minimumFractionDigits: 2 })"></strong>
|
||||||
|
<strong> €</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="important">
|
<form method="post"
|
||||||
<p v-for="error in errors"><strong>{{ error }}</strong></p>
|
action="{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}">
|
||||||
</div>
|
|
||||||
{% endraw %}
|
|
||||||
|
|
||||||
<form method="post" action="{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}">
|
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="action" value="finish">
|
<input type="hidden" name="action" value="finish">
|
||||||
<input type="submit" value="{% trans %}Finish{% endtrans %}"/>
|
<input type="submit" value="{% trans %}Finish{% endtrans %}"/>
|
||||||
</form>
|
</form>
|
||||||
<form method="post" action="{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}">
|
<form method="post"
|
||||||
|
action="{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="action" value="cancel">
|
<input type="hidden" name="action" value="cancel">
|
||||||
<input type="submit" value="{% trans %}Cancel{% endtrans %}"/>
|
<input type="submit" value="{% trans %}Cancel{% endtrans %}"/>
|
||||||
@ -110,7 +124,8 @@
|
|||||||
{% if (counter.type == 'BAR' and barmens_can_refill) %}
|
{% if (counter.type == 'BAR' and barmens_can_refill) %}
|
||||||
<h5>{% trans %}Refilling{% endtrans %}</h5>
|
<h5>{% trans %}Refilling{% endtrans %}</h5>
|
||||||
<div>
|
<div>
|
||||||
<form method="post" action="{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}">
|
<form method="post"
|
||||||
|
action="{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ refill_form.as_p() }}
|
{{ refill_form.as_p() }}
|
||||||
<input type="hidden" name="action" value="refill">
|
<input type="hidden" name="action" value="refill">
|
||||||
@ -130,95 +145,45 @@
|
|||||||
<div id="cat_{{ category|slugify }}">
|
<div id="cat_{{ category|slugify }}">
|
||||||
<h5>{{ category }}</h5>
|
<h5>{{ category }}</h5>
|
||||||
{% for p in categories[category] -%}
|
{% for p in categories[category] -%}
|
||||||
{% set file = None %}
|
<form method="post"
|
||||||
{% if p.icon %}
|
action="{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}"
|
||||||
{% set file = p.icon.url %}
|
class="form_button add_product_form" @submit.prevent="handle_action">
|
||||||
{% else %}
|
|
||||||
{% set file = static('core/img/na.gif') %}
|
|
||||||
{% endif %}
|
|
||||||
<form method="post" action="{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}" class="form_button add_product_form" @submit.prevent="handle_action">
|
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="action" value="add_product">
|
<input type="hidden" name="action" value="add_product">
|
||||||
<input type="hidden" name="product_id" value="{{ p.id }}">
|
<input type="hidden" name="product_id" value="{{ p.id }}">
|
||||||
<button type="submit"><strong>{{ p.name }}</strong><hr><img src="{{ file }}" /><span>{{ p.selling_price }} €<br>{{ p.code }}</span></button>
|
<button type="submit">
|
||||||
|
<strong>{{ p.name }}</strong>
|
||||||
|
{% if p.icon %}
|
||||||
|
<img src="{{ p.icon.url }}" alt="image de {{ p.name }}"/>
|
||||||
|
{% else %}
|
||||||
|
<img src="{{ static('core/img/na.gif') }}" alt="image de {{ p.name }}"/>
|
||||||
|
{% endif %}
|
||||||
|
<span>{{ p.price }} €<br>{{ p.code }}</span>
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
</div>
|
</div>
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block script %}
|
{% block script %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
<script src="{{ static('core/js/vue.global.prod.js') }}"></script>
|
|
||||||
<script>
|
<script>
|
||||||
$( function() {
|
const csrf_token = "{{ csrf_token }}";
|
||||||
/* Vue.JS dynamic form */
|
const click_api_url = "{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}";
|
||||||
const click_form_vue = Vue.createApp({
|
const basket = {{ request.session["basket"]|tojson }};
|
||||||
data() {
|
const products = {
|
||||||
return {
|
{%- for p in products -%}
|
||||||
js_csrf_token: "{{ csrf_token }}",
|
|
||||||
products: {
|
|
||||||
{% for p in products -%}
|
|
||||||
{{ p.id }}: {
|
{{ p.id }}: {
|
||||||
code: "{{ p.code }}",
|
code: "{{ p.code }}",
|
||||||
name: "{{ p.name }}",
|
name: "{{ p.name }}",
|
||||||
selling_price: "{{ p.selling_price }}",
|
price: {{ p.price }},
|
||||||
special_selling_price: "{{ p.special_selling_price }}",
|
|
||||||
},
|
},
|
||||||
{%- endfor %}
|
{%- endfor -%}
|
||||||
},
|
};
|
||||||
basket: {{ request.session["basket"]|tojson }},
|
const products_autocomplete = [
|
||||||
errors: [],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
sum_basket() {
|
|
||||||
var vm = this;
|
|
||||||
var total = 0;
|
|
||||||
for(idx in vm.basket) {
|
|
||||||
var item = vm.basket[idx];
|
|
||||||
console.log(item);
|
|
||||||
total += item["qty"] * item["price"];
|
|
||||||
}
|
|
||||||
return total / 100;
|
|
||||||
},
|
|
||||||
handle_code(event) {
|
|
||||||
var vm = this;
|
|
||||||
var code = $(event.target).find("#code_field").val().toUpperCase();
|
|
||||||
console.log("Code:");
|
|
||||||
console.log(code);
|
|
||||||
if(code == "{% trans %}END{% endtrans %}" || code == "{% trans %}CAN{% endtrans %}") {
|
|
||||||
$(event.target).submit();
|
|
||||||
} else {
|
|
||||||
vm.handle_action(event);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handle_action(event) {
|
|
||||||
var vm = this;
|
|
||||||
var payload = $(event.target).serialize();
|
|
||||||
$.ajax({
|
|
||||||
type: 'post',
|
|
||||||
dataType: 'json',
|
|
||||||
data: payload,
|
|
||||||
success: function(response) {
|
|
||||||
vm.basket = response.basket;
|
|
||||||
vm.errors = [];
|
|
||||||
},
|
|
||||||
error: function(error) {
|
|
||||||
vm.basket = error.responseJSON.basket;
|
|
||||||
vm.errors = error.responseJSON.errors;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$('form.code_form #code_field').val("").focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).mount('#bar_ui');
|
|
||||||
|
|
||||||
/* Autocompletion in the code field */
|
|
||||||
var products_autocomplete = [
|
|
||||||
{% for p in products -%}
|
{% for p in products -%}
|
||||||
{
|
{
|
||||||
value: "{{ p.code }}",
|
value: "{{ p.code }}",
|
||||||
@ -227,41 +192,5 @@ $( function() {
|
|||||||
},
|
},
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
];
|
];
|
||||||
|
|
||||||
var quantity = "";
|
|
||||||
var search = "";
|
|
||||||
var pattern = /^(\d+x)?(.*)/i;
|
|
||||||
$( "#code_field" ).autocomplete({
|
|
||||||
select: function (event, ui) {
|
|
||||||
event.preventDefault();
|
|
||||||
$("#code_field").val(quantity + ui.item.value);
|
|
||||||
},
|
|
||||||
focus: function (event, ui) {
|
|
||||||
event.preventDefault();
|
|
||||||
$("#code_field").val(quantity + ui.item.value);
|
|
||||||
},
|
|
||||||
source: function( request, response ) {
|
|
||||||
var res = pattern.exec(request.term);
|
|
||||||
quantity = res[1] || "";
|
|
||||||
search = res[2];
|
|
||||||
var matcher = new RegExp( $.ui.autocomplete.escapeRegex( search ), "i" );
|
|
||||||
response($.grep( products_autocomplete, function( value ) {
|
|
||||||
value = value.tags;
|
|
||||||
return matcher.test( value );
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
/* Accordion UI between basket and refills */
|
|
||||||
$("#click_form").accordion({
|
|
||||||
heightStyle: "content",
|
|
||||||
activate: function(event, ui){
|
|
||||||
$(".focus").focus();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$("#products").tabs();
|
|
||||||
|
|
||||||
$("#code_field").focus();
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
#
|
#
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
import string
|
||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
@ -42,7 +43,7 @@ class CounterTest(TestCase):
|
|||||||
self.foyer = Counter.objects.get(id=2)
|
self.foyer = Counter.objects.get(id=2)
|
||||||
|
|
||||||
def test_full_click(self):
|
def test_full_click(self):
|
||||||
response = self.client.post(
|
self.client.post(
|
||||||
reverse("counter:login", kwargs={"counter_id": self.mde.id}),
|
reverse("counter:login", kwargs={"counter_id": self.mde.id}),
|
||||||
{"username": self.skia.username, "password": "plop"},
|
{"username": self.skia.username, "password": "plop"},
|
||||||
)
|
)
|
||||||
@ -62,13 +63,12 @@ class CounterTest(TestCase):
|
|||||||
reverse("counter:details", kwargs={"counter_id": self.mde.id}),
|
reverse("counter:details", kwargs={"counter_id": self.mde.id}),
|
||||||
{"code": "4000k", "counter_token": counter_token},
|
{"code": "4000k", "counter_token": counter_token},
|
||||||
)
|
)
|
||||||
location = response.get("location")
|
counter_url = response.get("location")
|
||||||
|
|
||||||
response = self.client.get(response.get("location"))
|
response = self.client.get(response.get("location"))
|
||||||
self.assertTrue(">Richard Batsbak</" in str(response.content))
|
self.assertTrue(">Richard Batsbak</" in str(response.content))
|
||||||
|
|
||||||
self.client.post(
|
self.client.post(
|
||||||
location,
|
counter_url,
|
||||||
{
|
{
|
||||||
"action": "refill",
|
"action": "refill",
|
||||||
"amount": "5",
|
"amount": "5",
|
||||||
@ -76,17 +76,27 @@ class CounterTest(TestCase):
|
|||||||
"bank": "OTHER",
|
"bank": "OTHER",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.client.post(location, {"action": "code", "code": "BARB"})
|
self.client.post(counter_url, "action=code&code=BARB", content_type="text/xml")
|
||||||
self.client.post(location, {"action": "add_product", "product_id": "4"})
|
self.client.post(
|
||||||
self.client.post(location, {"action": "del_product", "product_id": "4"})
|
counter_url, "action=add_product&product_id=4", content_type="text/xml"
|
||||||
self.client.post(location, {"action": "code", "code": "2xdeco"})
|
)
|
||||||
self.client.post(location, {"action": "code", "code": "1xbarb"})
|
self.client.post(
|
||||||
response = self.client.post(location, {"action": "code", "code": "fin"})
|
counter_url, "action=del_product&product_id=4", content_type="text/xml"
|
||||||
|
)
|
||||||
|
self.client.post(
|
||||||
|
counter_url, "action=code&code=2xdeco", content_type="text/xml"
|
||||||
|
)
|
||||||
|
self.client.post(
|
||||||
|
counter_url, "action=code&code=1xbarb", content_type="text/xml"
|
||||||
|
)
|
||||||
|
response = self.client.post(
|
||||||
|
counter_url, "action=code&code=fin", content_type="text/xml"
|
||||||
|
)
|
||||||
|
|
||||||
response_get = self.client.get(response.get("location"))
|
response_get = self.client.get(response.get("location"))
|
||||||
response_content = response_get.content.decode("utf-8")
|
response_content = response_get.content.decode("utf-8")
|
||||||
self.assertTrue("<li>2 x Barbar" in str(response_content))
|
self.assertTrue("2 x Barbar" in str(response_content))
|
||||||
self.assertTrue("<li>2 x Déconsigne Eco-cup" in str(response_content))
|
self.assertTrue("2 x Déconsigne Eco-cup" in str(response_content))
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
"<p>Client : Richard Batsbak - Nouveau montant : 3.60"
|
"<p>Client : Richard Batsbak - Nouveau montant : 3.60"
|
||||||
in str(response_content)
|
in str(response_content)
|
||||||
@ -98,7 +108,7 @@ class CounterTest(TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
location,
|
counter_url,
|
||||||
{
|
{
|
||||||
"action": "refill",
|
"action": "refill",
|
||||||
"amount": "5",
|
"amount": "5",
|
||||||
@ -108,7 +118,7 @@ class CounterTest(TestCase):
|
|||||||
)
|
)
|
||||||
self.assertTrue(response.status_code == 200)
|
self.assertTrue(response.status_code == 200)
|
||||||
|
|
||||||
response = self.client.post(
|
self.client.post(
|
||||||
reverse("counter:login", kwargs={"counter_id": self.foyer.id}),
|
reverse("counter:login", kwargs={"counter_id": self.foyer.id}),
|
||||||
{"username": self.krophil.username, "password": "plop"},
|
{"username": self.krophil.username, "password": "plop"},
|
||||||
)
|
)
|
||||||
@ -125,10 +135,10 @@ class CounterTest(TestCase):
|
|||||||
reverse("counter:details", kwargs={"counter_id": self.foyer.id}),
|
reverse("counter:details", kwargs={"counter_id": self.foyer.id}),
|
||||||
{"code": "4000k", "counter_token": counter_token},
|
{"code": "4000k", "counter_token": counter_token},
|
||||||
)
|
)
|
||||||
location = response.get("location")
|
counter_url = response.get("location")
|
||||||
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
location,
|
counter_url,
|
||||||
{
|
{
|
||||||
"action": "refill",
|
"action": "refill",
|
||||||
"amount": "5",
|
"amount": "5",
|
||||||
@ -138,13 +148,27 @@ class CounterTest(TestCase):
|
|||||||
)
|
)
|
||||||
self.assertTrue(response.status_code == 200)
|
self.assertTrue(response.status_code == 200)
|
||||||
|
|
||||||
|
def test_annotate_has_barman_queryset(self):
|
||||||
|
"""
|
||||||
|
Test if the custom queryset method ``annotate_has_barman``
|
||||||
|
works as intended
|
||||||
|
"""
|
||||||
|
self.sli.counters.clear()
|
||||||
|
self.sli.counters.add(self.foyer, self.mde)
|
||||||
|
counters = Counter.objects.annotate_has_barman(self.sli)
|
||||||
|
for counter in counters:
|
||||||
|
if counter.name in ("Foyer", "MDE"):
|
||||||
|
self.assertTrue(counter.has_annotated_barman)
|
||||||
|
else:
|
||||||
|
self.assertFalse(counter.has_annotated_barman)
|
||||||
|
|
||||||
|
|
||||||
class CounterStatsTest(TestCase):
|
class CounterStatsTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
call_command("populate")
|
call_command("populate")
|
||||||
self.counter = Counter.objects.filter(id=2).first()
|
self.counter = Counter.objects.filter(id=2).first()
|
||||||
|
|
||||||
def test_unothorized_user_fail(self):
|
def test_unauthorised_user_fail(self):
|
||||||
# Test with not login user
|
# Test with not login user
|
||||||
response = self.client.get(reverse("counter:stats", args=[self.counter.id]))
|
response = self.client.get(reverse("counter:stats", args=[self.counter.id]))
|
||||||
self.assertTrue(response.status_code == 403)
|
self.assertTrue(response.status_code == 403)
|
||||||
@ -745,18 +769,30 @@ class StudentCardTest(TestCase):
|
|||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
|
|
||||||
class AccountIdTest(TestCase):
|
class CustomerAccountIdTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
user_a = User.objects.create(username="a", password="plop", email="a.a@a.fr")
|
self.user_a = User.objects.create(
|
||||||
|
username="a", password="plop", email="a.a@a.fr"
|
||||||
|
)
|
||||||
user_b = User.objects.create(username="b", password="plop", email="b.b@b.fr")
|
user_b = User.objects.create(username="b", password="plop", email="b.b@b.fr")
|
||||||
user_c = User.objects.create(username="c", password="plop", email="c.c@c.fr")
|
user_c = User.objects.create(username="c", password="plop", email="c.c@c.fr")
|
||||||
Customer.objects.create(user=user_a, amount=0, account_id="1111a")
|
Customer.objects.create(user=self.user_a, amount=10, account_id="1111a")
|
||||||
Customer.objects.create(user=user_b, amount=0, account_id="9999z")
|
Customer.objects.create(user=user_b, amount=0, account_id="9999z")
|
||||||
Customer.objects.create(user=user_c, amount=0, account_id="12345f")
|
Customer.objects.create(user=user_c, amount=0, account_id="12345f")
|
||||||
|
|
||||||
def test_create_customer(self):
|
def test_create_customer(self):
|
||||||
user_d = User.objects.create(username="d", password="plop")
|
user_d = User.objects.create(username="d", password="plop")
|
||||||
customer_d = Customer.new_for_user(user_d)
|
customer, created = Customer.get_or_create(user_d)
|
||||||
customer_d.save()
|
account_id = customer.account_id
|
||||||
number = customer_d.account_id[:-1]
|
number = account_id[:-1]
|
||||||
|
self.assertTrue(created)
|
||||||
self.assertEqual(number, "12346")
|
self.assertEqual(number, "12346")
|
||||||
|
self.assertEqual(6, len(account_id))
|
||||||
|
self.assertIn(account_id[-1], string.ascii_lowercase)
|
||||||
|
self.assertEqual(0, customer.amount)
|
||||||
|
|
||||||
|
def test_get_existing_account(self):
|
||||||
|
account, created = Customer.get_or_create(self.user_a)
|
||||||
|
self.assertFalse(created)
|
||||||
|
self.assertEqual(account.account_id, "1111a")
|
||||||
|
self.assertEqual(10, account.amount)
|
||||||
|
106
counter/urls.py
106
counter/urls.py
@ -22,47 +22,47 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.urls import re_path, path
|
from django.urls import path
|
||||||
|
|
||||||
from counter.views import *
|
from counter.views import *
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
re_path(r"^(?P<counter_id>[0-9]+)$", CounterMain.as_view(), name="details"),
|
path("<int:counter_id>/", CounterMain.as_view(), name="details"),
|
||||||
re_path(
|
path(
|
||||||
r"^(?P<counter_id>[0-9]+)/click/(?P<user_id>[0-9]+)$",
|
"<int:counter_id>/click/<int:user_id>/",
|
||||||
CounterClick.as_view(),
|
CounterClick.as_view(),
|
||||||
name="click",
|
name="click",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^(?P<counter_id>[0-9]+)/last_ops$",
|
"<int:counter_id>/last_ops/",
|
||||||
CounterLastOperationsView.as_view(),
|
CounterLastOperationsView.as_view(),
|
||||||
name="last_ops",
|
name="last_ops",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^(?P<counter_id>[0-9]+)/cash_summary$",
|
"<int:counter_id>/cash_summary/",
|
||||||
CounterCashSummaryView.as_view(),
|
CounterCashSummaryView.as_view(),
|
||||||
name="cash_summary",
|
name="cash_summary",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^(?P<counter_id>[0-9]+)/activity$",
|
"<int:counter_id>/activity/",
|
||||||
CounterActivityView.as_view(),
|
CounterActivityView.as_view(),
|
||||||
name="activity",
|
name="activity",
|
||||||
),
|
),
|
||||||
re_path(r"^(?P<counter_id>[0-9]+)/stats$", CounterStatView.as_view(), name="stats"),
|
path("<int:counter_id>/stats/", CounterStatView.as_view(), name="stats"),
|
||||||
re_path(r"^(?P<counter_id>[0-9]+)/login$", CounterLogin.as_view(), name="login"),
|
path("<int:counter_id>/login/", CounterLogin.as_view(), name="login"),
|
||||||
re_path(r"^(?P<counter_id>[0-9]+)/logout$", CounterLogout.as_view(), name="logout"),
|
path("<int:counter_id>/logout/", CounterLogout.as_view(), name="logout"),
|
||||||
re_path(
|
path(
|
||||||
r"^eticket/(?P<selling_id>[0-9]+)/pdf$",
|
"eticket/<int:selling_id>/pdf/",
|
||||||
EticketPDFView.as_view(),
|
EticketPDFView.as_view(),
|
||||||
name="eticket_pdf",
|
name="eticket_pdf",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^customer/(?P<customer_id>[0-9]+)/card/add$",
|
"customer/<int:customer_id>/card/add/",
|
||||||
StudentCardFormView.as_view(),
|
StudentCardFormView.as_view(),
|
||||||
name="add_student_card",
|
name="add_student_card",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^customer/(?P<customer_id>[0-9]+)/card/delete/(?P<card_id>[0-9]+)/$",
|
"customer/<int:customer_id>/card/delete/<int:card_id>/",
|
||||||
StudentCardDeleteView.as_view(),
|
StudentCardDeleteView.as_view(),
|
||||||
name="delete_student_card",
|
name="delete_student_card",
|
||||||
),
|
),
|
||||||
@ -76,76 +76,76 @@ urlpatterns = [
|
|||||||
edit_billing_info,
|
edit_billing_info,
|
||||||
name="edit_billing_info",
|
name="edit_billing_info",
|
||||||
),
|
),
|
||||||
re_path(r"^admin/(?P<counter_id>[0-9]+)$", CounterEditView.as_view(), name="admin"),
|
path("admin/<int:counter_id>/", CounterEditView.as_view(), name="admin"),
|
||||||
re_path(
|
path(
|
||||||
r"^admin/(?P<counter_id>[0-9]+)/prop$",
|
"admin/<int:counter_id>/prop/",
|
||||||
CounterEditPropView.as_view(),
|
CounterEditPropView.as_view(),
|
||||||
name="prop_admin",
|
name="prop_admin",
|
||||||
),
|
),
|
||||||
re_path(r"^admin$", CounterListView.as_view(), name="admin_list"),
|
path("admin/", CounterListView.as_view(), name="admin_list"),
|
||||||
re_path(r"^admin/new$", CounterCreateView.as_view(), name="new"),
|
path("admin/new/", CounterCreateView.as_view(), name="new"),
|
||||||
re_path(
|
path(
|
||||||
r"^admin/delete/(?P<counter_id>[0-9]+)$",
|
"admin/delete/<int:counter_id>/",
|
||||||
CounterDeleteView.as_view(),
|
CounterDeleteView.as_view(),
|
||||||
name="delete",
|
name="delete",
|
||||||
),
|
),
|
||||||
re_path(r"^admin/invoices_call$", InvoiceCallView.as_view(), name="invoices_call"),
|
path("admin/invoices_call/", InvoiceCallView.as_view(), name="invoices_call"),
|
||||||
re_path(
|
path(
|
||||||
r"^admin/cash_summary/list$",
|
"admin/cash_summary/list/",
|
||||||
CashSummaryListView.as_view(),
|
CashSummaryListView.as_view(),
|
||||||
name="cash_summary_list",
|
name="cash_summary_list",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^admin/cash_summary/(?P<cashsummary_id>[0-9]+)$",
|
"admin/cash_summary/<int:cashsummary_id>/",
|
||||||
CashSummaryEditView.as_view(),
|
CashSummaryEditView.as_view(),
|
||||||
name="cash_summary_edit",
|
name="cash_summary_edit",
|
||||||
),
|
),
|
||||||
re_path(r"^admin/product/list$", ProductListView.as_view(), name="product_list"),
|
path("admin/product/list/", ProductListView.as_view(), name="product_list"),
|
||||||
re_path(
|
path(
|
||||||
r"^admin/product/list_archived$",
|
"admin/product/list_archived/",
|
||||||
ProductArchivedListView.as_view(),
|
ProductArchivedListView.as_view(),
|
||||||
name="product_list_archived",
|
name="product_list_archived",
|
||||||
),
|
),
|
||||||
re_path(r"^admin/product/create$", ProductCreateView.as_view(), name="new_product"),
|
path("admin/product/create/", ProductCreateView.as_view(), name="new_product"),
|
||||||
re_path(
|
path(
|
||||||
r"^admin/product/(?P<product_id>[0-9]+)$",
|
"admin/product/<int:product_id>/",
|
||||||
ProductEditView.as_view(),
|
ProductEditView.as_view(),
|
||||||
name="product_edit",
|
name="product_edit",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^admin/producttype/list$",
|
"admin/producttype/list/",
|
||||||
ProductTypeListView.as_view(),
|
ProductTypeListView.as_view(),
|
||||||
name="producttype_list",
|
name="producttype_list",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^admin/producttype/create$",
|
"admin/producttype/create/",
|
||||||
ProductTypeCreateView.as_view(),
|
ProductTypeCreateView.as_view(),
|
||||||
name="new_producttype",
|
name="new_producttype",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^admin/producttype/(?P<type_id>[0-9]+)$",
|
"admin/producttype/<int:type_id>/",
|
||||||
ProductTypeEditView.as_view(),
|
ProductTypeEditView.as_view(),
|
||||||
name="producttype_edit",
|
name="producttype_edit",
|
||||||
),
|
),
|
||||||
re_path(r"^admin/eticket/list$", EticketListView.as_view(), name="eticket_list"),
|
path("admin/eticket/list/", EticketListView.as_view(), name="eticket_list"),
|
||||||
re_path(r"^admin/eticket/new$", EticketCreateView.as_view(), name="new_eticket"),
|
path("admin/eticket/new/", EticketCreateView.as_view(), name="new_eticket"),
|
||||||
re_path(
|
path(
|
||||||
r"^admin/eticket/(?P<eticket_id>[0-9]+)$",
|
"admin/eticket/<int:eticket_id>/",
|
||||||
EticketEditView.as_view(),
|
EticketEditView.as_view(),
|
||||||
name="edit_eticket",
|
name="edit_eticket",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^admin/selling/(?P<selling_id>[0-9]+)/delete$",
|
"admin/selling/<int:selling_id>/delete/",
|
||||||
SellingDeleteView.as_view(),
|
SellingDeleteView.as_view(),
|
||||||
name="selling_delete",
|
name="selling_delete",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^admin/refilling/(?P<refilling_id>[0-9]+)/delete$",
|
"admin/refilling/<int:refilling_id>/delete/",
|
||||||
RefillingDeleteView.as_view(),
|
RefillingDeleteView.as_view(),
|
||||||
name="refilling_delete",
|
name="refilling_delete",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^admin/(?P<counter_id>[0-9]+)/refillings$",
|
"admin/<int:counter_id>/refillings/",
|
||||||
CounterRefillingListView.as_view(),
|
CounterRefillingListView.as_view(),
|
||||||
name="refilling_list",
|
name="refilling_list",
|
||||||
),
|
),
|
||||||
|
@ -22,8 +22,10 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
import json
|
import json
|
||||||
|
from urllib.parse import parse_qs
|
||||||
|
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.db.models import F
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
@ -300,7 +302,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
|
|||||||
current_tab = "counter"
|
current_tab = "counter"
|
||||||
|
|
||||||
def render_to_response(self, *args, **kwargs):
|
def render_to_response(self, *args, **kwargs):
|
||||||
if self.request.is_ajax(): # JSON response for AJAX requests
|
if self.is_ajax(self.request):
|
||||||
response = {"errors": []}
|
response = {"errors": []}
|
||||||
status = HTTPStatus.OK
|
status = HTTPStatus.OK
|
||||||
|
|
||||||
@ -395,42 +397,40 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
|
|||||||
request.session["not_valid_student_card_uid"] = False
|
request.session["not_valid_student_card_uid"] = False
|
||||||
if self.object.type != "BAR":
|
if self.object.type != "BAR":
|
||||||
self.operator = request.user
|
self.operator = request.user
|
||||||
elif self.is_barman_price():
|
elif self.customer_is_barman():
|
||||||
self.operator = self.customer.user
|
self.operator = self.customer.user
|
||||||
else:
|
else:
|
||||||
self.operator = self.object.get_random_barman()
|
self.operator = self.object.get_random_barman()
|
||||||
|
action = self.request.POST.get("action", None)
|
||||||
if "add_product" in request.POST["action"]:
|
if action is None:
|
||||||
|
action = parse_qs(request.body.decode()).get("action", [""])[0]
|
||||||
|
if action == "add_product":
|
||||||
self.add_product(request)
|
self.add_product(request)
|
||||||
elif "add_student_card" in request.POST["action"]:
|
elif action == "add_student_card":
|
||||||
self.add_student_card(request)
|
self.add_student_card(request)
|
||||||
elif "del_product" in request.POST["action"]:
|
elif action == "del_product":
|
||||||
self.del_product(request)
|
self.del_product(request)
|
||||||
elif "refill" in request.POST["action"]:
|
elif action == "refill":
|
||||||
self.refill(request)
|
self.refill(request)
|
||||||
elif "code" in request.POST["action"]:
|
elif action == "code":
|
||||||
return self.parse_code(request)
|
return self.parse_code(request)
|
||||||
elif "cancel" in request.POST["action"]:
|
elif action == "cancel":
|
||||||
return self.cancel(request)
|
return self.cancel(request)
|
||||||
elif "finish" in request.POST["action"]:
|
elif action == "finish":
|
||||||
return self.finish(request)
|
return self.finish(request)
|
||||||
context = self.get_context_data(object=self.object)
|
context = self.get_context_data(object=self.object)
|
||||||
return self.render_to_response(context)
|
return self.render_to_response(context)
|
||||||
|
|
||||||
def is_barman_price(self):
|
def customer_is_barman(self) -> bool:
|
||||||
if self.object.type == "BAR" and self.customer.user.id in [
|
barmen = self.object.barmen_list
|
||||||
s.id for s in self.object.get_barmen_list()
|
return self.object.type == "BAR" and self.customer.user in barmen
|
||||||
]:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_product(self, pid):
|
def get_product(self, pid):
|
||||||
return Product.objects.filter(pk=int(pid)).first()
|
return Product.objects.filter(pk=int(pid)).first()
|
||||||
|
|
||||||
def get_price(self, pid):
|
def get_price(self, pid):
|
||||||
p = self.get_product(pid)
|
p = self.get_product(pid)
|
||||||
if self.is_barman_price():
|
if self.customer_is_barman():
|
||||||
price = p.special_selling_price
|
price = p.special_selling_price
|
||||||
else:
|
else:
|
||||||
price = p.selling_price
|
price = p.selling_price
|
||||||
@ -475,13 +475,22 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
|
|||||||
self.compute_record_product(request, product)
|
self.compute_record_product(request, product)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_ajax(request):
|
||||||
|
# when using the fetch API, the django request.POST dict is empty
|
||||||
|
# this is but a wretched contrivance which strive to replace
|
||||||
|
# the deprecated django is_ajax() method
|
||||||
|
# and which must be replaced as soon as possible
|
||||||
|
# by a proper separation between the api endpoints of the counter
|
||||||
|
return len(request.POST) == 0 and len(request.body) != 0
|
||||||
|
|
||||||
def add_product(self, request, q=1, p=None):
|
def add_product(self, request, q=1, p=None):
|
||||||
"""
|
"""
|
||||||
Add a product to the basket
|
Add a product to the basket
|
||||||
q is the quantity passed as integer
|
q is the quantity passed as integer
|
||||||
p is the product id, passed as an integer
|
p is the product id, passed as an integer
|
||||||
"""
|
"""
|
||||||
pid = p or request.POST["product_id"]
|
pid = p or parse_qs(request.body.decode())["product_id"][0]
|
||||||
pid = str(pid)
|
pid = str(pid)
|
||||||
price = self.get_price(pid)
|
price = self.get_price(pid)
|
||||||
total = self.sum_basket(request)
|
total = self.sum_basket(request)
|
||||||
@ -563,7 +572,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
|
|||||||
|
|
||||||
def del_product(self, request):
|
def del_product(self, request):
|
||||||
"""Delete a product from the basket"""
|
"""Delete a product from the basket"""
|
||||||
pid = str(request.POST["product_id"])
|
pid = parse_qs(request.body.decode())["product_id"][0]
|
||||||
product = self.get_product(pid)
|
product = self.get_product(pid)
|
||||||
if pid in request.session["basket"]:
|
if pid in request.session["basket"]:
|
||||||
if (
|
if (
|
||||||
@ -576,30 +585,29 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
|
|||||||
request.session["basket"][pid]["qty"] -= 1
|
request.session["basket"][pid]["qty"] -= 1
|
||||||
if request.session["basket"][pid]["qty"] <= 0:
|
if request.session["basket"][pid]["qty"] <= 0:
|
||||||
del request.session["basket"][pid]
|
del request.session["basket"][pid]
|
||||||
else:
|
|
||||||
request.session["basket"][pid] = None
|
|
||||||
request.session.modified = True
|
request.session.modified = True
|
||||||
|
|
||||||
def parse_code(self, request):
|
def parse_code(self, request):
|
||||||
"""Parse the string entered by the barman"""
|
"""
|
||||||
string = str(request.POST["code"]).upper()
|
Parse the string entered by the barman
|
||||||
if string == _("END"):
|
This can be of two forms :
|
||||||
|
- <str>, where the string is the code of the product
|
||||||
|
- <int>X<str>, where the integer is the quantity and str the code
|
||||||
|
"""
|
||||||
|
string = parse_qs(request.body.decode())["code"][0].upper()
|
||||||
|
if string == "FIN":
|
||||||
return self.finish(request)
|
return self.finish(request)
|
||||||
elif string == _("CAN"):
|
elif string == "ANN":
|
||||||
return self.cancel(request)
|
return self.cancel(request)
|
||||||
regex = re.compile(r"^((?P<nb>[0-9]+)X)?(?P<code>[A-Z0-9]+)$")
|
regex = re.compile(r"^((?P<nb>[0-9]+)X)?(?P<code>[A-Z0-9]+)$")
|
||||||
m = regex.match(string)
|
m = regex.match(string)
|
||||||
if m is not None:
|
if m is not None:
|
||||||
nb = m.group("nb")
|
nb = m.group("nb")
|
||||||
code = m.group("code")
|
code = m.group("code")
|
||||||
if nb is None:
|
nb = int(nb) if nb is not None else 1
|
||||||
nb = 1
|
|
||||||
else:
|
|
||||||
nb = int(nb)
|
|
||||||
p = self.object.products.filter(code=code).first()
|
p = self.object.products.filter(code=code).first()
|
||||||
if p is not None:
|
if p is not None:
|
||||||
while nb > 0 and not self.add_product(request, nb, p.id):
|
self.add_product(request, nb, p.id)
|
||||||
nb -= 1
|
|
||||||
context = self.get_context_data(object=self.object)
|
context = self.get_context_data(object=self.object)
|
||||||
return self.render_to_response(context)
|
return self.render_to_response(context)
|
||||||
|
|
||||||
@ -613,7 +621,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
|
|||||||
for pid, infos in request.session["basket"].items():
|
for pid, infos in request.session["basket"].items():
|
||||||
# This duplicates code for DB optimization (prevent to load many times the same object)
|
# This duplicates code for DB optimization (prevent to load many times the same object)
|
||||||
p = Product.objects.filter(pk=pid).first()
|
p = Product.objects.filter(pk=pid).first()
|
||||||
if self.is_barman_price():
|
if self.customer_is_barman():
|
||||||
uprice = p.special_selling_price
|
uprice = p.special_selling_price
|
||||||
else:
|
else:
|
||||||
uprice = p.selling_price
|
uprice = p.selling_price
|
||||||
@ -665,7 +673,8 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
|
|||||||
|
|
||||||
def refill(self, request):
|
def refill(self, request):
|
||||||
"""Refill the customer's account"""
|
"""Refill the customer's account"""
|
||||||
if self.get_object().type == "BAR" and self.object.can_refill():
|
if not self.object.can_refill():
|
||||||
|
raise PermissionDenied
|
||||||
form = RefillForm(request.POST)
|
form = RefillForm(request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
form.instance.counter = self.object
|
form.instance.counter = self.object
|
||||||
@ -674,13 +683,16 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
|
|||||||
form.instance.save()
|
form.instance.save()
|
||||||
else:
|
else:
|
||||||
self.refill_form = form
|
self.refill_form = form
|
||||||
else:
|
|
||||||
raise PermissionDenied
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
"""Add customer to the context"""
|
"""Add customer to the context"""
|
||||||
kwargs = super(CounterClick, self).get_context_data(**kwargs)
|
kwargs = super(CounterClick, self).get_context_data(**kwargs)
|
||||||
kwargs["products"] = self.object.products.select_related("product_type")
|
products = self.object.products.select_related("product_type")
|
||||||
|
if self.customer_is_barman():
|
||||||
|
products = products.annotate(price=F("special_selling_price"))
|
||||||
|
else:
|
||||||
|
products = products.annotate(price=F("selling_price"))
|
||||||
|
kwargs["products"] = products
|
||||||
kwargs["categories"] = {}
|
kwargs["categories"] = {}
|
||||||
for product in kwargs["products"]:
|
for product in kwargs["products"]:
|
||||||
if product.product_type:
|
if product.product_type:
|
||||||
@ -1780,11 +1792,7 @@ def create_billing_info(request, user_id):
|
|||||||
if user.id != user_id and not user.has_perm("counter:add_billinginfo"):
|
if user.id != user_id and not user.has_perm("counter:add_billinginfo"):
|
||||||
raise PermissionDenied()
|
raise PermissionDenied()
|
||||||
user = get_object_or_404(User, pk=user_id)
|
user = get_object_or_404(User, pk=user_id)
|
||||||
if not hasattr(user, "customer"):
|
customer, _ = Customer.get_or_create(user)
|
||||||
customer = Customer.new_for_user(user)
|
|
||||||
customer.save()
|
|
||||||
else:
|
|
||||||
customer = get_object_or_404(Customer, user_id=user_id)
|
|
||||||
BillingInfo.objects.create(customer=customer)
|
BillingInfo.objects.create(customer=customer)
|
||||||
return __manage_billing_info_req(request, user_id, True)
|
return __manage_billing_info_req(request, user_id, True)
|
||||||
|
|
||||||
|
@ -245,12 +245,13 @@ class Invoice(models.Model):
|
|||||||
def validate(self):
|
def validate(self):
|
||||||
if self.validated:
|
if self.validated:
|
||||||
raise DataError(_("Invoice already validated"))
|
raise DataError(_("Invoice already validated"))
|
||||||
|
customer, created = Customer.get_or_create(user=self.user)
|
||||||
eboutic = Counter.objects.filter(type="EBOUTIC").first()
|
eboutic = Counter.objects.filter(type="EBOUTIC").first()
|
||||||
for i in self.items.all():
|
for i in self.items.all():
|
||||||
if i.type_id == settings.SITH_COUNTER_PRODUCTTYPE_REFILLING:
|
if i.type_id == settings.SITH_COUNTER_PRODUCTTYPE_REFILLING:
|
||||||
new = Refilling(
|
new = Refilling(
|
||||||
counter=eboutic,
|
counter=eboutic,
|
||||||
customer=self.user.customer,
|
customer=customer,
|
||||||
operator=self.user,
|
operator=self.user,
|
||||||
amount=i.product_unit_price * i.quantity,
|
amount=i.product_unit_price * i.quantity,
|
||||||
payment_method="CARD",
|
payment_method="CARD",
|
||||||
@ -266,7 +267,7 @@ class Invoice(models.Model):
|
|||||||
club=product.club,
|
club=product.club,
|
||||||
product=product,
|
product=product,
|
||||||
seller=self.user,
|
seller=self.user,
|
||||||
customer=self.user.customer,
|
customer=customer,
|
||||||
unit_price=i.product_unit_price,
|
unit_price=i.product_unit_price,
|
||||||
quantity=i.quantity,
|
quantity=i.quantity,
|
||||||
payment_method="CARD",
|
payment_method="CARD",
|
||||||
|
@ -111,7 +111,7 @@
|
|||||||
<i class="fa fa-2x fa-picture-o product-image" ></i>
|
<i class="fa fa-2x fa-picture-o product-image" ></i>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="product-description">
|
<div class="product-description">
|
||||||
<h4>{{ p.name }}</strong></h4>
|
<h4>{{ p.name }}</h4>
|
||||||
<p>{{ p.selling_price }} €</p>
|
<p>{{ p.selling_price }} €</p>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
@ -1,57 +1,49 @@
|
|||||||
from django.urls import re_path
|
from django.urls import path
|
||||||
|
|
||||||
from election.views import *
|
from election.views import *
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
re_path(r"^$", ElectionsListView.as_view(), name="list"),
|
path("", ElectionsListView.as_view(), name="list"),
|
||||||
re_path(r"^archived$", ElectionListArchivedView.as_view(), name="list_archived"),
|
path("archived/", ElectionListArchivedView.as_view(), name="list_archived"),
|
||||||
re_path(r"^add$", ElectionCreateView.as_view(), name="create"),
|
path("add/", ElectionCreateView.as_view(), name="create"),
|
||||||
re_path(
|
path("<int:election_id>/edit/", ElectionUpdateView.as_view(), name="update"),
|
||||||
r"^(?P<election_id>[0-9]+)/edit$", ElectionUpdateView.as_view(), name="update"
|
path("<int:election_id>/delete/", ElectionDeleteView.as_view(), name="delete"),
|
||||||
),
|
path(
|
||||||
re_path(
|
"<int:election_id>/list/add/",
|
||||||
r"^(?P<election_id>[0-9]+)/delete$", ElectionDeleteView.as_view(), name="delete"
|
|
||||||
),
|
|
||||||
re_path(
|
|
||||||
r"^(?P<election_id>[0-9]+)/list/add$",
|
|
||||||
ElectionListCreateView.as_view(),
|
ElectionListCreateView.as_view(),
|
||||||
name="create_list",
|
name="create_list",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^(?P<list_id>[0-9]+)/list/delete$",
|
"<int:list_id>/list/delete/",
|
||||||
ElectionListDeleteView.as_view(),
|
ElectionListDeleteView.as_view(),
|
||||||
name="delete_list",
|
name="delete_list",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^(?P<election_id>[0-9]+)/role/create$",
|
"<int:election_id>/role/create/",
|
||||||
RoleCreateView.as_view(),
|
RoleCreateView.as_view(),
|
||||||
name="create_role",
|
name="create_role",
|
||||||
),
|
),
|
||||||
re_path(
|
path("<int:role_id>/role/edit/", RoleUpdateView.as_view(), name="update_role"),
|
||||||
r"^(?P<role_id>[0-9]+)/role/edit$", RoleUpdateView.as_view(), name="update_role"
|
path(
|
||||||
),
|
"<int:role_id>/role/delete/",
|
||||||
re_path(
|
|
||||||
r"^(?P<role_id>[0-9]+)/role/delete$",
|
|
||||||
RoleDeleteView.as_view(),
|
RoleDeleteView.as_view(),
|
||||||
name="delete_role",
|
name="delete_role",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^(?P<election_id>[0-9]+)/candidate/add$",
|
"<int:election_id>/candidate/add/",
|
||||||
CandidatureCreateView.as_view(),
|
CandidatureCreateView.as_view(),
|
||||||
name="candidate",
|
name="candidate",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^(?P<candidature_id>[0-9]+)/candidate/edit$",
|
"<int:candidature_id>/candidate/edit/",
|
||||||
CandidatureUpdateView.as_view(),
|
CandidatureUpdateView.as_view(),
|
||||||
name="update_candidate",
|
name="update_candidate",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^(?P<candidature_id>[0-9]+)/candidate/delete$",
|
"<int:candidature_id>/candidate/delete/",
|
||||||
CandidatureDeleteView.as_view(),
|
CandidatureDeleteView.as_view(),
|
||||||
name="delete_candidate",
|
name="delete_candidate",
|
||||||
),
|
),
|
||||||
re_path(r"^(?P<election_id>[0-9]+)/vote$", VoteFormView.as_view(), name="vote"),
|
path("<int:election_id>/vote/", VoteFormView.as_view(), name="vote"),
|
||||||
re_path(
|
path("<int:election_id>/detail/", ElectionDetailView.as_view(), name="detail"),
|
||||||
r"^(?P<election_id>[0-9]+)/detail$", ElectionDetailView.as_view(), name="detail"
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
@ -22,69 +22,62 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.urls import re_path
|
from django.urls import path
|
||||||
|
|
||||||
from forum.views import *
|
from forum.views import *
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
re_path(r"^$", ForumMainView.as_view(), name="main"),
|
path("", ForumMainView.as_view(), name="main"),
|
||||||
re_path(r"^search/$", ForumSearchView.as_view(), name="search"),
|
path("search/", ForumSearchView.as_view(), name="search"),
|
||||||
re_path(r"^new_forum$", ForumCreateView.as_view(), name="new_forum"),
|
path("new_forum/", ForumCreateView.as_view(), name="new_forum"),
|
||||||
re_path(
|
path("mark_all_as_read/", ForumMarkAllAsRead.as_view(), name="mark_all_as_read"),
|
||||||
r"^mark_all_as_read$", ForumMarkAllAsRead.as_view(), name="mark_all_as_read"
|
path("last_unread/", ForumLastUnread.as_view(), name="last_unread"),
|
||||||
),
|
path("favorite_topics/", ForumFavoriteTopics.as_view(), name="favorite_topics"),
|
||||||
re_path(r"^last_unread$", ForumLastUnread.as_view(), name="last_unread"),
|
path("<int:forum_id>/", ForumDetailView.as_view(), name="view_forum"),
|
||||||
re_path(
|
path("<int:forum_id>/edit/", ForumEditView.as_view(), name="edit_forum"),
|
||||||
r"^favorite_topics$", ForumFavoriteTopics.as_view(), name="favorite_topics"
|
path("<int:forum_id>/delete/", ForumDeleteView.as_view(), name="delete_forum"),
|
||||||
),
|
path(
|
||||||
re_path(r"^(?P<forum_id>[0-9]+)$", ForumDetailView.as_view(), name="view_forum"),
|
"<int:forum_id>/new_topic/",
|
||||||
re_path(r"^(?P<forum_id>[0-9]+)/edit$", ForumEditView.as_view(), name="edit_forum"),
|
|
||||||
re_path(
|
|
||||||
r"^(?P<forum_id>[0-9]+)/delete$", ForumDeleteView.as_view(), name="delete_forum"
|
|
||||||
),
|
|
||||||
re_path(
|
|
||||||
r"^(?P<forum_id>[0-9]+)/new_topic$",
|
|
||||||
ForumTopicCreateView.as_view(),
|
ForumTopicCreateView.as_view(),
|
||||||
name="new_topic",
|
name="new_topic",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^topic/(?P<topic_id>[0-9]+)$",
|
"topic/<int:topic_id>/",
|
||||||
ForumTopicDetailView.as_view(),
|
ForumTopicDetailView.as_view(),
|
||||||
name="view_topic",
|
name="view_topic",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^topic/(?P<topic_id>[0-9]+)/edit$",
|
"topic/<int:topic_id>/edit/",
|
||||||
ForumTopicEditView.as_view(),
|
ForumTopicEditView.as_view(),
|
||||||
name="edit_topic",
|
name="edit_topic",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^topic/(?P<topic_id>[0-9]+)/new_message$",
|
"topic/<int:topic_id>/new_message/",
|
||||||
ForumMessageCreateView.as_view(),
|
ForumMessageCreateView.as_view(),
|
||||||
name="new_message",
|
name="new_message",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^topic/(?P<topic_id>[0-9]+)/toggle_subscribe$",
|
"topic/<int:topic_id>/toggle_subscribe/",
|
||||||
ForumTopicSubscribeView.as_view(),
|
ForumTopicSubscribeView.as_view(),
|
||||||
name="toggle_subscribe_topic",
|
name="toggle_subscribe_topic",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^message/(?P<message_id>[0-9]+)$",
|
"message/<int:message_id>/",
|
||||||
ForumMessageView.as_view(),
|
ForumMessageView.as_view(),
|
||||||
name="view_message",
|
name="view_message",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^message/(?P<message_id>[0-9]+)/edit$",
|
"message/<int:message_id>/edit/",
|
||||||
ForumMessageEditView.as_view(),
|
ForumMessageEditView.as_view(),
|
||||||
name="edit_message",
|
name="edit_message",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^message/(?P<message_id>[0-9]+)/delete$",
|
"message/<int:message_id>/delete/",
|
||||||
ForumMessageDeleteView.as_view(),
|
ForumMessageDeleteView.as_view(),
|
||||||
name="delete_message",
|
name="delete_message",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^message/(?P<message_id>[0-9]+)/undelete$",
|
"message/<int:message_id>/undelete/",
|
||||||
ForumMessageUndeleteView.as_view(),
|
ForumMessageUndeleteView.as_view(),
|
||||||
name="undelete_message",
|
name="undelete_message",
|
||||||
),
|
),
|
||||||
|
@ -26,7 +26,6 @@ import math
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Q, Case, F, Value, When, Count
|
from django.db.models import Q, Case, F, Value, When, Count
|
||||||
from django.db.models.functions import Concat
|
from django.db.models.functions import Concat
|
||||||
|
@ -142,8 +142,4 @@ class GalaxyTest(TestCase):
|
|||||||
Galaxy.rule()
|
Galaxy.rule()
|
||||||
self.client.login(username="root", password="plop")
|
self.client.login(username="root", password="plop")
|
||||||
response = self.client.get("/galaxy/2/")
|
response = self.client.get("/galaxy/2/")
|
||||||
self.assertContains(
|
self.assertEquals(response.status_code, 404)
|
||||||
response,
|
|
||||||
"Ce citoyen n'a pas encore rejoint la galaxie",
|
|
||||||
status_code=404,
|
|
||||||
)
|
|
||||||
|
@ -22,54 +22,54 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.urls import re_path
|
from django.urls import path
|
||||||
|
|
||||||
from launderette.views import *
|
from launderette.views import *
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# views
|
# views
|
||||||
re_path(r"^$", LaunderetteMainView.as_view(), name="launderette_main"),
|
path("", LaunderetteMainView.as_view(), name="launderette_main"),
|
||||||
re_path(
|
path(
|
||||||
r"^slot/(?P<slot_id>[0-9]+)/delete$",
|
"slot/<int:slot_id>/delete/",
|
||||||
SlotDeleteView.as_view(),
|
SlotDeleteView.as_view(),
|
||||||
name="delete_slot",
|
name="delete_slot",
|
||||||
),
|
),
|
||||||
re_path(r"^book$", LaunderetteBookMainView.as_view(), name="book_main"),
|
path("book/", LaunderetteBookMainView.as_view(), name="book_main"),
|
||||||
re_path(
|
path(
|
||||||
r"^book/(?P<launderette_id>[0-9]+)$",
|
"book/<int:launderette_id>/",
|
||||||
LaunderetteBookView.as_view(),
|
LaunderetteBookView.as_view(),
|
||||||
name="book_slot",
|
name="book_slot",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^(?P<launderette_id>[0-9]+)/click$",
|
"<int:launderette_id>/click/",
|
||||||
LaunderetteMainClickView.as_view(),
|
LaunderetteMainClickView.as_view(),
|
||||||
name="main_click",
|
name="main_click",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^(?P<launderette_id>[0-9]+)/click/(?P<user_id>[0-9]+)$",
|
"<int:launderette_id>/click/<int:user_id>/",
|
||||||
LaunderetteClickView.as_view(),
|
LaunderetteClickView.as_view(),
|
||||||
name="click",
|
name="click",
|
||||||
),
|
),
|
||||||
re_path(r"^admin$", LaunderetteListView.as_view(), name="launderette_list"),
|
path("admin/", LaunderetteListView.as_view(), name="launderette_list"),
|
||||||
re_path(
|
path(
|
||||||
r"^admin/(?P<launderette_id>[0-9]+)$",
|
"admin/<int:launderette_id>/",
|
||||||
LaunderetteAdminView.as_view(),
|
LaunderetteAdminView.as_view(),
|
||||||
name="launderette_admin",
|
name="launderette_admin",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^admin/(?P<launderette_id>[0-9]+)/edit$",
|
"admin/<int:launderette_id>/edit/",
|
||||||
LaunderetteEditView.as_view(),
|
LaunderetteEditView.as_view(),
|
||||||
name="launderette_edit",
|
name="launderette_edit",
|
||||||
),
|
),
|
||||||
re_path(r"^admin/new$", LaunderetteCreateView.as_view(), name="launderette_new"),
|
path("admin/new/", LaunderetteCreateView.as_view(), name="launderette_new"),
|
||||||
re_path(r"^admin/machine/new$", MachineCreateView.as_view(), name="machine_new"),
|
path("admin/machine/new/", MachineCreateView.as_view(), name="machine_new"),
|
||||||
re_path(
|
path(
|
||||||
r"^admin/machine/(?P<machine_id>[0-9]+)/edit$",
|
"admin/machine/<int:machine_id>/edit/",
|
||||||
MachineEditView.as_view(),
|
MachineEditView.as_view(),
|
||||||
name="machine_edit",
|
name="machine_edit",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^admin/machine/(?P<machine_id>[0-9]+)/delete$",
|
"admin/machine/<int:machine_id>/delete/",
|
||||||
MachineDeleteView.as_view(),
|
MachineDeleteView.as_view(),
|
||||||
name="machine_delete",
|
name="machine_delete",
|
||||||
),
|
),
|
||||||
|
@ -3070,7 +3070,7 @@ msgid "Eboutic invoices"
|
|||||||
msgstr "Facture eboutic"
|
msgstr "Facture eboutic"
|
||||||
|
|
||||||
#: core/templates/core/user_account.jinja:57
|
#: core/templates/core/user_account.jinja:57
|
||||||
#: core/templates/core/user_tools.jinja:37 counter/views.py:795
|
#: core/templates/core/user_tools.jinja:37 counter/views.py:787
|
||||||
msgid "Etickets"
|
msgid "Etickets"
|
||||||
msgstr "Etickets"
|
msgstr "Etickets"
|
||||||
|
|
||||||
@ -3381,7 +3381,7 @@ msgstr "Achats"
|
|||||||
msgid "Product top 10"
|
msgid "Product top 10"
|
||||||
msgstr "Top 10 produits"
|
msgstr "Top 10 produits"
|
||||||
|
|
||||||
#: core/templates/core/user_stats.jinja:27 counter/forms.py:176
|
#: core/templates/core/user_stats.jinja:27 counter/forms.py:168
|
||||||
msgid "Product"
|
msgid "Product"
|
||||||
msgstr "Produit"
|
msgstr "Produit"
|
||||||
|
|
||||||
@ -3717,24 +3717,24 @@ msgstr "Galaxie"
|
|||||||
msgid "User already has a profile picture"
|
msgid "User already has a profile picture"
|
||||||
msgstr "L'utilisateur a déjà une photo de profil"
|
msgstr "L'utilisateur a déjà une photo de profil"
|
||||||
|
|
||||||
#: counter/app.py:31 counter/models.py:341 counter/models.py:750
|
#: counter/app.py:31 counter/models.py:340 counter/models.py:749
|
||||||
#: counter/models.py:786 launderette/models.py:41 stock/models.py:43
|
#: counter/models.py:779 launderette/models.py:41 stock/models.py:43
|
||||||
msgid "counter"
|
msgid "counter"
|
||||||
msgstr "comptoir"
|
msgstr "comptoir"
|
||||||
|
|
||||||
#: counter/forms.py:38
|
#: counter/forms.py:30
|
||||||
msgid "This UID is invalid"
|
msgid "This UID is invalid"
|
||||||
msgstr "Cet UID est invalide"
|
msgstr "Cet UID est invalide"
|
||||||
|
|
||||||
#: counter/forms.py:77
|
#: counter/forms.py:69
|
||||||
msgid "User not found"
|
msgid "User not found"
|
||||||
msgstr "Utilisateur non trouvé"
|
msgstr "Utilisateur non trouvé"
|
||||||
|
|
||||||
#: counter/forms.py:125
|
#: counter/forms.py:117
|
||||||
msgid "Parent product"
|
msgid "Parent product"
|
||||||
msgstr "Produit parent"
|
msgstr "Produit parent"
|
||||||
|
|
||||||
#: counter/forms.py:131
|
#: counter/forms.py:123
|
||||||
msgid "Buying groups"
|
msgid "Buying groups"
|
||||||
msgstr "Groupes d'achat"
|
msgstr "Groupes d'achat"
|
||||||
|
|
||||||
@ -3758,7 +3758,7 @@ msgstr "client"
|
|||||||
msgid "customers"
|
msgid "customers"
|
||||||
msgstr "clients"
|
msgstr "clients"
|
||||||
|
|
||||||
#: counter/models.py:126 counter/views.py:317
|
#: counter/models.py:126 counter/views.py:309
|
||||||
msgid "Not enough money"
|
msgid "Not enough money"
|
||||||
msgstr "Solde insuffisant"
|
msgstr "Solde insuffisant"
|
||||||
|
|
||||||
@ -3774,133 +3774,133 @@ msgstr "Nom de famille"
|
|||||||
msgid "Address 1"
|
msgid "Address 1"
|
||||||
msgstr "Adresse 1"
|
msgstr "Adresse 1"
|
||||||
|
|
||||||
#: counter/models.py:160
|
#: counter/models.py:161
|
||||||
msgid "Address 2"
|
msgid "Address 2"
|
||||||
msgstr "Adresse 2"
|
msgstr "Adresse 2"
|
||||||
|
|
||||||
#: counter/models.py:161
|
#: counter/models.py:163
|
||||||
msgid "Zip code"
|
msgid "Zip code"
|
||||||
msgstr "Code postal"
|
msgstr "Code postal"
|
||||||
|
|
||||||
#: counter/models.py:162
|
#: counter/models.py:164
|
||||||
msgid "City"
|
msgid "City"
|
||||||
msgstr "Ville"
|
msgstr "Ville"
|
||||||
|
|
||||||
#: counter/models.py:163
|
#: counter/models.py:165
|
||||||
msgid "Country"
|
msgid "Country"
|
||||||
msgstr "Pays"
|
msgstr "Pays"
|
||||||
|
|
||||||
#: counter/models.py:206 counter/models.py:234
|
#: counter/models.py:209 counter/models.py:237
|
||||||
msgid "product type"
|
msgid "product type"
|
||||||
msgstr "type du produit"
|
msgstr "type du produit"
|
||||||
|
|
||||||
#: counter/models.py:240
|
#: counter/models.py:243
|
||||||
msgid "purchase price"
|
msgid "purchase price"
|
||||||
msgstr "prix d'achat"
|
msgstr "prix d'achat"
|
||||||
|
|
||||||
#: counter/models.py:241
|
#: counter/models.py:244
|
||||||
msgid "selling price"
|
msgid "selling price"
|
||||||
msgstr "prix de vente"
|
msgstr "prix de vente"
|
||||||
|
|
||||||
#: counter/models.py:242
|
#: counter/models.py:245
|
||||||
msgid "special selling price"
|
msgid "special selling price"
|
||||||
msgstr "prix de vente spécial"
|
msgstr "prix de vente spécial"
|
||||||
|
|
||||||
#: counter/models.py:244
|
#: counter/models.py:247
|
||||||
msgid "icon"
|
msgid "icon"
|
||||||
msgstr "icône"
|
msgstr "icône"
|
||||||
|
|
||||||
#: counter/models.py:249
|
#: counter/models.py:252
|
||||||
msgid "limit age"
|
msgid "limit age"
|
||||||
msgstr "âge limite"
|
msgstr "âge limite"
|
||||||
|
|
||||||
#: counter/models.py:250
|
#: counter/models.py:253
|
||||||
msgid "tray price"
|
msgid "tray price"
|
||||||
msgstr "prix plateau"
|
msgstr "prix plateau"
|
||||||
|
|
||||||
#: counter/models.py:254
|
#: counter/models.py:257
|
||||||
msgid "parent product"
|
msgid "parent product"
|
||||||
msgstr "produit parent"
|
msgstr "produit parent"
|
||||||
|
|
||||||
#: counter/models.py:260
|
#: counter/models.py:263
|
||||||
msgid "buying groups"
|
msgid "buying groups"
|
||||||
msgstr "groupe d'achat"
|
msgstr "groupe d'achat"
|
||||||
|
|
||||||
#: counter/models.py:262 election/models.py:52
|
#: counter/models.py:265 election/models.py:52
|
||||||
msgid "archived"
|
msgid "archived"
|
||||||
msgstr "archivé"
|
msgstr "archivé"
|
||||||
|
|
||||||
#: counter/models.py:265 counter/models.py:881
|
#: counter/models.py:268 counter/models.py:874
|
||||||
msgid "product"
|
msgid "product"
|
||||||
msgstr "produit"
|
msgstr "produit"
|
||||||
|
|
||||||
#: counter/models.py:322
|
#: counter/models.py:321
|
||||||
msgid "products"
|
msgid "products"
|
||||||
msgstr "produits"
|
msgstr "produits"
|
||||||
|
|
||||||
#: counter/models.py:325
|
#: counter/models.py:324
|
||||||
msgid "counter type"
|
msgid "counter type"
|
||||||
msgstr "type de comptoir"
|
msgstr "type de comptoir"
|
||||||
|
|
||||||
#: counter/models.py:327
|
#: counter/models.py:326
|
||||||
msgid "Bar"
|
msgid "Bar"
|
||||||
msgstr "Bar"
|
msgstr "Bar"
|
||||||
|
|
||||||
#: counter/models.py:327
|
#: counter/models.py:326
|
||||||
msgid "Office"
|
msgid "Office"
|
||||||
msgstr "Bureau"
|
msgstr "Bureau"
|
||||||
|
|
||||||
#: counter/models.py:330
|
#: counter/models.py:329
|
||||||
msgid "sellers"
|
msgid "sellers"
|
||||||
msgstr "vendeurs"
|
msgstr "vendeurs"
|
||||||
|
|
||||||
#: counter/models.py:338 launderette/models.py:207
|
#: counter/models.py:337 launderette/models.py:207
|
||||||
msgid "token"
|
msgid "token"
|
||||||
msgstr "jeton"
|
msgstr "jeton"
|
||||||
|
|
||||||
#: counter/models.py:493
|
#: counter/models.py:492
|
||||||
msgid "bank"
|
msgid "bank"
|
||||||
msgstr "banque"
|
msgstr "banque"
|
||||||
|
|
||||||
#: counter/models.py:495 counter/models.py:585
|
#: counter/models.py:494 counter/models.py:584
|
||||||
msgid "is validated"
|
msgid "is validated"
|
||||||
msgstr "est validé"
|
msgstr "est validé"
|
||||||
|
|
||||||
#: counter/models.py:498
|
#: counter/models.py:497
|
||||||
msgid "refilling"
|
msgid "refilling"
|
||||||
msgstr "rechargement"
|
msgstr "rechargement"
|
||||||
|
|
||||||
#: counter/models.py:562 eboutic/models.py:288
|
#: counter/models.py:561 eboutic/models.py:292
|
||||||
msgid "unit price"
|
msgid "unit price"
|
||||||
msgstr "prix unitaire"
|
msgstr "prix unitaire"
|
||||||
|
|
||||||
#: counter/models.py:563 counter/models.py:866 eboutic/models.py:289
|
#: counter/models.py:562 counter/models.py:859 eboutic/models.py:293
|
||||||
msgid "quantity"
|
msgid "quantity"
|
||||||
msgstr "quantité"
|
msgstr "quantité"
|
||||||
|
|
||||||
#: counter/models.py:582
|
#: counter/models.py:581
|
||||||
msgid "Sith account"
|
msgid "Sith account"
|
||||||
msgstr "Compte utilisateur"
|
msgstr "Compte utilisateur"
|
||||||
|
|
||||||
#: counter/models.py:582 sith/settings.py:383 sith/settings.py:388
|
#: counter/models.py:581 sith/settings.py:359 sith/settings.py:364
|
||||||
#: sith/settings.py:408
|
#: sith/settings.py:384
|
||||||
msgid "Credit card"
|
msgid "Credit card"
|
||||||
msgstr "Carte bancaire"
|
msgstr "Carte bancaire"
|
||||||
|
|
||||||
#: counter/models.py:588
|
#: counter/models.py:587
|
||||||
msgid "selling"
|
msgid "selling"
|
||||||
msgstr "vente"
|
msgstr "vente"
|
||||||
|
|
||||||
#: counter/models.py:615
|
#: counter/models.py:614
|
||||||
msgid "Unknown event"
|
msgid "Unknown event"
|
||||||
msgstr "Événement inconnu"
|
msgstr "Événement inconnu"
|
||||||
|
|
||||||
#: counter/models.py:616
|
#: counter/models.py:615
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Eticket bought for the event %(event)s"
|
msgid "Eticket bought for the event %(event)s"
|
||||||
msgstr "Eticket acheté pour l'événement %(event)s"
|
msgstr "Eticket acheté pour l'événement %(event)s"
|
||||||
|
|
||||||
#: counter/models.py:618 counter/models.py:641
|
#: counter/models.py:617 counter/models.py:640
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"You bought an eticket for the event %(event)s.\n"
|
"You bought an eticket for the event %(event)s.\n"
|
||||||
@ -3912,59 +3912,59 @@ msgstr ""
|
|||||||
"Vous pouvez également retrouver tous vos e-tickets sur votre page de compte "
|
"Vous pouvez également retrouver tous vos e-tickets sur votre page de compte "
|
||||||
"%(url)s."
|
"%(url)s."
|
||||||
|
|
||||||
#: counter/models.py:755
|
#: counter/models.py:754
|
||||||
msgid "last activity date"
|
msgid "last activity date"
|
||||||
msgstr "dernière activité"
|
msgstr "dernière activité"
|
||||||
|
|
||||||
#: counter/models.py:758
|
#: counter/models.py:757
|
||||||
msgid "permanency"
|
msgid "permanency"
|
||||||
msgstr "permanence"
|
msgstr "permanence"
|
||||||
|
|
||||||
#: counter/models.py:791
|
#: counter/models.py:784
|
||||||
msgid "emptied"
|
msgid "emptied"
|
||||||
msgstr "coffre vidée"
|
msgstr "coffre vidée"
|
||||||
|
|
||||||
#: counter/models.py:794
|
#: counter/models.py:787
|
||||||
msgid "cash register summary"
|
msgid "cash register summary"
|
||||||
msgstr "relevé de caisse"
|
msgstr "relevé de caisse"
|
||||||
|
|
||||||
#: counter/models.py:862
|
#: counter/models.py:855
|
||||||
msgid "cash summary"
|
msgid "cash summary"
|
||||||
msgstr "relevé"
|
msgstr "relevé"
|
||||||
|
|
||||||
#: counter/models.py:865
|
#: counter/models.py:858
|
||||||
msgid "value"
|
msgid "value"
|
||||||
msgstr "valeur"
|
msgstr "valeur"
|
||||||
|
|
||||||
#: counter/models.py:867
|
#: counter/models.py:860
|
||||||
msgid "check"
|
msgid "check"
|
||||||
msgstr "chèque"
|
msgstr "chèque"
|
||||||
|
|
||||||
#: counter/models.py:870
|
#: counter/models.py:863
|
||||||
msgid "cash register summary item"
|
msgid "cash register summary item"
|
||||||
msgstr "élément de relevé de caisse"
|
msgstr "élément de relevé de caisse"
|
||||||
|
|
||||||
#: counter/models.py:885
|
#: counter/models.py:878
|
||||||
msgid "banner"
|
msgid "banner"
|
||||||
msgstr "bannière"
|
msgstr "bannière"
|
||||||
|
|
||||||
#: counter/models.py:887
|
#: counter/models.py:880
|
||||||
msgid "event date"
|
msgid "event date"
|
||||||
msgstr "date de l'événement"
|
msgstr "date de l'événement"
|
||||||
|
|
||||||
#: counter/models.py:889
|
#: counter/models.py:882
|
||||||
msgid "event title"
|
msgid "event title"
|
||||||
msgstr "titre de l'événement"
|
msgstr "titre de l'événement"
|
||||||
|
|
||||||
#: counter/models.py:891
|
#: counter/models.py:884
|
||||||
msgid "secret"
|
msgid "secret"
|
||||||
msgstr "secret"
|
msgstr "secret"
|
||||||
|
|
||||||
#: counter/models.py:947
|
#: counter/models.py:940
|
||||||
msgid "uid"
|
msgid "uid"
|
||||||
msgstr "uid"
|
msgstr "uid"
|
||||||
|
|
||||||
#: counter/models.py:952
|
#: counter/models.py:945
|
||||||
msgid "student cards"
|
msgid "student cards"
|
||||||
msgstr "cartes étudiante"
|
msgstr "cartes étudiante"
|
||||||
|
|
||||||
@ -4016,7 +4016,7 @@ msgstr "Liste des relevés de caisse"
|
|||||||
msgid "Theoric sums"
|
msgid "Theoric sums"
|
||||||
msgstr "Sommes théoriques"
|
msgstr "Sommes théoriques"
|
||||||
|
|
||||||
#: counter/templates/counter/cash_summary_list.jinja:36 counter/views.py:1073
|
#: counter/templates/counter/cash_summary_list.jinja:36 counter/views.py:1065
|
||||||
msgid "Emptied"
|
msgid "Emptied"
|
||||||
msgstr "Coffre vidé"
|
msgstr "Coffre vidé"
|
||||||
|
|
||||||
@ -4074,11 +4074,11 @@ msgstr "Terminer"
|
|||||||
msgid "Refilling"
|
msgid "Refilling"
|
||||||
msgstr "Rechargement"
|
msgstr "Rechargement"
|
||||||
|
|
||||||
#: counter/templates/counter/counter_click.jinja:193 counter/views.py:586
|
#: counter/templates/counter/counter_click.jinja:193 counter/views.py:578
|
||||||
msgid "END"
|
msgid "END"
|
||||||
msgstr "FIN"
|
msgstr "FIN"
|
||||||
|
|
||||||
#: counter/templates/counter/counter_click.jinja:193 counter/views.py:588
|
#: counter/templates/counter/counter_click.jinja:193 counter/views.py:580
|
||||||
msgid "CAN"
|
msgid "CAN"
|
||||||
msgstr "ANN"
|
msgstr "ANN"
|
||||||
|
|
||||||
@ -4244,109 +4244,109 @@ msgstr "Temps"
|
|||||||
msgid "Top 100 barman %(counter_name)s (all semesters)"
|
msgid "Top 100 barman %(counter_name)s (all semesters)"
|
||||||
msgstr "Top 100 barman %(counter_name)s (tous les semestres)"
|
msgstr "Top 100 barman %(counter_name)s (tous les semestres)"
|
||||||
|
|
||||||
#: counter/views.py:175
|
#: counter/views.py:167
|
||||||
msgid "Cash summary"
|
msgid "Cash summary"
|
||||||
msgstr "Relevé de caisse"
|
msgstr "Relevé de caisse"
|
||||||
|
|
||||||
#: counter/views.py:189
|
#: counter/views.py:181
|
||||||
msgid "Last operations"
|
msgid "Last operations"
|
||||||
msgstr "Dernières opérations"
|
msgstr "Dernières opérations"
|
||||||
|
|
||||||
#: counter/views.py:204
|
#: counter/views.py:196
|
||||||
msgid "Take items from stock"
|
msgid "Take items from stock"
|
||||||
msgstr "Prendre des éléments du stock"
|
msgstr "Prendre des éléments du stock"
|
||||||
|
|
||||||
#: counter/views.py:257
|
#: counter/views.py:249
|
||||||
msgid "Bad credentials"
|
msgid "Bad credentials"
|
||||||
msgstr "Mauvais identifiants"
|
msgstr "Mauvais identifiants"
|
||||||
|
|
||||||
#: counter/views.py:259
|
#: counter/views.py:251
|
||||||
msgid "User is not barman"
|
msgid "User is not barman"
|
||||||
msgstr "L'utilisateur n'est pas barman."
|
msgstr "L'utilisateur n'est pas barman."
|
||||||
|
|
||||||
#: counter/views.py:264
|
#: counter/views.py:256
|
||||||
msgid "Bad location, someone is already logged in somewhere else"
|
msgid "Bad location, someone is already logged in somewhere else"
|
||||||
msgstr "Mauvais comptoir, quelqu'un est déjà connecté ailleurs"
|
msgstr "Mauvais comptoir, quelqu'un est déjà connecté ailleurs"
|
||||||
|
|
||||||
#: counter/views.py:308
|
#: counter/views.py:300
|
||||||
msgid "Too young for that product"
|
msgid "Too young for that product"
|
||||||
msgstr "Trop jeune pour ce produit"
|
msgstr "Trop jeune pour ce produit"
|
||||||
|
|
||||||
#: counter/views.py:311
|
#: counter/views.py:303
|
||||||
msgid "Not allowed for that product"
|
msgid "Not allowed for that product"
|
||||||
msgstr "Non autorisé pour ce produit"
|
msgstr "Non autorisé pour ce produit"
|
||||||
|
|
||||||
#: counter/views.py:314
|
#: counter/views.py:306
|
||||||
msgid "No date of birth provided"
|
msgid "No date of birth provided"
|
||||||
msgstr "Pas de date de naissance renseignée"
|
msgstr "Pas de date de naissance renseignée"
|
||||||
|
|
||||||
#: counter/views.py:611
|
#: counter/views.py:603
|
||||||
msgid "You have not enough money to buy all the basket"
|
msgid "You have not enough money to buy all the basket"
|
||||||
msgstr "Vous n'avez pas assez d'argent pour acheter le panier"
|
msgstr "Vous n'avez pas assez d'argent pour acheter le panier"
|
||||||
|
|
||||||
#: counter/views.py:759
|
#: counter/views.py:751
|
||||||
msgid "Counter administration"
|
msgid "Counter administration"
|
||||||
msgstr "Administration des comptoirs"
|
msgstr "Administration des comptoirs"
|
||||||
|
|
||||||
#: counter/views.py:761
|
#: counter/views.py:753
|
||||||
msgid "Stocks"
|
msgid "Stocks"
|
||||||
msgstr "Stocks"
|
msgstr "Stocks"
|
||||||
|
|
||||||
#: counter/views.py:780
|
#: counter/views.py:772
|
||||||
msgid "Product types"
|
msgid "Product types"
|
||||||
msgstr "Types de produit"
|
msgstr "Types de produit"
|
||||||
|
|
||||||
#: counter/views.py:1030
|
#: counter/views.py:1022
|
||||||
msgid "10 cents"
|
msgid "10 cents"
|
||||||
msgstr "10 centimes"
|
msgstr "10 centimes"
|
||||||
|
|
||||||
#: counter/views.py:1031
|
#: counter/views.py:1023
|
||||||
msgid "20 cents"
|
msgid "20 cents"
|
||||||
msgstr "20 centimes"
|
msgstr "20 centimes"
|
||||||
|
|
||||||
#: counter/views.py:1032
|
#: counter/views.py:1024
|
||||||
msgid "50 cents"
|
msgid "50 cents"
|
||||||
msgstr "50 centimes"
|
msgstr "50 centimes"
|
||||||
|
|
||||||
#: counter/views.py:1033
|
#: counter/views.py:1025
|
||||||
msgid "1 euro"
|
msgid "1 euro"
|
||||||
msgstr "1 €"
|
msgstr "1 €"
|
||||||
|
|
||||||
#: counter/views.py:1034
|
#: counter/views.py:1026
|
||||||
msgid "2 euros"
|
msgid "2 euros"
|
||||||
msgstr "2 €"
|
msgstr "2 €"
|
||||||
|
|
||||||
#: counter/views.py:1035
|
#: counter/views.py:1027
|
||||||
msgid "5 euros"
|
msgid "5 euros"
|
||||||
msgstr "5 €"
|
msgstr "5 €"
|
||||||
|
|
||||||
#: counter/views.py:1036
|
#: counter/views.py:1028
|
||||||
msgid "10 euros"
|
msgid "10 euros"
|
||||||
msgstr "10 €"
|
msgstr "10 €"
|
||||||
|
|
||||||
#: counter/views.py:1037
|
#: counter/views.py:1029
|
||||||
msgid "20 euros"
|
msgid "20 euros"
|
||||||
msgstr "20 €"
|
msgstr "20 €"
|
||||||
|
|
||||||
#: counter/views.py:1038
|
#: counter/views.py:1030
|
||||||
msgid "50 euros"
|
msgid "50 euros"
|
||||||
msgstr "50 €"
|
msgstr "50 €"
|
||||||
|
|
||||||
#: counter/views.py:1040
|
#: counter/views.py:1032
|
||||||
msgid "100 euros"
|
msgid "100 euros"
|
||||||
msgstr "100 €"
|
msgstr "100 €"
|
||||||
|
|
||||||
#: counter/views.py:1043 counter/views.py:1049 counter/views.py:1055
|
#: counter/views.py:1035 counter/views.py:1041 counter/views.py:1047
|
||||||
#: counter/views.py:1061 counter/views.py:1067
|
#: counter/views.py:1053 counter/views.py:1059
|
||||||
msgid "Check amount"
|
msgid "Check amount"
|
||||||
msgstr "Montant du chèque"
|
msgstr "Montant du chèque"
|
||||||
|
|
||||||
#: counter/views.py:1046 counter/views.py:1052 counter/views.py:1058
|
#: counter/views.py:1038 counter/views.py:1044 counter/views.py:1050
|
||||||
#: counter/views.py:1064 counter/views.py:1070
|
#: counter/views.py:1056 counter/views.py:1062
|
||||||
msgid "Check quantity"
|
msgid "Check quantity"
|
||||||
msgstr "Nombre de chèque"
|
msgstr "Nombre de chèque"
|
||||||
|
|
||||||
#: counter/views.py:1684
|
#: counter/views.py:1676
|
||||||
msgid "people(s)"
|
msgid "people(s)"
|
||||||
msgstr "personne(s)"
|
msgstr "personne(s)"
|
||||||
|
|
||||||
@ -4371,37 +4371,37 @@ msgstr "Votre panier est vide"
|
|||||||
msgid "%(name)s : this product does not exist."
|
msgid "%(name)s : this product does not exist."
|
||||||
msgstr "%(name)s : ce produit n'existe pas."
|
msgstr "%(name)s : ce produit n'existe pas."
|
||||||
|
|
||||||
#: eboutic/forms.py:150
|
#: eboutic/forms.py:134
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(name)s : this product does not exist or may no longer be available."
|
msgid "%(name)s : this product does not exist or may no longer be available."
|
||||||
msgstr "%(name)s : ce produit n'existe pas ou n'est peut-être plus disponible."
|
msgstr "%(name)s : ce produit n'existe pas ou n'est peut-être plus disponible."
|
||||||
|
|
||||||
#: eboutic/forms.py:157
|
#: eboutic/forms.py:141
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "You cannot buy %(nbr)d %(name)s."
|
msgid "You cannot buy %(nbr)d %(name)s."
|
||||||
msgstr "Vous ne pouvez pas acheter %(nbr)d %(name)s."
|
msgstr "Vous ne pouvez pas acheter %(nbr)d %(name)s."
|
||||||
|
|
||||||
#: eboutic/models.py:237
|
#: eboutic/models.py:241
|
||||||
msgid "validated"
|
msgid "validated"
|
||||||
msgstr "validé"
|
msgstr "validé"
|
||||||
|
|
||||||
#: eboutic/models.py:247
|
#: eboutic/models.py:251
|
||||||
msgid "Invoice already validated"
|
msgid "Invoice already validated"
|
||||||
msgstr "Facture déjà validée"
|
msgstr "Facture déjà validée"
|
||||||
|
|
||||||
#: eboutic/models.py:285
|
#: eboutic/models.py:289
|
||||||
msgid "product id"
|
msgid "product id"
|
||||||
msgstr "ID du produit"
|
msgstr "ID du produit"
|
||||||
|
|
||||||
#: eboutic/models.py:286
|
#: eboutic/models.py:290
|
||||||
msgid "product name"
|
msgid "product name"
|
||||||
msgstr "nom du produit"
|
msgstr "nom du produit"
|
||||||
|
|
||||||
#: eboutic/models.py:287
|
#: eboutic/models.py:291
|
||||||
msgid "product type id"
|
msgid "product type id"
|
||||||
msgstr "id du type du produit"
|
msgstr "id du type du produit"
|
||||||
|
|
||||||
#: eboutic/models.py:304
|
#: eboutic/models.py:308
|
||||||
msgid "basket"
|
msgid "basket"
|
||||||
msgstr "panier"
|
msgstr "panier"
|
||||||
|
|
||||||
@ -4467,18 +4467,18 @@ msgstr ""
|
|||||||
"Vous devez renseigner vos coordonnées de facturation si vous voulez payer "
|
"Vous devez renseigner vos coordonnées de facturation si vous voulez payer "
|
||||||
"par carte bancaire"
|
"par carte bancaire"
|
||||||
|
|
||||||
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:111
|
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:112
|
||||||
msgid "Pay with credit card"
|
msgid "Pay with credit card"
|
||||||
msgstr "Payer avec une carte bancaire"
|
msgstr "Payer avec une carte bancaire"
|
||||||
|
|
||||||
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:115
|
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:116
|
||||||
msgid ""
|
msgid ""
|
||||||
"AE account payment disabled because your basket contains refilling items."
|
"AE account payment disabled because your basket contains refilling items."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Paiement par compte AE désactivé parce que votre panier contient des bons de "
|
"Paiement par compte AE désactivé parce que votre panier contient des bons de "
|
||||||
"rechargement."
|
"rechargement."
|
||||||
|
|
||||||
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:120
|
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:121
|
||||||
msgid "Pay with Sith account"
|
msgid "Pay with Sith account"
|
||||||
msgstr "Payer avec un compte AE"
|
msgstr "Payer avec un compte AE"
|
||||||
|
|
||||||
|
@ -22,13 +22,13 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.urls import re_path
|
from django.urls import path
|
||||||
|
|
||||||
from matmat.views import *
|
from matmat.views import *
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
re_path(r"^$", SearchNormalFormView.as_view(), name="search"),
|
path("", SearchNormalFormView.as_view(), name="search"),
|
||||||
re_path(r"^reverse$", SearchReverseFormView.as_view(), name="search_reverse"),
|
path("reverse/", SearchReverseFormView.as_view(), name="search_reverse"),
|
||||||
re_path(r"^quick$", SearchQuickFormView.as_view(), name="search_quick"),
|
path("quick/", SearchQuickFormView.as_view(), name="search_quick"),
|
||||||
re_path(r"^clear$", SearchClearFormView.as_view(), name="search_clear"),
|
path("clear/", SearchClearFormView.as_view(), name="search_clear"),
|
||||||
]
|
]
|
||||||
|
@ -732,9 +732,9 @@ class UVSearchTest(TestCase):
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"absolute_url": "/pedagogy/uv/1",
|
"absolute_url": "/pedagogy/uv/1/",
|
||||||
"update_url": "/pedagogy/uv/1/edit",
|
"update_url": "/pedagogy/uv/1/edit/",
|
||||||
"delete_url": "/pedagogy/uv/1/delete",
|
"delete_url": "/pedagogy/uv/1/delete/",
|
||||||
"code": "PA00",
|
"code": "PA00",
|
||||||
"author": 0,
|
"author": 0,
|
||||||
"credit_type": "OM",
|
"credit_type": "OM",
|
||||||
|
@ -22,33 +22,33 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.urls import re_path
|
from django.urls import path
|
||||||
|
|
||||||
from pedagogy.views import *
|
from pedagogy.views import *
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# Urls displaying the actual application for visitors
|
# Urls displaying the actual application for visitors
|
||||||
re_path(r"^$", UVListView.as_view(), name="guide"),
|
path("", UVListView.as_view(), name="guide"),
|
||||||
re_path(r"^uv/(?P<uv_id>[0-9]+)$", UVDetailFormView.as_view(), name="uv_detail"),
|
path("uv/<int:uv_id>/", UVDetailFormView.as_view(), name="uv_detail"),
|
||||||
re_path(
|
path(
|
||||||
r"^comment/(?P<comment_id>[0-9]+)/edit$",
|
"comment/<int:comment_id>/edit/",
|
||||||
UVCommentUpdateView.as_view(),
|
UVCommentUpdateView.as_view(),
|
||||||
name="comment_update",
|
name="comment_update",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^comment/(?P<comment_id>[0-9]+)/delete$",
|
"comment/<int:comment_id>/delete/",
|
||||||
UVCommentDeleteView.as_view(),
|
UVCommentDeleteView.as_view(),
|
||||||
name="comment_delete",
|
name="comment_delete",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^comment/(?P<comment_id>[0-9]+)/report$",
|
"comment/<int:comment_id>/report/",
|
||||||
UVCommentReportCreateView.as_view(),
|
UVCommentReportCreateView.as_view(),
|
||||||
name="comment_report",
|
name="comment_report",
|
||||||
),
|
),
|
||||||
# Moderation
|
# Moderation
|
||||||
re_path(r"^moderation$", UVModerationFormView.as_view(), name="moderation"),
|
path("moderation/", UVModerationFormView.as_view(), name="moderation"),
|
||||||
# Administration : Create Update Delete Edit
|
# Administration : Create Update Delete Edit
|
||||||
re_path(r"^uv/create$", UVCreateView.as_view(), name="uv_create"),
|
path("uv/create/", UVCreateView.as_view(), name="uv_create"),
|
||||||
re_path(r"^uv/(?P<uv_id>[0-9]+)/delete$", UVDeleteView.as_view(), name="uv_delete"),
|
path("uv/<int:uv_id>/delete/", UVDeleteView.as_view(), name="uv_delete"),
|
||||||
re_path(r"^uv/(?P<uv_id>[0-9]+)/edit$", UVUpdateView.as_view(), name="uv_update"),
|
path("uv/<int:uv_id>/edit/", UVUpdateView.as_view(), name="uv_update"),
|
||||||
]
|
]
|
||||||
|
@ -21,7 +21,212 @@
|
|||||||
# Place - Suite 330, Boston, MA 02111-1307, USA.
|
# Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
from datetime import date, timedelta
|
||||||
|
|
||||||
|
from django.core.management import call_command
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
# Create your tests here.
|
from club.models import Club
|
||||||
|
from core.models import User, RealGroup
|
||||||
|
from counter.models import Customer, Product, Selling, Counter, Refilling
|
||||||
|
from subscription.models import Subscription
|
||||||
|
|
||||||
|
|
||||||
|
class MergeUserTest(TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super().setUpClass()
|
||||||
|
call_command("populate")
|
||||||
|
cls.ae = Club.objects.get(unix_name="ae")
|
||||||
|
cls.eboutic = Counter.objects.get(name="Eboutic")
|
||||||
|
cls.barbar = Product.objects.get(code="BARB")
|
||||||
|
cls.barbar.selling_price = 2
|
||||||
|
cls.barbar.save()
|
||||||
|
cls.root = User.objects.get(username="root")
|
||||||
|
|
||||||
|
def setUp(self) -> None:
|
||||||
|
super().setUp()
|
||||||
|
self.to_keep = User(username="to_keep", password="plop", email="u.1@utbm.fr")
|
||||||
|
self.to_delete = User(username="to_del", password="plop", email="u.2@utbm.fr")
|
||||||
|
self.to_keep.save()
|
||||||
|
self.to_delete.save()
|
||||||
|
self.client.login(username="root", password="plop")
|
||||||
|
|
||||||
|
def test_simple(self):
|
||||||
|
self.to_delete.first_name = "Biggus"
|
||||||
|
self.to_keep.last_name = "Dickus"
|
||||||
|
self.to_keep.nick_name = "B'ian"
|
||||||
|
self.to_keep.address = "Jerusalem"
|
||||||
|
self.to_delete.parent_address = "Rome"
|
||||||
|
self.to_delete.address = "Rome"
|
||||||
|
subscribers = RealGroup.objects.get(name="Subscribers")
|
||||||
|
mde_admin = RealGroup.objects.get(name="MDE admin")
|
||||||
|
sas_admin = RealGroup.objects.get(name="SAS admin")
|
||||||
|
self.to_keep.groups.add(subscribers.id)
|
||||||
|
self.to_delete.groups.add(mde_admin.id)
|
||||||
|
self.to_keep.groups.add(sas_admin.id)
|
||||||
|
self.to_delete.groups.add(sas_admin.id)
|
||||||
|
self.to_delete.save()
|
||||||
|
self.to_keep.save()
|
||||||
|
data = {"user1": self.to_keep.id, "user2": self.to_delete.id}
|
||||||
|
res = self.client.post(reverse("rootplace:merge"), data)
|
||||||
|
self.assertRedirects(res, self.to_keep.get_absolute_url())
|
||||||
|
self.assertFalse(User.objects.filter(pk=self.to_delete.pk).exists())
|
||||||
|
self.to_keep = User.objects.get(pk=self.to_keep.pk)
|
||||||
|
# fields of to_delete should be assigned to to_keep
|
||||||
|
# if they were not set beforehand
|
||||||
|
self.assertEqual("Biggus", self.to_keep.first_name)
|
||||||
|
self.assertEqual("Dickus", self.to_keep.last_name)
|
||||||
|
self.assertEqual("B'ian", self.to_keep.nick_name)
|
||||||
|
self.assertEqual("Jerusalem", self.to_keep.address)
|
||||||
|
self.assertEqual("Rome", self.to_keep.parent_address)
|
||||||
|
self.assertEqual(3, self.to_keep.groups.count())
|
||||||
|
groups = list(self.to_keep.groups.all())
|
||||||
|
expected = [subscribers, mde_admin, sas_admin]
|
||||||
|
self.assertCountEqual(groups, expected)
|
||||||
|
|
||||||
|
def test_both_subscribers_and_with_account(self):
|
||||||
|
Customer(user=self.to_keep, account_id="11000l", amount=0).save()
|
||||||
|
Customer(user=self.to_delete, account_id="12000m", amount=0).save()
|
||||||
|
Refilling(
|
||||||
|
amount=10,
|
||||||
|
operator=self.root,
|
||||||
|
customer=self.to_keep.customer,
|
||||||
|
counter=self.eboutic,
|
||||||
|
).save()
|
||||||
|
Refilling(
|
||||||
|
amount=20,
|
||||||
|
operator=self.root,
|
||||||
|
customer=self.to_delete.customer,
|
||||||
|
counter=self.eboutic,
|
||||||
|
).save()
|
||||||
|
Selling(
|
||||||
|
label="barbar",
|
||||||
|
counter=self.eboutic,
|
||||||
|
club=self.ae,
|
||||||
|
product=self.barbar,
|
||||||
|
customer=self.to_keep.customer,
|
||||||
|
seller=self.root,
|
||||||
|
unit_price=2,
|
||||||
|
quantity=2,
|
||||||
|
payment_method="SITH_ACCOUNT",
|
||||||
|
).save()
|
||||||
|
Selling(
|
||||||
|
label="barbar",
|
||||||
|
counter=self.eboutic,
|
||||||
|
club=self.ae,
|
||||||
|
product=self.barbar,
|
||||||
|
customer=self.to_delete.customer,
|
||||||
|
seller=self.root,
|
||||||
|
unit_price=2,
|
||||||
|
quantity=4,
|
||||||
|
payment_method="SITH_ACCOUNT",
|
||||||
|
).save()
|
||||||
|
today = date.today()
|
||||||
|
# both subscriptions began last month and shall end in 5 months
|
||||||
|
Subscription(
|
||||||
|
member=self.to_keep,
|
||||||
|
subscription_type="un-semestre",
|
||||||
|
payment_method="EBOUTIC",
|
||||||
|
subscription_start=today - timedelta(30),
|
||||||
|
subscription_end=today + timedelta(5 * 30),
|
||||||
|
).save()
|
||||||
|
Subscription(
|
||||||
|
member=self.to_delete,
|
||||||
|
subscription_type="un-semestre",
|
||||||
|
payment_method="EBOUTIC",
|
||||||
|
subscription_start=today - timedelta(30),
|
||||||
|
subscription_end=today + timedelta(5 * 30),
|
||||||
|
).save()
|
||||||
|
data = {"user1": self.to_keep.id, "user2": self.to_delete.id}
|
||||||
|
res = self.client.post(reverse("rootplace:merge"), data)
|
||||||
|
self.to_keep = User.objects.get(pk=self.to_keep.id)
|
||||||
|
self.assertRedirects(res, self.to_keep.get_absolute_url())
|
||||||
|
# to_keep had 10€ at first and bought 2 barbar worth 2€ each
|
||||||
|
# to_delete had 20€ and bought 4 barbar
|
||||||
|
# total should be 10 - 4 + 20 - 8 = 18
|
||||||
|
self.assertAlmostEqual(18, self.to_keep.customer.amount, delta=0.0001)
|
||||||
|
self.assertEqual(2, self.to_keep.customer.buyings.count())
|
||||||
|
self.assertEqual(2, self.to_keep.customer.refillings.count())
|
||||||
|
self.assertTrue(self.to_keep.is_subscribed)
|
||||||
|
# to_keep had 5 months of subscription remaining and received
|
||||||
|
# 5 more months from to_delete, so he should be subscribed for 10 months
|
||||||
|
self.assertEqual(
|
||||||
|
today + timedelta(10 * 30),
|
||||||
|
self.to_keep.subscriptions.order_by("subscription_end")
|
||||||
|
.last()
|
||||||
|
.subscription_end,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_godfathers(self):
|
||||||
|
users = list(User.objects.all()[:4])
|
||||||
|
self.to_keep.godfathers.add(users[0])
|
||||||
|
self.to_keep.godchildren.add(users[1])
|
||||||
|
self.to_delete.godfathers.add(users[2])
|
||||||
|
self.to_delete.godfathers.add(self.to_keep)
|
||||||
|
self.to_delete.godchildren.add(users[3])
|
||||||
|
data = {"user1": self.to_keep.id, "user2": self.to_delete.id}
|
||||||
|
res = self.client.post(reverse("rootplace:merge"), data)
|
||||||
|
self.assertRedirects(res, self.to_keep.get_absolute_url())
|
||||||
|
self.to_keep = User.objects.get(pk=self.to_keep.id)
|
||||||
|
self.assertCountEqual(list(self.to_keep.godfathers.all()), [users[0], users[2]])
|
||||||
|
self.assertCountEqual(
|
||||||
|
list(self.to_keep.godchildren.all()), [users[1], users[3]]
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_keep_has_no_account(self):
|
||||||
|
Customer(user=self.to_delete, account_id="12000m", amount=0).save()
|
||||||
|
Refilling(
|
||||||
|
amount=20,
|
||||||
|
operator=self.root,
|
||||||
|
customer=self.to_delete.customer,
|
||||||
|
counter=self.eboutic,
|
||||||
|
).save()
|
||||||
|
Selling(
|
||||||
|
label="barbar",
|
||||||
|
counter=self.eboutic,
|
||||||
|
club=self.ae,
|
||||||
|
product=self.barbar,
|
||||||
|
customer=self.to_delete.customer,
|
||||||
|
seller=self.root,
|
||||||
|
unit_price=2,
|
||||||
|
quantity=4,
|
||||||
|
payment_method="SITH_ACCOUNT",
|
||||||
|
).save()
|
||||||
|
data = {"user1": self.to_keep.id, "user2": self.to_delete.id}
|
||||||
|
res = self.client.post(reverse("rootplace:merge"), data)
|
||||||
|
self.to_keep = User.objects.get(pk=self.to_keep.id)
|
||||||
|
self.assertRedirects(res, self.to_keep.get_absolute_url())
|
||||||
|
# to_delete had 20€ and bought 4 barbar worth 2€ each
|
||||||
|
# total should be 20 - 8 = 12
|
||||||
|
self.assertTrue(hasattr(self.to_keep, "customer"))
|
||||||
|
self.assertAlmostEqual(12, self.to_keep.customer.amount, delta=0.0001)
|
||||||
|
|
||||||
|
def test_delete_has_no_account(self):
|
||||||
|
Customer(user=self.to_keep, account_id="12000m", amount=0).save()
|
||||||
|
Refilling(
|
||||||
|
amount=20,
|
||||||
|
operator=self.root,
|
||||||
|
customer=self.to_keep.customer,
|
||||||
|
counter=self.eboutic,
|
||||||
|
).save()
|
||||||
|
Selling(
|
||||||
|
label="barbar",
|
||||||
|
counter=self.eboutic,
|
||||||
|
club=self.ae,
|
||||||
|
product=self.barbar,
|
||||||
|
customer=self.to_keep.customer,
|
||||||
|
seller=self.root,
|
||||||
|
unit_price=2,
|
||||||
|
quantity=4,
|
||||||
|
payment_method="SITH_ACCOUNT",
|
||||||
|
).save()
|
||||||
|
data = {"user1": self.to_keep.id, "user2": self.to_delete.id}
|
||||||
|
res = self.client.post(reverse("rootplace:merge"), data)
|
||||||
|
self.to_keep = User.objects.get(pk=self.to_keep.id)
|
||||||
|
self.assertRedirects(res, self.to_keep.get_absolute_url())
|
||||||
|
# to_keep had 20€ and bought 4 barbar worth 2€ each
|
||||||
|
# total should be 20 - 8 = 12
|
||||||
|
self.assertTrue(hasattr(self.to_keep, "customer"))
|
||||||
|
self.assertAlmostEqual(12, self.to_keep.customer.amount, delta=0.0001)
|
||||||
|
@ -23,16 +23,16 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.urls import re_path
|
from django.urls import path
|
||||||
|
|
||||||
from rootplace.views import *
|
from rootplace.views import *
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
re_path(r"^merge$", MergeUsersView.as_view(), name="merge"),
|
path("merge/", MergeUsersView.as_view(), name="merge"),
|
||||||
re_path(
|
path(
|
||||||
r"^forum/messages/delete$",
|
"forum/messages/delete/",
|
||||||
DeleteAllForumUserMessagesView.as_view(),
|
DeleteAllForumUserMessagesView.as_view(),
|
||||||
name="delete_forum_messages",
|
name="delete_forum_messages",
|
||||||
),
|
),
|
||||||
re_path(r"^logs$", OperationLogListView.as_view(), name="operation_logs"),
|
path("logs/", OperationLogListView.as_view(), name="operation_logs"),
|
||||||
]
|
]
|
||||||
|
@ -23,72 +23,114 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.utils.translation import gettext as _
|
from ajax_select.fields import AutoCompleteSelectField
|
||||||
from django.views.generic.edit import FormView
|
|
||||||
from django.views.generic import ListView
|
|
||||||
from django.urls import reverse
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
from django.views.generic import ListView
|
||||||
|
from django.views.generic.edit import FormView
|
||||||
|
|
||||||
from ajax_select.fields import AutoCompleteSelectField
|
from core.models import User, OperationLog, SithFile
|
||||||
|
|
||||||
from core.views import CanEditPropMixin
|
from core.views import CanEditPropMixin
|
||||||
from core.models import User, OperationLog
|
|
||||||
from counter.models import Customer
|
from counter.models import Customer
|
||||||
|
|
||||||
from forum.models import ForumMessageMeta
|
from forum.models import ForumMessageMeta
|
||||||
|
|
||||||
|
|
||||||
def merge_users(u1, u2):
|
def __merge_subscriptions(u1: User, u2: User):
|
||||||
u1.nick_name = u1.nick_name or u2.nick_name
|
"""
|
||||||
u1.date_of_birth = u1.date_of_birth or u2.date_of_birth
|
Give all the subscriptions of the second user to first one
|
||||||
u1.home = u1.home or u2.home
|
If some subscriptions are still active, update their end date
|
||||||
u1.sex = u1.sex or u2.sex
|
to increase the overall subscription time of the first user.
|
||||||
u1.pronouns = u1.pronouns or u2.pronouns
|
|
||||||
u1.tshirt_size = u1.tshirt_size or u2.tshirt_size
|
Some examples :
|
||||||
u1.role = u1.role or u2.role
|
- if u1 is not subscribed, his subscription end date become the one of u2
|
||||||
u1.department = u1.department or u2.department
|
- if u1 is subscribed but not u2, nothing happen
|
||||||
u1.dpt_option = u1.dpt_option or u2.dpt_option
|
- if u1 is subscribed for, let's say, 2 remaining months and u2 is subscribed for 3 remaining months,
|
||||||
u1.semester = u1.semester or u2.semester
|
he shall then be subscribed for 5 months
|
||||||
u1.quote = u1.quote or u2.quote
|
"""
|
||||||
u1.school = u1.school or u2.school
|
last_subscription = (
|
||||||
u1.promo = u1.promo or u2.promo
|
u1.subscriptions.filter(
|
||||||
u1.forum_signature = u1.forum_signature or u2.forum_signature
|
subscription_start__lte=timezone.now(), subscription_end__gte=timezone.now()
|
||||||
u1.second_email = u1.second_email or u2.second_email
|
)
|
||||||
u1.phone = u1.phone or u2.phone
|
.order_by("subscription_end")
|
||||||
u1.parent_phone = u1.parent_phone or u2.parent_phone
|
.last()
|
||||||
u1.address = u1.address or u2.address
|
)
|
||||||
u1.parent_address = u1.parent_address or u2.parent_address
|
if last_subscription is not None:
|
||||||
|
subscription_end = last_subscription.subscription_end
|
||||||
|
for subscription in u2.subscriptions.filter(
|
||||||
|
subscription_end__gte=timezone.now()
|
||||||
|
):
|
||||||
|
subscription.subscription_start = subscription_end
|
||||||
|
if subscription.subscription_start > timezone.now().date():
|
||||||
|
remaining = subscription.subscription_end - timezone.now().date()
|
||||||
|
else:
|
||||||
|
remaining = (
|
||||||
|
subscription.subscription_end - subscription.subscription_start
|
||||||
|
)
|
||||||
|
subscription_end += remaining
|
||||||
|
subscription.subscription_end = subscription_end
|
||||||
|
subscription.save()
|
||||||
|
u2.subscriptions.all().update(member=u1)
|
||||||
|
|
||||||
|
|
||||||
|
def __merge_pictures(u1: User, u2: User) -> None:
|
||||||
|
SithFile.objects.filter(owner=u2).update(owner=u1)
|
||||||
|
if u1.profile_pict is None and u2.profile_pict is not None:
|
||||||
|
u1.profile_pict, u2.profile_pict = u2.profile_pict, None
|
||||||
|
if u1.scrub_pict is None and u2.scrub_pict is not None:
|
||||||
|
u1.scrub_pict, u2.scrub_pict = u2.scrub_pict, None
|
||||||
|
if u1.avatar_pict is None and u2.avatar_pict is not None:
|
||||||
|
u1.avatar_pict, u2.avatar_pict = u2.avatar_pict, None
|
||||||
|
u2.save()
|
||||||
u1.save()
|
u1.save()
|
||||||
for u in u2.godfathers.all():
|
|
||||||
u1.godfathers.add(u)
|
|
||||||
|
def merge_users(u1: User, u2: User) -> User:
|
||||||
|
"""
|
||||||
|
Merge u2 into u1
|
||||||
|
This means that u1 shall receive everything that belonged to u2 :
|
||||||
|
|
||||||
|
- pictures
|
||||||
|
- refills of the sith account
|
||||||
|
- purchases of any item bought on the eboutic or the counters
|
||||||
|
- subscriptions
|
||||||
|
- godfathers
|
||||||
|
- godchildren
|
||||||
|
|
||||||
|
If u1 had no account id, he shall receive the one of u2.
|
||||||
|
If u1 and u2 were both in the middle of a subscription, the remaining
|
||||||
|
durations stack
|
||||||
|
If u1 had no profile picture, he shall receive the one of u2
|
||||||
|
"""
|
||||||
|
for field in u1._meta.fields:
|
||||||
|
if not field.is_relation and not u1.__dict__[field.name]:
|
||||||
|
u1.__dict__[field.name] = u2.__dict__[field.name]
|
||||||
|
for group in u2.groups.all():
|
||||||
|
u1.groups.add(group.id)
|
||||||
|
for godfather in u2.godfathers.exclude(id=u1.id):
|
||||||
|
u1.godfathers.add(godfather)
|
||||||
|
for godchild in u2.godchildren.exclude(id=u1.id):
|
||||||
|
u1.godchildren.add(godchild)
|
||||||
|
__merge_subscriptions(u1, u2)
|
||||||
|
__merge_pictures(u1, u2)
|
||||||
|
u2.invoices.all().update(user=u1)
|
||||||
|
c_src = Customer.objects.filter(user=u2).first()
|
||||||
|
if c_src is not None:
|
||||||
|
c_dest, created = Customer.get_or_create(u1)
|
||||||
|
c_src.refillings.update(customer=c_dest)
|
||||||
|
c_src.buyings.update(customer=c_dest)
|
||||||
|
c_dest.recompute_amount()
|
||||||
|
if created:
|
||||||
|
# swap the account numbers, so that the user keep
|
||||||
|
# the id he is accustomed to
|
||||||
|
tmp_id = c_src.account_id
|
||||||
|
# delete beforehand in order not to have a unique constraint violation
|
||||||
|
c_src.delete()
|
||||||
|
c_dest.account_id = tmp_id
|
||||||
u1.save()
|
u1.save()
|
||||||
for i in u2.invoices.all():
|
u2.delete() # everything remaining in u2 gets deleted thanks to on_delete=CASCADE
|
||||||
for f in i._meta.local_fields: # I have sadly not found anything better :/
|
|
||||||
if f.name == "date":
|
|
||||||
f.auto_now = False
|
|
||||||
u1.invoices.add(i)
|
|
||||||
u1.save()
|
|
||||||
s1 = User.objects.filter(id=u1.id).first()
|
|
||||||
s2 = User.objects.filter(id=u2.id).first()
|
|
||||||
for s in s2.subscriptions.all():
|
|
||||||
s1.subscriptions.add(s)
|
|
||||||
s1.save()
|
|
||||||
c1 = Customer.objects.filter(user__id=u1.id).first()
|
|
||||||
c2 = Customer.objects.filter(user__id=u2.id).first()
|
|
||||||
if c1 and c2:
|
|
||||||
for r in c2.refillings.all():
|
|
||||||
c1.refillings.add(r)
|
|
||||||
c1.save()
|
|
||||||
for s in c2.buyings.all():
|
|
||||||
c1.buyings.add(s)
|
|
||||||
c1.save()
|
|
||||||
elif c2 and not c1:
|
|
||||||
c2.user = u1
|
|
||||||
c1 = c2
|
|
||||||
c1.save()
|
|
||||||
c1.recompute_amount()
|
|
||||||
u2.delete()
|
|
||||||
return u1
|
return u1
|
||||||
|
|
||||||
|
|
||||||
@ -128,9 +170,8 @@ class MergeUsersView(FormView):
|
|||||||
form_class = MergeForm
|
form_class = MergeForm
|
||||||
|
|
||||||
def dispatch(self, request, *arg, **kwargs):
|
def dispatch(self, request, *arg, **kwargs):
|
||||||
res = super(MergeUsersView, self).dispatch(request, *arg, **kwargs)
|
|
||||||
if request.user.is_root:
|
if request.user.is_root:
|
||||||
return res
|
return super().dispatch(request, *arg, **kwargs)
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
@ -140,7 +181,7 @@ class MergeUsersView(FormView):
|
|||||||
return super(MergeUsersView, self).form_valid(form)
|
return super(MergeUsersView, self).form_valid(form)
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return reverse("core:user_profile", kwargs={"user_id": self.final_user.id})
|
return self.final_user.get_absolute_url()
|
||||||
|
|
||||||
|
|
||||||
class DeleteAllForumUserMessagesView(FormView):
|
class DeleteAllForumUserMessagesView(FormView):
|
||||||
|
36
sas/urls.py
36
sas/urls.py
@ -22,40 +22,36 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.urls import re_path
|
from django.urls import path
|
||||||
|
|
||||||
from sas.views import *
|
from sas.views import *
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
re_path(r"^$", SASMainView.as_view(), name="main"),
|
path("", SASMainView.as_view(), name="main"),
|
||||||
re_path(r"^moderation$", ModerationView.as_view(), name="moderation"),
|
path("moderation/", ModerationView.as_view(), name="moderation"),
|
||||||
re_path(r"^album/(?P<album_id>[0-9]+)$", AlbumView.as_view(), name="album"),
|
path("album/<int:album_id>/", AlbumView.as_view(), name="album"),
|
||||||
re_path(
|
path(
|
||||||
r"^album/(?P<album_id>[0-9]+)/upload$",
|
"album/<int:album_id>/upload/",
|
||||||
AlbumUploadView.as_view(),
|
AlbumUploadView.as_view(),
|
||||||
name="album_upload",
|
name="album_upload",
|
||||||
),
|
),
|
||||||
re_path(
|
path("album/<int:album_id>/edit/", AlbumEditView.as_view(), name="album_edit"),
|
||||||
r"^album/(?P<album_id>[0-9]+)/edit$", AlbumEditView.as_view(), name="album_edit"
|
path("album/<int:album_id>/preview/", send_album, name="album_preview"),
|
||||||
),
|
path("picture/<int:picture_id>/", PictureView.as_view(), name="picture"),
|
||||||
re_path(r"^album/(?P<album_id>[0-9]+)/preview$", send_album, name="album_preview"),
|
path(
|
||||||
re_path(r"^picture/(?P<picture_id>[0-9]+)$", PictureView.as_view(), name="picture"),
|
"picture/<int:picture_id>/edit/",
|
||||||
re_path(
|
|
||||||
r"^picture/(?P<picture_id>[0-9]+)/edit$",
|
|
||||||
PictureEditView.as_view(),
|
PictureEditView.as_view(),
|
||||||
name="picture_edit",
|
name="picture_edit",
|
||||||
),
|
),
|
||||||
re_path(r"^picture/(?P<picture_id>[0-9]+)/download$", send_pict, name="download"),
|
path("picture/<int:picture_id>/download/", send_pict, name="download"),
|
||||||
re_path(
|
path(
|
||||||
r"^picture/(?P<picture_id>[0-9]+)/download/compressed$",
|
"picture/<int:picture_id>/download/compressed/",
|
||||||
send_compressed,
|
send_compressed,
|
||||||
name="download_compressed",
|
name="download_compressed",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^picture/(?P<picture_id>[0-9]+)/download/thumb$",
|
"picture/<int:picture_id>/download/thumb/",
|
||||||
send_thumb,
|
send_thumb,
|
||||||
name="download_thumb",
|
name="download_thumb",
|
||||||
),
|
),
|
||||||
# re_path(r'^album/new$', AlbumCreateView.as_view(), name='album_new'),
|
|
||||||
# re_path(r'^(?P<club_id>[0-9]+)/$', ClubView.as_view(), name='club_view'),
|
|
||||||
]
|
]
|
||||||
|
@ -291,6 +291,10 @@ SITH_URL = "my.url.git.an"
|
|||||||
SITH_NAME = "Sith website"
|
SITH_NAME = "Sith website"
|
||||||
SITH_TWITTER = "@ae_utbm"
|
SITH_TWITTER = "@ae_utbm"
|
||||||
|
|
||||||
|
# Enable experimental features
|
||||||
|
# Enable/Disable the galaxy button on user profile (urls stay activated)
|
||||||
|
SITH_ENABLE_GALAXY = False
|
||||||
|
|
||||||
# AE configuration
|
# AE configuration
|
||||||
# TODO: keep only that first setting, with the ID, and do the same for the other clubs
|
# TODO: keep only that first setting, with the ID, and do the same for the other clubs
|
||||||
SITH_MAIN_CLUB_ID = 1
|
SITH_MAIN_CLUB_ID = 1
|
||||||
|
@ -23,67 +23,65 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.urls import include, re_path
|
from django.urls import path
|
||||||
|
|
||||||
from stock.views import *
|
from stock.views import *
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# Stock re_paths
|
# Stock re_paths
|
||||||
re_path(
|
path("new/counter/<int:counter_id>/", StockCreateView.as_view(), name="new"),
|
||||||
r"^new/counter/(?P<counter_id>[0-9]+)$", StockCreateView.as_view(), name="new"
|
path("edit/<int:stock_id>/", StockEditView.as_view(), name="edit"),
|
||||||
),
|
path("list/", StockListView.as_view(), name="list"),
|
||||||
re_path(r"^edit/(?P<stock_id>[0-9]+)$", StockEditView.as_view(), name="edit"),
|
|
||||||
re_path(r"^list$", StockListView.as_view(), name="list"),
|
|
||||||
# StockItem re_paths
|
# StockItem re_paths
|
||||||
re_path(r"^(?P<stock_id>[0-9]+)$", StockItemList.as_view(), name="items_list"),
|
path("<int:stock_id>/", StockItemList.as_view(), name="items_list"),
|
||||||
re_path(
|
path(
|
||||||
r"^(?P<stock_id>[0-9]+)/stock_item/new_item$",
|
"<int:stock_id>/stock_item/new_item/",
|
||||||
StockItemCreateView.as_view(),
|
StockItemCreateView.as_view(),
|
||||||
name="new_item",
|
name="new_item",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^stock_item/(?P<item_id>[0-9]+)/edit$",
|
"stock_item/<int:item_id>/edit/",
|
||||||
StockItemEditView.as_view(),
|
StockItemEditView.as_view(),
|
||||||
name="edit_item",
|
name="edit_item",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^(?P<stock_id>[0-9]+)/stock_item/take_items$",
|
"<int:stock_id>/stock_item/take_items/",
|
||||||
StockTakeItemsBaseFormView.as_view(),
|
StockTakeItemsBaseFormView.as_view(),
|
||||||
name="take_items",
|
name="take_items",
|
||||||
),
|
),
|
||||||
# ShoppingList re_paths
|
# ShoppingList re_paths
|
||||||
re_path(
|
path(
|
||||||
r"^(?P<stock_id>[0-9]+)/shopping_list/list$",
|
"<int:stock_id>/shopping_list/list/",
|
||||||
StockShoppingListView.as_view(),
|
StockShoppingListView.as_view(),
|
||||||
name="shoppinglist_list",
|
name="shoppinglist_list",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^(?P<stock_id>[0-9]+)/shopping_list/create$",
|
"<int:stock_id>/shopping_list/create/",
|
||||||
StockItemQuantityBaseFormView.as_view(),
|
StockItemQuantityBaseFormView.as_view(),
|
||||||
name="shoppinglist_create",
|
name="shoppinglist_create",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^(?P<stock_id>[0-9]+)/shopping_list/(?P<shoppinglist_id>[0-9]+)/items$",
|
"<int:stock_id>/shopping_list/<int:shoppinglist_id>/items/",
|
||||||
StockShoppingListItemListView.as_view(),
|
StockShoppingListItemListView.as_view(),
|
||||||
name="shoppinglist_items",
|
name="shoppinglist_items",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^(?P<stock_id>[0-9]+)/shopping_list/(?P<shoppinglist_id>[0-9]+)/delete$",
|
"<int:stock_id>/shopping_list/<int:shoppinglist_id>/delete/",
|
||||||
StockShoppingListDeleteView.as_view(),
|
StockShoppingListDeleteView.as_view(),
|
||||||
name="shoppinglist_delete",
|
name="shoppinglist_delete",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^(?P<stock_id>[0-9]+)/shopping_list/(?P<shoppinglist_id>[0-9]+)/set_done$",
|
"<int:stock_id>/shopping_list/<int:shoppinglist_id>/set_done/",
|
||||||
StockShopppingListSetDone.as_view(),
|
StockShopppingListSetDone.as_view(),
|
||||||
name="shoppinglist_set_done",
|
name="shoppinglist_set_done",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^(?P<stock_id>[0-9]+)/shopping_list/(?P<shoppinglist_id>[0-9]+)/set_todo$",
|
"<int:stock_id>/shopping_list/<int:shoppinglist_id>/set_todo/",
|
||||||
StockShopppingListSetTodo.as_view(),
|
StockShopppingListSetTodo.as_view(),
|
||||||
name="shoppinglist_set_todo",
|
name="shoppinglist_set_todo",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^(?P<stock_id>[0-9]+)/shopping_list/(?P<shoppinglist_id>[0-9]+)/update_stock$",
|
"<int:stock_id>/shopping_list/<int:shoppinglist_id>/update_stock/",
|
||||||
StockUpdateAfterShopppingBaseFormView.as_view(),
|
StockUpdateAfterShopppingBaseFormView.as_view(),
|
||||||
name="update_after_shopping",
|
name="update_after_shopping",
|
||||||
),
|
),
|
||||||
|
@ -24,7 +24,6 @@
|
|||||||
|
|
||||||
from datetime import date, timedelta
|
from datetime import date, timedelta
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils import timezone
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
@ -101,8 +100,8 @@ class Subscription(models.Model):
|
|||||||
super(Subscription, self).save()
|
super(Subscription, self).save()
|
||||||
from counter.models import Customer
|
from counter.models import Customer
|
||||||
|
|
||||||
if not Customer.objects.filter(user=self.member).exists():
|
_, created = Customer.get_or_create(self.member)
|
||||||
Customer.new_for_user(self.member).save()
|
if created:
|
||||||
form = PasswordResetForm({"email": self.member.email})
|
form = PasswordResetForm({"email": self.member.email})
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
form.save(
|
form.save(
|
||||||
@ -166,7 +165,4 @@ class Subscription(models.Model):
|
|||||||
return user.is_in_group(settings.SITH_MAIN_BOARD_GROUP) or user.is_root
|
return user.is_in_group(settings.SITH_MAIN_BOARD_GROUP) or user.is_root
|
||||||
|
|
||||||
def is_valid_now(self):
|
def is_valid_now(self):
|
||||||
return (
|
return self.subscription_start <= date.today() <= self.subscription_end
|
||||||
self.subscription_start <= date.today()
|
|
||||||
and date.today() <= self.subscription_end
|
|
||||||
)
|
|
||||||
|
@ -22,12 +22,12 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.urls import re_path
|
from django.urls import path
|
||||||
|
|
||||||
from subscription.views import *
|
from subscription.views import *
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# Subscription views
|
# Subscription views
|
||||||
re_path(r"^$", NewSubscription.as_view(), name="subscription"),
|
path("", NewSubscription.as_view(), name="subscription"),
|
||||||
re_path(r"stats", SubscriptionsStatsView.as_view(), name="stats"),
|
path("stats/", SubscriptionsStatsView.as_view(), name="stats"),
|
||||||
]
|
]
|
||||||
|
@ -23,67 +23,65 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.urls import re_path
|
from django.urls import path
|
||||||
|
|
||||||
from trombi.views import *
|
from trombi.views import *
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
re_path(r"^(?P<club_id>[0-9]+)/new$", TrombiCreateView.as_view(), name="create"),
|
path("<int:club_id>/new/", TrombiCreateView.as_view(), name="create"),
|
||||||
re_path(
|
path("<int:trombi_id>/export/", TrombiExportView.as_view(), name="export"),
|
||||||
r"^(?P<trombi_id>[0-9]+)/export$", TrombiExportView.as_view(), name="export"
|
path("<int:trombi_id>/edit/", TrombiEditView.as_view(), name="edit"),
|
||||||
),
|
path(
|
||||||
re_path(r"^(?P<trombi_id>[0-9]+)/edit$", TrombiEditView.as_view(), name="edit"),
|
"<int:trombi_id>/moderate_comments/",
|
||||||
re_path(
|
|
||||||
r"^(?P<trombi_id>[0-9]+)/moderate_comments$",
|
|
||||||
TrombiModerateCommentsView.as_view(),
|
TrombiModerateCommentsView.as_view(),
|
||||||
name="moderate_comments",
|
name="moderate_comments",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^(?P<comment_id>[0-9]+)/moderate$",
|
"<int:comment_id>/moderate/",
|
||||||
TrombiModerateCommentView.as_view(),
|
TrombiModerateCommentView.as_view(),
|
||||||
name="moderate_comment",
|
name="moderate_comment",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^user/(?P<user_id>[0-9]+)/delete$",
|
"user/<int:user_id>/delete/",
|
||||||
TrombiDeleteUserView.as_view(),
|
TrombiDeleteUserView.as_view(),
|
||||||
name="delete_user",
|
name="delete_user",
|
||||||
),
|
),
|
||||||
re_path(r"^(?P<trombi_id>[0-9]+)$", TrombiDetailView.as_view(), name="detail"),
|
path("<int:trombi_id>/", TrombiDetailView.as_view(), name="detail"),
|
||||||
re_path(
|
path(
|
||||||
r"^(?P<user_id>[0-9]+)/new_comment$",
|
"<int:user_id>/new_comment/",
|
||||||
TrombiCommentCreateView.as_view(),
|
TrombiCommentCreateView.as_view(),
|
||||||
name="new_comment",
|
name="new_comment",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^(?P<user_id>[0-9]+)/profile$",
|
"<int:user_id>/profile/",
|
||||||
UserTrombiProfileView.as_view(),
|
UserTrombiProfileView.as_view(),
|
||||||
name="user_profile",
|
name="user_profile",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^comment/(?P<comment_id>[0-9]+)/edit$",
|
"comment/<int:comment_id>/edit/",
|
||||||
TrombiCommentEditView.as_view(),
|
TrombiCommentEditView.as_view(),
|
||||||
name="edit_comment",
|
name="edit_comment",
|
||||||
),
|
),
|
||||||
re_path(r"^tools$", UserTrombiToolsView.as_view(), name="user_tools"),
|
path("tools/", UserTrombiToolsView.as_view(), name="user_tools"),
|
||||||
re_path(r"^profile$", UserTrombiEditProfileView.as_view(), name="profile"),
|
path("profile/", UserTrombiEditProfileView.as_view(), name="profile"),
|
||||||
re_path(r"^pictures$", UserTrombiEditPicturesView.as_view(), name="pictures"),
|
path("pictures/", UserTrombiEditPicturesView.as_view(), name="pictures"),
|
||||||
re_path(
|
path(
|
||||||
r"^reset_memberships$",
|
"reset_memberships/",
|
||||||
UserTrombiResetClubMembershipsView.as_view(),
|
UserTrombiResetClubMembershipsView.as_view(),
|
||||||
name="reset_memberships",
|
name="reset_memberships",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^membership/(?P<membership_id>[0-9]+)/edit$",
|
"membership/<int:membership_id>/edit/",
|
||||||
UserTrombiEditMembershipView.as_view(),
|
UserTrombiEditMembershipView.as_view(),
|
||||||
name="edit_membership",
|
name="edit_membership",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^membership/(?P<membership_id>[0-9]+)/delete$",
|
"membership/<int:membership_id>/delete/",
|
||||||
UserTrombiDeleteMembershipView.as_view(),
|
UserTrombiDeleteMembershipView.as_view(),
|
||||||
name="delete_membership",
|
name="delete_membership",
|
||||||
),
|
),
|
||||||
re_path(
|
path(
|
||||||
r"^membership/(?P<user_id>[0-9]+)/create$",
|
"membership/<int:user_id>/create/",
|
||||||
UserTrombiAddMembershipView.as_view(),
|
UserTrombiAddMembershipView.as_view(),
|
||||||
name="create_membership",
|
name="create_membership",
|
||||||
),
|
),
|
||||||
|
Loading…
Reference in New Issue
Block a user