diff --git a/CHANGELOG b/CHANGELOG index ec8f6077..138789e8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +- On peut supprimer des comptes et des articles K-Fêt - Passage à Django2 - Dev : on peut désactiver la barre de debug avec une variable shell - Remplace les CSS de Google par des polices de proximité diff --git a/kfet/__init__.py b/kfet/__init__.py index 42ea33b1..47a6b0b8 100644 --- a/kfet/__init__.py +++ b/kfet/__init__.py @@ -1 +1,3 @@ default_app_config = "kfet.apps.KFetConfig" +KFET_DELETED_TRIGRAMME = "☠☠☠" +KFET_DELETED_USERNAME = "kfet_deleted_user" diff --git a/kfet/migrations/0066_on_delete_actions.py b/kfet/migrations/0066_on_delete_actions.py new file mode 100644 index 00000000..e2d635e2 --- /dev/null +++ b/kfet/migrations/0066_on_delete_actions.py @@ -0,0 +1,97 @@ +# Generated by Django 2.2 on 2019-05-23 13:20 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [("kfet", "0065_choices_promo")] + + operations = [ + migrations.RemoveField(model_name="checkouttransfer", name="from_checkout"), + migrations.RemoveField(model_name="checkouttransfer", name="to_checkout"), + migrations.AlterField( + model_name="accountnegative", + name="account", + field=models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + related_name="negative", + to="kfet.Account", + ), + ), + migrations.AlterField( + model_name="checkoutstatement", + name="checkout", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="statements", + to="kfet.Checkout", + ), + ), + migrations.AlterField( + model_name="inventoryarticle", + name="article", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="kfet.Article" + ), + ), + migrations.AlterField( + model_name="inventoryarticle", + name="inventory", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="kfet.Inventory" + ), + ), + migrations.AlterField( + model_name="operation", + name="article", + field=models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="operations", + to="kfet.Article", + ), + ), + migrations.AlterField( + model_name="order", + name="supplier", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="orders", + to="kfet.Supplier", + ), + ), + migrations.AlterField( + model_name="orderarticle", + name="article", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="kfet.Article" + ), + ), + migrations.AlterField( + model_name="orderarticle", + name="order", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="kfet.Order" + ), + ), + migrations.AlterField( + model_name="supplierarticle", + name="article", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="kfet.Article" + ), + ), + migrations.AlterField( + model_name="supplierarticle", + name="supplier", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="kfet.Supplier" + ), + ), + migrations.DeleteModel(name="ArticleRule"), + migrations.DeleteModel(name="CheckoutTransfer"), + ] diff --git a/kfet/migrations/0067_deleted_account.py b/kfet/migrations/0067_deleted_account.py new file mode 100644 index 00000000..155034f9 --- /dev/null +++ b/kfet/migrations/0067_deleted_account.py @@ -0,0 +1,32 @@ +# Generated by Django 2.2 on 2019-05-23 13:54 + +from django.db import migrations, models + +from kfet import KFET_DELETED_TRIGRAMME, KFET_DELETED_USERNAME + + +def setup_kfet_deleted_user(apps, schema_editor): + """ + Setup models instances for the kfet deleted account. + + Username and trigramme are retrieved from kfet.__init__ module. + Other data are registered here. + """ + User = apps.get_model("auth", "User") + CofProfile = apps.get_model("gestioncof", "CofProfile") + Account = apps.get_model("kfet", "Account") + + user, _ = User.objects.update_or_create( + username=KFET_DELETED_USERNAME, defaults={"first_name": "Compte K-Fêt supprimé"} + ) + profile, _ = CofProfile.objects.update_or_create(user=user) + account, _ = Account.objects.update_or_create( + cofprofile=profile, defaults={"trigramme": KFET_DELETED_TRIGRAMME} + ) + + +class Migration(migrations.Migration): + + dependencies = [("kfet", "0066_on_delete_actions")] + + operations = [migrations.RunPython(setup_kfet_deleted_user)] diff --git a/kfet/migrations/0068_on_delete_account.py b/kfet/migrations/0068_on_delete_account.py new file mode 100644 index 00000000..b8cfdb76 --- /dev/null +++ b/kfet/migrations/0068_on_delete_account.py @@ -0,0 +1,127 @@ +# Generated by Django 2.2 on 2019-05-23 16:17 + +from django.db import migrations, models + +import kfet.models + + +class Migration(migrations.Migration): + + dependencies = [("kfet", "0067_deleted_account")] + + operations = [ + migrations.AlterField( + model_name="checkout", + name="created_by", + field=models.ForeignKey( + on_delete=models.SET(kfet.models.get_deleted_account), + related_name="+", + to="kfet.Account", + ), + ), + migrations.AlterField( + model_name="checkoutstatement", + name="by", + field=models.ForeignKey( + on_delete=models.SET(kfet.models.get_deleted_account), + related_name="+", + to="kfet.Account", + ), + ), + migrations.AlterField( + model_name="inventory", + name="by", + field=models.ForeignKey( + on_delete=models.SET(kfet.models.get_deleted_account), + related_name="+", + to="kfet.Account", + ), + ), + migrations.AlterField( + model_name="operation", + name="addcost_for", + field=models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=models.SET(kfet.models.get_deleted_account), + related_name="addcosts", + to="kfet.Account", + ), + ), + migrations.AlterField( + model_name="operation", + name="canceled_by", + field=models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=models.SET(kfet.models.get_deleted_account), + related_name="+", + to="kfet.Account", + ), + ), + migrations.AlterField( + model_name="operationgroup", + name="on_acc", + field=models.ForeignKey( + on_delete=models.SET(kfet.models.get_deleted_account), + related_name="opesgroup", + to="kfet.Account", + ), + ), + migrations.AlterField( + model_name="operationgroup", + name="valid_by", + field=models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=models.SET(kfet.models.get_deleted_account), + related_name="+", + to="kfet.Account", + ), + ), + migrations.AlterField( + model_name="transfer", + name="canceled_by", + field=models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=models.SET(kfet.models.get_deleted_account), + related_name="+", + to="kfet.Account", + ), + ), + migrations.AlterField( + model_name="transfer", + name="from_acc", + field=models.ForeignKey( + on_delete=models.SET(kfet.models.get_deleted_account), + related_name="transfers_from", + to="kfet.Account", + ), + ), + migrations.AlterField( + model_name="transfer", + name="to_acc", + field=models.ForeignKey( + on_delete=models.SET(kfet.models.get_deleted_account), + related_name="transfers_to", + to="kfet.Account", + ), + ), + migrations.AlterField( + model_name="transfergroup", + name="valid_by", + field=models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=models.SET(kfet.models.get_deleted_account), + related_name="+", + to="kfet.Account", + ), + ), + ] diff --git a/kfet/models.py b/kfet/models.py index 5a8ea858..5d8ad3cb 100644 --- a/kfet/models.py +++ b/kfet/models.py @@ -12,6 +12,7 @@ from django.utils.translation import ugettext_lazy as _ from gestioncof.models import CofProfile +from . import KFET_DELETED_TRIGRAMME from .auth import KFET_GENERIC_TRIGRAMME from .auth.models import GenericTeamToken # noqa from .config import kfet_config @@ -151,7 +152,7 @@ class Account(models.Model): @property def readable(self): - return self.trigramme != "GNR" + return self.trigramme not in [KFET_DELETED_TRIGRAMME, KFET_GENERIC_TRIGRAMME] @property def is_team(self): @@ -267,12 +268,6 @@ class Account(models.Model): self.password = hash_password(clear_password) - # Surcharge de delete - # Pas de suppression possible - # Cas à régler plus tard - def delete(self, *args, **kwargs): - pass - def update_negative(self): if self.real_balance < 0: if hasattr(self, "negative") and not self.negative.start: @@ -299,6 +294,10 @@ class Account(models.Model): self.trigramme = trigramme +def get_deleted_account(): + return Account.objects.get(trigramme=KFET_DELETED_TRIGRAMME) + + class AccountNegativeManager(models.Manager): """Manager for AccountNegative model.""" @@ -310,7 +309,7 @@ class AccountNegative(models.Model): objects = AccountNegativeManager() account = models.OneToOneField( - Account, on_delete=models.PROTECT, related_name="negative" + Account, on_delete=models.CASCADE, related_name="negative" ) start = models.DateTimeField(blank=True, null=True, default=None) balance_offset = models.DecimalField( @@ -350,7 +349,9 @@ class CheckoutQuerySet(models.QuerySet): class Checkout(models.Model): - created_by = models.ForeignKey(Account, on_delete=models.PROTECT, related_name="+") + created_by = models.ForeignKey( + Account, on_delete=models.SET(get_deleted_account), related_name="+" + ) name = models.CharField(max_length=45) valid_from = models.DateTimeField() valid_to = models.DateTimeField() @@ -384,20 +385,12 @@ class Checkout(models.Model): return ret -class CheckoutTransfer(models.Model): - from_checkout = models.ForeignKey( - Checkout, on_delete=models.PROTECT, related_name="transfers_from" - ) - to_checkout = models.ForeignKey( - Checkout, on_delete=models.PROTECT, related_name="transfers_to" - ) - amount = models.DecimalField(max_digits=6, decimal_places=2) - - class CheckoutStatement(models.Model): - by = models.ForeignKey(Account, on_delete=models.PROTECT, related_name="+") + by = models.ForeignKey( + Account, on_delete=models.SET(get_deleted_account), related_name="+" + ) checkout = models.ForeignKey( - Checkout, on_delete=models.PROTECT, related_name="statements" + Checkout, on_delete=models.CASCADE, related_name="statements" ) balance_old = models.DecimalField( "ancienne balance", max_digits=6, decimal_places=2 @@ -526,21 +519,13 @@ class Article(models.Model): return to_ukf(self.price) -class ArticleRule(models.Model): - article_on = models.OneToOneField( - Article, on_delete=models.PROTECT, related_name="rule_on" - ) - article_to = models.OneToOneField( - Article, on_delete=models.PROTECT, related_name="rule_to" - ) - ratio = models.PositiveSmallIntegerField() - - class Inventory(models.Model): articles = models.ManyToManyField( Article, through="InventoryArticle", related_name="inventories" ) - by = models.ForeignKey(Account, on_delete=models.PROTECT, related_name="+") + by = models.ForeignKey( + Account, on_delete=models.SET(get_deleted_account), related_name="+" + ) at = models.DateTimeField(auto_now_add=True) # Optional order = models.OneToOneField( @@ -560,8 +545,8 @@ class Inventory(models.Model): class InventoryArticle(models.Model): - inventory = models.ForeignKey(Inventory, on_delete=models.PROTECT) - article = models.ForeignKey(Article, on_delete=models.PROTECT) + inventory = models.ForeignKey(Inventory, on_delete=models.CASCADE) + article = models.ForeignKey(Article, on_delete=models.CASCADE) stock_old = models.IntegerField() stock_new = models.IntegerField() stock_error = models.IntegerField(default=0) @@ -592,8 +577,8 @@ class Supplier(models.Model): class SupplierArticle(models.Model): - supplier = models.ForeignKey(Supplier, on_delete=models.PROTECT) - article = models.ForeignKey(Article, on_delete=models.PROTECT) + supplier = models.ForeignKey(Supplier, on_delete=models.CASCADE) + article = models.ForeignKey(Article, on_delete=models.CASCADE) at = models.DateTimeField(auto_now_add=True) price_HT = models.DecimalField( max_digits=7, decimal_places=4, blank=True, null=True, default=None @@ -608,7 +593,7 @@ class SupplierArticle(models.Model): class Order(models.Model): supplier = models.ForeignKey( - Supplier, on_delete=models.PROTECT, related_name="orders" + Supplier, on_delete=models.CASCADE, related_name="orders" ) articles = models.ManyToManyField( Article, through="OrderArticle", related_name="orders" @@ -621,8 +606,8 @@ class Order(models.Model): class OrderArticle(models.Model): - order = models.ForeignKey(Order, on_delete=models.PROTECT) - article = models.ForeignKey(Article, on_delete=models.PROTECT) + order = models.ForeignKey(Order, on_delete=models.CASCADE) + article = models.ForeignKey(Article, on_delete=models.CASCADE) quantity_ordered = models.IntegerField() quantity_received = models.IntegerField(default=0) @@ -633,7 +618,7 @@ class TransferGroup(models.Model): comment = models.CharField(max_length=255, blank=True, default="") valid_by = models.ForeignKey( Account, - on_delete=models.PROTECT, + on_delete=models.SET(get_deleted_account), related_name="+", blank=True, null=True, @@ -646,16 +631,18 @@ class Transfer(models.Model): TransferGroup, on_delete=models.PROTECT, related_name="transfers" ) from_acc = models.ForeignKey( - Account, on_delete=models.PROTECT, related_name="transfers_from" + Account, + on_delete=models.SET(get_deleted_account), + related_name="transfers_from", ) to_acc = models.ForeignKey( - Account, on_delete=models.PROTECT, related_name="transfers_to" + Account, on_delete=models.SET(get_deleted_account), related_name="transfers_to" ) amount = models.DecimalField(max_digits=6, decimal_places=2) # Optional canceled_by = models.ForeignKey( Account, - on_delete=models.PROTECT, + on_delete=models.SET(get_deleted_account), null=True, blank=True, default=None, @@ -669,7 +656,7 @@ class Transfer(models.Model): class OperationGroup(models.Model): on_acc = models.ForeignKey( - Account, on_delete=models.PROTECT, related_name="opesgroup" + Account, on_delete=models.SET(get_deleted_account), related_name="opesgroup" ) checkout = models.ForeignKey( Checkout, on_delete=models.PROTECT, related_name="opesgroup" @@ -681,7 +668,7 @@ class OperationGroup(models.Model): comment = models.CharField(max_length=255, blank=True, default="") valid_by = models.ForeignKey( Account, - on_delete=models.PROTECT, + on_delete=models.SET(get_deleted_account), related_name="+", blank=True, null=True, @@ -717,7 +704,7 @@ class Operation(models.Model): # Optional article = models.ForeignKey( Article, - on_delete=models.PROTECT, + on_delete=models.SET_NULL, related_name="operations", blank=True, null=True, @@ -726,7 +713,7 @@ class Operation(models.Model): article_nb = models.PositiveSmallIntegerField(blank=True, null=True, default=None) canceled_by = models.ForeignKey( Account, - on_delete=models.PROTECT, + on_delete=models.SET(get_deleted_account), related_name="+", blank=True, null=True, @@ -735,7 +722,7 @@ class Operation(models.Model): canceled_at = models.DateTimeField(blank=True, null=True, default=None) addcost_for = models.ForeignKey( Account, - on_delete=models.PROTECT, + on_delete=models.SET(get_deleted_account), related_name="addcosts", blank=True, null=True, diff --git a/kfet/static/kfet/css/base/fixed.css b/kfet/static/kfet/css/base/fixed.css index d198c50f..270331d8 100644 --- a/kfet/static/kfet/css/base/fixed.css +++ b/kfet/static/kfet/css/base/fixed.css @@ -57,12 +57,20 @@ aside .heading .sub { aside .buttons { margin-left: -15px; margin-right: -15px; + flex-wrap: wrap; } aside .buttons > * { flex: 0 1 auto !important; } +aside .buttons > hr { + flex-basis: 100%; + height: 0; + margin: 0; + border: 0; +} + /* Aside - Text */ diff --git a/kfet/static/kfet/css/libs/jconfirm-kfet.css b/kfet/static/kfet/css/libs/jconfirm-kfet.css index d2803434..a50e22d6 100644 --- a/kfet/static/kfet/css/libs/jconfirm-kfet.css +++ b/kfet/static/kfet/css/libs/jconfirm-kfet.css @@ -23,11 +23,19 @@ } .jconfirm .jconfirm-box .content-pane { - margin:0 !important; + border-bottom:1px solid #ddd; + margin: 0px !important; } .jconfirm .jconfirm-box .content { - border-bottom:1px solid #ddd; + padding: 5px; +} + +.jconfirm .jconfirm-box .content div.warning { + font-size: 16px; + font-weight: bold; + text-align: center; + margin: 5px 0px; } .jconfirm .jconfirm-box input { @@ -43,7 +51,7 @@ } .jconfirm .jconfirm-box .buttons { - margin-top:-5px; /* j'arrive pas à voir pk y'a un espace au dessus sinon... */ + margin-top:-6px; /* j'arrive pas à voir pk y'a un espace au dessus sinon... */ padding:0; height:40px; } diff --git a/kfet/static/kfet/js/history.js b/kfet/static/kfet/js/history.js index 8559f050..1c6495a7 100644 --- a/kfet/static/kfet/js/history.js +++ b/kfet/static/kfet/js/history.js @@ -1,28 +1,28 @@ -function KHistory(options={}) { +function KHistory(options = {}) { $.extend(this, KHistory.default_options, options); this.$container = $(this.container); - this.reset = function() { + this.reset = function () { this.$container.html(''); }; - this.addOpeGroup = function(opegroup) { + this.addOpeGroup = function (opegroup) { var $day = this._getOrCreateDay(opegroup['at']); var $opegroup = this._opeGroupHtml(opegroup); $day.after($opegroup); var trigramme = opegroup['on_acc_trigramme']; - var is_cof = opegroup['is_cof']; - for (var i=0; i Éditer + {% if perms.kfet.delete_account %} + +
+ {% csrf_token %} +
+ {% endif %}
-{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/kfet/templates/kfet/left_account.html b/kfet/templates/kfet/left_account.html index 18438ff1..716c96cc 100644 --- a/kfet/templates/kfet/left_account.html +++ b/kfet/templates/kfet/left_account.html @@ -18,6 +18,15 @@ Créditer + {% if perms.kfet.delete_account %} +
+ +
+ {% csrf_token %} +
+ {% endif %}
@@ -92,5 +101,24 @@ $( function() { $(this).addClass('focus'); }); + // Delete button + $('#button-delete').click(function() { + $.confirm({ + title: 'Confirmer la suppression', + content: ` +
+ Cette opération est irréversible ! +
+ Toutes les données associées à ce compte seront anonymisées. + `, + backgroundDismiss: true, + animation: 'top', + closeAnimation: 'bottom', + keyboardEnabled: true, + confirm: function() { + $('#account-delete-form').submit(); + } + }) + }) }); diff --git a/kfet/tests/test_views.py b/kfet/tests/test_views.py index 5c55f150..ec6565d7 100644 --- a/kfet/tests/test_views.py +++ b/kfet/tests/test_views.py @@ -8,6 +8,8 @@ from django.test import Client, TestCase from django.urls import reverse from django.utils import timezone +from .. import KFET_DELETED_TRIGRAMME +from ..auth import KFET_GENERIC_TRIGRAMME from ..config import kfet_config from ..models import ( Account, @@ -340,6 +342,61 @@ class AccountUpdateViewTests(ViewTestCaseMixin, TestCase): self.assertForbiddenKfet(r) +class AccountDeleteViewTests(ViewTestCaseMixin, TestCase): + url_name = "kfet.account.delete" + url_kwargs = {"trigramme": "001"} + url_expected = "/k-fet/accounts/001/delete" + + auth_user = "team1" + auth_forbidden = [None, "user", "team"] + http_methods = ["GET", "POST"] + with_liq = True + + def get_users_extra(self): + return { + "user1": create_user("user1", "001"), + "team1": create_team("team1", "101", perms=["kfet.delete_account"]), + "trez": create_user("trez", "#13"), + } + + def test_get_405(self): + r = self.client.get(self.url) + self.assertEqual(r.status_code, 405) + + def test_post_ok(self): + r = self.client.post(self.url, {}) + self.assertRedirects(r, reverse("kfet.account")) + + with self.assertRaises(Account.DoesNotExist): + self.accounts["user1"].refresh_from_db() + + def test_protected_accounts(self): + for trigramme in ["LIQ", "#13", KFET_GENERIC_TRIGRAMME, KFET_DELETED_TRIGRAMME]: + if Account.objects.get(trigramme=trigramme).readable: + expected_code = 200 + else: + expected_code = 403 + r = self.client.post( + reverse(self.url_name, kwargs={"trigramme": trigramme}), {} + ) + self.assertRedirects( + r, + reverse("kfet.account.read", kwargs={"trigramme": trigramme}), + target_status_code=expected_code, + ) + # Devrait être redondant avec le précédent, mais on sait jamais + self.assertTrue(Account.objects.filter(trigramme=trigramme).exists()) + + def test_nonempty_accounts(self): + self.accounts["user1"].balance = 1 + self.accounts["user1"].save() + + r = self.client.post(self.url, {}) + self.assertRedirects(r, reverse("kfet.account.read", kwargs=self.url_kwargs)) + # Shouldn't throw an error + self.accounts["user1"].refresh_from_db() + + class AccountGroupListViewTests(ViewTestCaseMixin, TestCase): url_name = "kfet.account.group" url_expected = "/k-fet/accounts/groups" @@ -1288,6 +1345,42 @@ class ArticleUpdateViewTests(ViewTestCaseMixin, TestCase): self.assertForbiddenKfet(r) +class ArticleDeleteViewTests(ViewTestCaseMixin, TestCase): + url_name = "kfet.article.delete" + + auth_user = "team1" + auth_forbidden = [None, "user", "team"] + + @property + def url_kwargs(self): + return {"pk": self.article.pk} + + @property + def url_expected(self): + return "/k-fet/articles/{}/delete".format(self.article.pk) + + def get_users_extra(self): + return {"team1": create_team("team1", "101", perms=["kfet.delete_article"])} + + def setUp(self): + super().setUp() + self.category = ArticleCategory.objects.create(name="Category") + self.article = Article.objects.create( + name="Article", category=self.category, stock=5, price=Decimal("2.5") + ) + + def test_get_redirects(self): + r = self.client.get(self.url) + self.assertRedirects(r, reverse("kfet.article.read", kwargs=self.url_kwargs)) + + def test_post_ok(self): + r = self.client.post(self.url, {}) + self.assertRedirects(r, reverse("kfet.article")) + + with self.assertRaises(Article.DoesNotExist): + self.article.refresh_from_db() + + class ArticleStatSalesListViewTests(ViewTestCaseMixin, TestCase): url_name = "kfet.article.stat.sales.list" diff --git a/kfet/urls.py b/kfet/urls.py index e3e3ad2d..681b7c31 100644 --- a/kfet/urls.py +++ b/kfet/urls.py @@ -57,6 +57,12 @@ urlpatterns = [ views.account_update, name="kfet.account.update", ), + # Account - Delete + path( + "accounts//delete", + views.AccountDelete.as_view(), + name="kfet.account.delete", + ), # Account - Groups path("accounts/groups", views.account_group, name="kfet.account.group"), path( @@ -180,6 +186,12 @@ urlpatterns = [ teamkfet_required(views.ArticleUpdate.as_view()), name="kfet.article.update", ), + # Article - Delete + path( + "articles//delete", + views.ArticleDelete.as_view(), + name="kfet.article.delete", + ), # Article - Statistics path( "articles//stat/sales/list", diff --git a/kfet/views.py b/kfet/views.py index 9ce17f47..ff71f1e0 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -7,6 +7,7 @@ from urllib.parse import urlencode from django.contrib import messages from django.contrib.auth.decorators import login_required, permission_required +from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.auth.models import Permission, User from django.contrib.messages.views import SuccessMessageMixin from django.core.exceptions import PermissionDenied @@ -20,10 +21,10 @@ from django.utils import timezone from django.utils.decorators import method_decorator from django.views.generic import DetailView, FormView, ListView, TemplateView from django.views.generic.detail import BaseDetailView -from django.views.generic.edit import CreateView, UpdateView +from django.views.generic.edit import CreateView, DeleteView, UpdateView from gestioncof.models import CofProfile -from kfet import consumers +from kfet import KFET_DELETED_TRIGRAMME, consumers from kfet.config import kfet_config from kfet.decorators import teamkfet_required from kfet.forms import ( @@ -78,6 +79,7 @@ from kfet.models import ( ) from kfet.statistic import ScaleMixin, WeekScale, last_stats_manifest +from .auth import KFET_GENERIC_TRIGRAMME from .auth.views import ( # noqa AccountGroupCreate, AccountGroupUpdate, @@ -467,6 +469,43 @@ def account_update(request, trigramme): }, ) + # Account - Delete + + +class AccountDelete(PermissionRequiredMixin, DeleteView): + model = Account + slug_field = "trigramme" + slug_url_kwarg = "trigramme" + success_url = reverse_lazy("kfet.account") + success_message = "Compte supprimé avec succès !" + permission_required = "kfet.delete_account" + + http_method_names = ["post"] + + def delete(self, request, *args, **kwargs): + self.object = self.get_object() + if self.object.balance >= 0.01: + messages.error( + request, + "Impossible de supprimer un compte " + "avec une balance strictement positive !", + ) + return redirect("kfet.account.read", self.object.trigramme) + + if self.object.trigramme in [ + "LIQ", + KFET_GENERIC_TRIGRAMME, + KFET_DELETED_TRIGRAMME, + "#13", + ]: + messages.error(request, "Impossible de supprimer un trigramme protégé !") + return redirect("kfet.account.read", self.object.trigramme) + + # SuccessMessageMixin does not work with DeleteView, see : + # https://code.djangoproject.com/ticket/21926 + messages.success(request, self.success_message) + return super().delete(request, *args, **kwargs) + class AccountNegativeList(ListView): queryset = AccountNegative.objects.select_related( @@ -837,6 +876,20 @@ class ArticleUpdate(SuccessMessageMixin, UpdateView): return super().form_valid(form) +class ArticleDelete(PermissionRequiredMixin, DeleteView): + model = Article + success_url = reverse_lazy("kfet.article") + success_message = "Article supprimé avec succès !" + permission_required = "kfet.delete_article" + + def get(self, request, *args, **kwargs): + return redirect("kfet.article.read", self.kwargs.get(self.pk_url_kwarg)) + + def delete(self, request, *args, **kwargs): + messages.success(request, self.success_message) + return super().delete(request, *args, **kwargs) + + # ----- # K-Psul # -----