From b091fee035d22c491c1ac1b3ddc3d311c1868e61 Mon Sep 17 00:00:00 2001 From: imperosol Date: Tue, 5 Nov 2024 19:40:59 +0100 Subject: [PATCH 1/2] custom queryset method to bulk update customer balance --- core/management/commands/populate_more.py | 24 +- counter/baker_recipes.py | 18 + counter/models.py | 52 ++- counter/tests/test_counter.py | 483 +------------------ counter/tests/test_customer.py | 535 ++++++++++++++++++++++ rootplace/views.py | 2 +- 6 files changed, 595 insertions(+), 519 deletions(-) create mode 100644 counter/baker_recipes.py create mode 100644 counter/tests/test_customer.py diff --git a/core/management/commands/populate_more.py b/core/management/commands/populate_more.py index 50a0052f..eaac58c0 100644 --- a/core/management/commands/populate_more.py +++ b/core/management/commands/populate_more.py @@ -1,14 +1,12 @@ import random from datetime import date, timedelta from datetime import timezone as tz -from decimal import Decimal from typing import Iterator from dateutil.relativedelta import relativedelta from django.conf import settings from django.core.management.base import BaseCommand -from django.db.models import Count, Exists, F, Min, OuterRef, Subquery, Sum -from django.db.models.functions import Coalesce +from django.db.models import Count, Exists, Min, OuterRef, Subquery from django.utils.timezone import localdate, make_aware, now from faker import Faker @@ -268,24 +266,6 @@ class Command(BaseCommand): Product.buying_groups.through.objects.bulk_create(buying_groups) Counter.products.through.objects.bulk_create(selling_places) - @staticmethod - def _update_balances(): - customers = Customer.objects.annotate( - money_in=Sum(F("refillings__amount"), default=0), - money_out=Coalesce( - Subquery( - Selling.objects.filter(customer=OuterRef("pk")) - .values("customer_id") # group by customer - .annotate(res=Sum(F("unit_price") * F("quantity"), default=0)) - .values("res") - ), - Decimal("0"), - ), - ).annotate(real_balance=F("money_in") - F("money_out")) - for c in customers: - c.amount = c.real_balance - Customer.objects.bulk_update(customers, fields=["amount"]) - def create_sales(self, sellers: list[User]): customers = list( Customer.objects.annotate( @@ -355,7 +335,7 @@ class Command(BaseCommand): sales.extend(this_customer_sales) Refilling.objects.bulk_create(reloads) Selling.objects.bulk_create(sales) - self._update_balances() + Customer.objects.update_amount() def create_permanences(self, sellers: list[User]): counters = list( diff --git a/counter/baker_recipes.py b/counter/baker_recipes.py new file mode 100644 index 00000000..aa77fb06 --- /dev/null +++ b/counter/baker_recipes.py @@ -0,0 +1,18 @@ +from model_bakery.recipe import Recipe, foreign_key + +from club.models import Club +from core.models import User +from counter.models import Counter, Product, Refilling, Selling + +counter_recipe = Recipe(Counter) +product_recipe = Recipe(Product, club=foreign_key(Recipe(Club))) +sale_recipe = Recipe( + Selling, + product=foreign_key(product_recipe), + counter=foreign_key(counter_recipe), + seller=foreign_key(Recipe(User)), + club=foreign_key(Recipe(Club)), +) +refill_recipe = Recipe( + Refilling, counter=foreign_key(counter_recipe), operator=foreign_key(Recipe(User)) +) diff --git a/counter/models.py b/counter/models.py index 1f1c6b51..1666572b 100644 --- a/counter/models.py +++ b/counter/models.py @@ -20,14 +20,15 @@ import random import string from datetime import date, datetime, timedelta from datetime import timezone as tz +from decimal import Decimal from typing import Self, Tuple from dict2xml import dict2xml from django.conf import settings from django.core.validators import MinLengthValidator from django.db import models -from django.db.models import Exists, F, OuterRef, Q, QuerySet, Sum, Value -from django.db.models.functions import Concat, Length +from django.db.models import Exists, F, OuterRef, Q, QuerySet, Subquery, Sum, Value +from django.db.models.functions import Coalesce, Concat, Length from django.forms import ValidationError from django.urls import reverse from django.utils import timezone @@ -45,6 +46,39 @@ from sith.settings import SITH_COUNTER_OFFICES, SITH_MAIN_CLUB from subscription.models import Subscription +class CustomerQuerySet(models.QuerySet): + def update_amount(self) -> int: + """Update the amount of all customers selected by this queryset. + + The result is given as the sum of all refills minus the sum of all purchases. + + Returns: + The number of updated rows. + + Warnings: + The execution time of this query grows really quickly. + When updating 500 customers, it may take around a second. + If you try to update all customers at once, the execution time + goes up to tens of seconds. + Use this either on a small subset of the `Customer` table, + or execute it inside an independent task + (like a Celery task or a management command). + """ + money_in = Subquery( + Refilling.objects.filter(customer=OuterRef("pk")) + .values("customer_id") # group by customer + .annotate(res=Sum(F("amount"), default=0)) + .values("res") + ) + money_out = Subquery( + Selling.objects.filter(customer=OuterRef("pk")) + .values("customer_id") + .annotate(res=Sum(F("unit_price") * F("quantity"), default=0)) + .values("res") + ) + return self.update(amount=Coalesce(money_in - money_out, Decimal("0"))) + + class Customer(models.Model): """Customer data of a User. @@ -57,6 +91,8 @@ class Customer(models.Model): amount = CurrencyField(_("amount"), default=0) recorded_products = models.IntegerField(_("recorded product"), default=0) + objects = CustomerQuerySet.as_manager() + class Meta: verbose_name = _("customer") verbose_name_plural = _("customers") @@ -141,18 +177,6 @@ class Customer(models.Model): account = cls.objects.create(user=user, account_id=account_id) return account, True - def recompute_amount(self): - refillings = self.refillings.aggregate(sum=Sum(F("amount")))["sum"] - self.amount = refillings if refillings is not None else 0 - purchases = ( - self.buyings.filter(payment_method="SITH_ACCOUNT") - .annotate(amount=F("quantity") * F("unit_price")) - .aggregate(sum=Sum(F("amount"))) - )["sum"] - if purchases is not None: - self.amount -= purchases - self.save() - def get_full_url(self): return f"https://{settings.SITH_URL}{self.get_absolute_url()}" diff --git a/counter/tests/test_counter.py b/counter/tests/test_counter.py index f37a25fb..cdc10835 100644 --- a/counter/tests/test_counter.py +++ b/counter/tests/test_counter.py @@ -12,15 +12,13 @@ # OR WITHIN THE LOCAL FILE "LICENSE" # # -import json import re -import string from datetime import timedelta import pytest from django.conf import settings from django.core.cache import cache -from django.test import Client, TestCase +from django.test import TestCase from django.urls import reverse from django.utils import timezone from django.utils.timezone import now @@ -31,7 +29,6 @@ from club.models import Club, Membership from core.baker_recipes import subscriber_user from core.models import User from counter.models import ( - BillingInfo, Counter, Customer, Permanency, @@ -313,149 +310,6 @@ class TestCounterStats(TestCase): ] -@pytest.mark.django_db -class TestBillingInfo: - @pytest.fixture - def payload(self): - return { - "first_name": "Subscribed", - "last_name": "User", - "address_1": "3, rue de Troyes", - "zip_code": "34301", - "city": "Sète", - "country": "FR", - "phone_number": "0612345678", - } - - def test_edit_infos(self, client: Client, payload: dict): - user = subscriber_user.make() - baker.make(BillingInfo, customer=user.customer) - client.force_login(user) - response = client.put( - reverse("api:put_billing_info", args=[user.id]), - json.dumps(payload), - content_type="application/json", - ) - user.refresh_from_db() - infos = BillingInfo.objects.get(customer__user=user) - assert response.status_code == 200 - assert hasattr(user.customer, "billing_infos") - assert infos.customer == user.customer - for key, val in payload.items(): - assert getattr(infos, key) == val - - @pytest.mark.parametrize( - "user_maker", [subscriber_user.make, lambda: baker.make(User)] - ) - @pytest.mark.django_db - def test_create_infos(self, client: Client, user_maker, payload): - user = user_maker() - client.force_login(user) - assert not BillingInfo.objects.filter(customer__user=user).exists() - response = client.put( - reverse("api:put_billing_info", args=[user.id]), - json.dumps(payload), - content_type="application/json", - ) - assert response.status_code == 200 - user.refresh_from_db() - assert hasattr(user, "customer") - infos = BillingInfo.objects.get(customer__user=user) - assert hasattr(user.customer, "billing_infos") - assert infos.customer == user.customer - for key, val in payload.items(): - assert getattr(infos, key) == val - - def test_invalid_data(self, client: Client, payload: dict[str, str]): - user = subscriber_user.make() - client.force_login(user) - # address_1, zip_code and country are missing - del payload["city"] - response = client.put( - reverse("api:put_billing_info", args=[user.id]), - json.dumps(payload), - content_type="application/json", - ) - assert response.status_code == 422 - user.customer.refresh_from_db() - assert not hasattr(user.customer, "billing_infos") - - @pytest.mark.parametrize( - ("operator_maker", "expected_code"), - [ - (subscriber_user.make, 403), - (lambda: baker.make(User), 403), - (lambda: baker.make(User, is_superuser=True), 200), - ], - ) - def test_edit_other_user( - self, client: Client, operator_maker, expected_code: int, payload: dict - ): - user = subscriber_user.make() - client.force_login(operator_maker()) - baker.make(BillingInfo, customer=user.customer) - response = client.put( - reverse("api:put_billing_info", args=[user.id]), - json.dumps(payload), - content_type="application/json", - ) - assert response.status_code == expected_code - - @pytest.mark.parametrize( - "phone_number", - ["+33612345678", "0612345678", "06 12 34 56 78", "06-12-34-56-78"], - ) - def test_phone_number_format( - self, client: Client, payload: dict, phone_number: str - ): - """Test that various formats of phone numbers are accepted.""" - user = subscriber_user.make() - client.force_login(user) - payload["phone_number"] = phone_number - response = client.put( - reverse("api:put_billing_info", args=[user.id]), - json.dumps(payload), - content_type="application/json", - ) - assert response.status_code == 200 - infos = BillingInfo.objects.get(customer__user=user) - assert infos.phone_number == "0612345678" - assert infos.phone_number.country_code == 33 - - def test_foreign_phone_number(self, client: Client, payload: dict): - """Test that a foreign phone number is accepted.""" - user = subscriber_user.make() - client.force_login(user) - payload["phone_number"] = "+49612345678" - response = client.put( - reverse("api:put_billing_info", args=[user.id]), - json.dumps(payload), - content_type="application/json", - ) - assert response.status_code == 200 - infos = BillingInfo.objects.get(customer__user=user) - assert infos.phone_number.as_national == "06123 45678" - assert infos.phone_number.country_code == 49 - - @pytest.mark.parametrize( - "phone_number", ["061234567a", "06 12 34 56", "061234567879", "azertyuiop"] - ) - def test_invalid_phone_number( - self, client: Client, payload: dict, phone_number: str - ): - """Test that invalid phone numbers are rejected.""" - user = subscriber_user.make() - client.force_login(user) - payload["phone_number"] = phone_number - response = client.put( - reverse("api:put_billing_info", args=[user.id]), - json.dumps(payload), - content_type="application/json", - ) - assert response.status_code == 422 - assert not BillingInfo.objects.filter(customer__user=user).exists() - - class TestBarmanConnection(TestCase): @classmethod def setUpTestData(cls): @@ -529,341 +383,6 @@ def test_barman_timeout(): assert bar.barmen_list == [] -class TestStudentCard(TestCase): - """Tests for adding and deleting Stundent Cards - Test that an user can be found with it's student card. - """ - - @classmethod - def setUpTestData(cls): - cls.krophil = User.objects.get(username="krophil") - cls.sli = User.objects.get(username="sli") - cls.skia = User.objects.get(username="skia") - cls.root = User.objects.get(username="root") - - cls.counter = Counter.objects.get(id=2) - - def setUp(self): - # Auto login on counter - self.client.post( - reverse("counter:login", args=[self.counter.id]), - {"username": "krophil", "password": "plop"}, - ) - - def test_search_user_with_student_card(self): - response = self.client.post( - reverse("counter:details", args=[self.counter.id]), - {"code": "9A89B82018B0A0"}, - ) - - assert response.url == reverse( - "counter:click", - kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, - ) - - def test_add_student_card_from_counter(self): - # Test card with mixed letters and numbers - response = self.client.post( - reverse( - "counter:click", - kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, - ), - {"student_card_uid": "8B90734A802A8F", "action": "add_student_card"}, - ) - self.assertContains(response, text="8B90734A802A8F") - - # Test card with only numbers - response = self.client.post( - reverse( - "counter:click", - kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, - ), - {"student_card_uid": "04786547890123", "action": "add_student_card"}, - ) - self.assertContains(response, text="04786547890123") - - # Test card with only letters - response = self.client.post( - reverse( - "counter:click", - kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, - ), - {"student_card_uid": "ABCAAAFAAFAAAB", "action": "add_student_card"}, - ) - self.assertContains(response, text="ABCAAAFAAFAAAB") - - def test_add_student_card_from_counter_fail(self): - # UID too short - response = self.client.post( - reverse( - "counter:click", - kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, - ), - {"student_card_uid": "8B90734A802A8", "action": "add_student_card"}, - ) - self.assertContains( - response, text="Ce n'est pas un UID de carte étudiante valide" - ) - - # UID too long - response = self.client.post( - reverse( - "counter:click", - kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, - ), - {"student_card_uid": "8B90734A802A8FA", "action": "add_student_card"}, - ) - self.assertContains( - response, text="Ce n'est pas un UID de carte étudiante valide" - ) - - # Test with already existing card - response = self.client.post( - reverse( - "counter:click", - kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, - ), - {"student_card_uid": "9A89B82018B0A0", "action": "add_student_card"}, - ) - self.assertContains( - response, text="Ce n'est pas un UID de carte étudiante valide" - ) - - # Test with lowercase - response = self.client.post( - reverse( - "counter:click", - kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, - ), - {"student_card_uid": "8b90734a802a9f", "action": "add_student_card"}, - ) - self.assertContains( - response, text="Ce n'est pas un UID de carte étudiante valide" - ) - - # Test with white spaces - response = self.client.post( - reverse( - "counter:click", - kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, - ), - {"student_card_uid": " ", "action": "add_student_card"}, - ) - self.assertContains( - response, text="Ce n'est pas un UID de carte étudiante valide" - ) - - def test_delete_student_card_with_owner(self): - self.client.force_login(self.sli) - self.client.post( - reverse( - "counter:delete_student_card", - kwargs={ - "customer_id": self.sli.customer.pk, - "card_id": self.sli.customer.student_cards.first().id, - }, - ) - ) - assert not self.sli.customer.student_cards.exists() - - def test_delete_student_card_with_board_member(self): - self.client.force_login(self.skia) - self.client.post( - reverse( - "counter:delete_student_card", - kwargs={ - "customer_id": self.sli.customer.pk, - "card_id": self.sli.customer.student_cards.first().id, - }, - ) - ) - assert not self.sli.customer.student_cards.exists() - - def test_delete_student_card_with_root(self): - self.client.force_login(self.root) - self.client.post( - reverse( - "counter:delete_student_card", - kwargs={ - "customer_id": self.sli.customer.pk, - "card_id": self.sli.customer.student_cards.first().id, - }, - ) - ) - assert not self.sli.customer.student_cards.exists() - - def test_delete_student_card_fail(self): - self.client.force_login(self.krophil) - response = self.client.post( - reverse( - "counter:delete_student_card", - kwargs={ - "customer_id": self.sli.customer.pk, - "card_id": self.sli.customer.student_cards.first().id, - }, - ) - ) - assert response.status_code == 403 - assert self.sli.customer.student_cards.exists() - - def test_add_student_card_from_user_preferences(self): - # Test with owner of the card - self.client.force_login(self.sli) - self.client.post( - reverse( - "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} - ), - {"uid": "8B90734A802A8F"}, - ) - - response = self.client.get( - reverse("core:user_prefs", kwargs={"user_id": self.sli.id}) - ) - self.assertContains(response, text="8B90734A802A8F") - - # Test with board member - self.client.force_login(self.skia) - self.client.post( - reverse( - "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} - ), - {"uid": "8B90734A802A8A"}, - ) - - response = self.client.get( - reverse("core:user_prefs", kwargs={"user_id": self.sli.id}) - ) - self.assertContains(response, text="8B90734A802A8A") - - # Test card with only numbers - self.client.post( - reverse( - "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} - ), - {"uid": "04786547890123"}, - ) - response = self.client.get( - reverse("core:user_prefs", kwargs={"user_id": self.sli.id}) - ) - self.assertContains(response, text="04786547890123") - - # Test card with only letters - self.client.post( - reverse( - "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} - ), - {"uid": "ABCAAAFAAFAAAB"}, - ) - response = self.client.get( - reverse("core:user_prefs", kwargs={"user_id": self.sli.id}) - ) - self.assertContains(response, text="ABCAAAFAAFAAAB") - - # Test with root - self.client.force_login(self.root) - self.client.post( - reverse( - "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} - ), - {"uid": "8B90734A802A8B"}, - ) - - response = self.client.get( - reverse("core:user_prefs", kwargs={"user_id": self.sli.id}) - ) - self.assertContains(response, text="8B90734A802A8B") - - def test_add_student_card_from_user_preferences_fail(self): - self.client.force_login(self.sli) - # UID too short - response = self.client.post( - reverse( - "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} - ), - {"uid": "8B90734A802A8"}, - ) - - self.assertContains(response, text="Cet UID est invalide") - - # UID too long - response = self.client.post( - reverse( - "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} - ), - {"uid": "8B90734A802A8FA"}, - ) - self.assertContains(response, text="Cet UID est invalide") - - # Test with already existing card - response = self.client.post( - reverse( - "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} - ), - {"uid": "9A89B82018B0A0"}, - ) - self.assertContains( - response, text="Un objet Student card avec ce champ Uid existe déjà." - ) - - # Test with lowercase - response = self.client.post( - reverse( - "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} - ), - {"uid": "8b90734a802a9f"}, - ) - self.assertContains(response, text="Cet UID est invalide") - - # Test with white spaces - response = self.client.post( - reverse( - "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} - ), - {"uid": " " * 14}, - ) - self.assertContains(response, text="Cet UID est invalide") - - # Test with unauthorized user - self.client.force_login(self.krophil) - response = self.client.post( - reverse( - "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} - ), - {"uid": "8B90734A802A8F"}, - ) - assert response.status_code == 403 - - -class TestCustomerAccountId(TestCase): - @classmethod - def setUpTestData(cls): - cls.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_c = User.objects.create(username="c", password="plop", email="c.c@c.fr") - Customer.objects.create(user=cls.user_a, amount=10, account_id="1111a") - Customer.objects.create(user=user_b, amount=0, account_id="9999z") - Customer.objects.create(user=user_c, amount=0, account_id="12345f") - - def test_create_customer(self): - user_d = User.objects.create(username="d", password="plop") - customer, created = Customer.get_or_create(user_d) - account_id = customer.account_id - number = account_id[:-1] - assert created is True - assert number == "12346" - assert len(account_id) == 6 - assert account_id[-1] in string.ascii_lowercase - assert customer.amount == 0 - - def test_get_existing_account(self): - account, created = Customer.get_or_create(self.user_a) - assert created is False - assert account.account_id == "1111a" - assert account.amount == 10 - - class TestClubCounterClickAccess(TestCase): @classmethod def setUpTestData(cls): diff --git a/counter/tests/test_customer.py b/counter/tests/test_customer.py new file mode 100644 index 00000000..2d7e1c60 --- /dev/null +++ b/counter/tests/test_customer.py @@ -0,0 +1,535 @@ +import json +import string + +import pytest +from django.test import Client, TestCase +from django.urls import reverse +from model_bakery import baker + +from core.baker_recipes import subscriber_user +from core.models import User +from counter.baker_recipes import refill_recipe, sale_recipe +from counter.models import BillingInfo, Counter, Customer, Refilling, Selling + + +@pytest.mark.django_db +class TestBillingInfo: + @pytest.fixture + def payload(self): + return { + "first_name": "Subscribed", + "last_name": "User", + "address_1": "3, rue de Troyes", + "zip_code": "34301", + "city": "Sète", + "country": "FR", + "phone_number": "0612345678", + } + + def test_edit_infos(self, client: Client, payload: dict): + user = subscriber_user.make() + baker.make(BillingInfo, customer=user.customer) + client.force_login(user) + response = client.put( + reverse("api:put_billing_info", args=[user.id]), + json.dumps(payload), + content_type="application/json", + ) + user.refresh_from_db() + infos = BillingInfo.objects.get(customer__user=user) + assert response.status_code == 200 + assert hasattr(user.customer, "billing_infos") + assert infos.customer == user.customer + for key, val in payload.items(): + assert getattr(infos, key) == val + + @pytest.mark.parametrize( + "user_maker", [subscriber_user.make, lambda: baker.make(User)] + ) + @pytest.mark.django_db + def test_create_infos(self, client: Client, user_maker, payload): + user = user_maker() + client.force_login(user) + assert not BillingInfo.objects.filter(customer__user=user).exists() + response = client.put( + reverse("api:put_billing_info", args=[user.id]), + json.dumps(payload), + content_type="application/json", + ) + assert response.status_code == 200 + user.refresh_from_db() + assert hasattr(user, "customer") + infos = BillingInfo.objects.get(customer__user=user) + assert hasattr(user.customer, "billing_infos") + assert infos.customer == user.customer + for key, val in payload.items(): + assert getattr(infos, key) == val + + def test_invalid_data(self, client: Client, payload: dict[str, str]): + user = subscriber_user.make() + client.force_login(user) + # address_1, zip_code and country are missing + del payload["city"] + response = client.put( + reverse("api:put_billing_info", args=[user.id]), + json.dumps(payload), + content_type="application/json", + ) + assert response.status_code == 422 + user.customer.refresh_from_db() + assert not hasattr(user.customer, "billing_infos") + + @pytest.mark.parametrize( + ("operator_maker", "expected_code"), + [ + (subscriber_user.make, 403), + (lambda: baker.make(User), 403), + (lambda: baker.make(User, is_superuser=True), 200), + ], + ) + def test_edit_other_user( + self, client: Client, operator_maker, expected_code: int, payload: dict + ): + user = subscriber_user.make() + client.force_login(operator_maker()) + baker.make(BillingInfo, customer=user.customer) + response = client.put( + reverse("api:put_billing_info", args=[user.id]), + json.dumps(payload), + content_type="application/json", + ) + assert response.status_code == expected_code + + @pytest.mark.parametrize( + "phone_number", + ["+33612345678", "0612345678", "06 12 34 56 78", "06-12-34-56-78"], + ) + def test_phone_number_format( + self, client: Client, payload: dict, phone_number: str + ): + """Test that various formats of phone numbers are accepted.""" + user = subscriber_user.make() + client.force_login(user) + payload["phone_number"] = phone_number + response = client.put( + reverse("api:put_billing_info", args=[user.id]), + json.dumps(payload), + content_type="application/json", + ) + assert response.status_code == 200 + infos = BillingInfo.objects.get(customer__user=user) + assert infos.phone_number == "0612345678" + assert infos.phone_number.country_code == 33 + + def test_foreign_phone_number(self, client: Client, payload: dict): + """Test that a foreign phone number is accepted.""" + user = subscriber_user.make() + client.force_login(user) + payload["phone_number"] = "+49612345678" + response = client.put( + reverse("api:put_billing_info", args=[user.id]), + json.dumps(payload), + content_type="application/json", + ) + assert response.status_code == 200 + infos = BillingInfo.objects.get(customer__user=user) + assert infos.phone_number.as_national == "06123 45678" + assert infos.phone_number.country_code == 49 + + @pytest.mark.parametrize( + "phone_number", ["061234567a", "06 12 34 56", "061234567879", "azertyuiop"] + ) + def test_invalid_phone_number( + self, client: Client, payload: dict, phone_number: str + ): + """Test that invalid phone numbers are rejected.""" + user = subscriber_user.make() + client.force_login(user) + payload["phone_number"] = phone_number + response = client.put( + reverse("api:put_billing_info", args=[user.id]), + json.dumps(payload), + content_type="application/json", + ) + assert response.status_code == 422 + assert not BillingInfo.objects.filter(customer__user=user).exists() + + +class TestStudentCard(TestCase): + """Tests for adding and deleting Stundent Cards + Test that an user can be found with it's student card. + """ + + @classmethod + def setUpTestData(cls): + cls.krophil = User.objects.get(username="krophil") + cls.sli = User.objects.get(username="sli") + cls.skia = User.objects.get(username="skia") + cls.root = User.objects.get(username="root") + + cls.counter = Counter.objects.get(id=2) + + def setUp(self): + # Auto login on counter + self.client.post( + reverse("counter:login", args=[self.counter.id]), + {"username": "krophil", "password": "plop"}, + ) + + def test_search_user_with_student_card(self): + response = self.client.post( + reverse("counter:details", args=[self.counter.id]), + {"code": "9A89B82018B0A0"}, + ) + + assert response.url == reverse( + "counter:click", + kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, + ) + + def test_add_student_card_from_counter(self): + # Test card with mixed letters and numbers + response = self.client.post( + reverse( + "counter:click", + kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, + ), + {"student_card_uid": "8B90734A802A8F", "action": "add_student_card"}, + ) + self.assertContains(response, text="8B90734A802A8F") + + # Test card with only numbers + response = self.client.post( + reverse( + "counter:click", + kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, + ), + {"student_card_uid": "04786547890123", "action": "add_student_card"}, + ) + self.assertContains(response, text="04786547890123") + + # Test card with only letters + response = self.client.post( + reverse( + "counter:click", + kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, + ), + {"student_card_uid": "ABCAAAFAAFAAAB", "action": "add_student_card"}, + ) + self.assertContains(response, text="ABCAAAFAAFAAAB") + + def test_add_student_card_from_counter_fail(self): + # UID too short + response = self.client.post( + reverse( + "counter:click", + kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, + ), + {"student_card_uid": "8B90734A802A8", "action": "add_student_card"}, + ) + self.assertContains( + response, text="Ce n'est pas un UID de carte étudiante valide" + ) + + # UID too long + response = self.client.post( + reverse( + "counter:click", + kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, + ), + {"student_card_uid": "8B90734A802A8FA", "action": "add_student_card"}, + ) + self.assertContains( + response, text="Ce n'est pas un UID de carte étudiante valide" + ) + + # Test with already existing card + response = self.client.post( + reverse( + "counter:click", + kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, + ), + {"student_card_uid": "9A89B82018B0A0", "action": "add_student_card"}, + ) + self.assertContains( + response, text="Ce n'est pas un UID de carte étudiante valide" + ) + + # Test with lowercase + response = self.client.post( + reverse( + "counter:click", + kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, + ), + {"student_card_uid": "8b90734a802a9f", "action": "add_student_card"}, + ) + self.assertContains( + response, text="Ce n'est pas un UID de carte étudiante valide" + ) + + # Test with white spaces + response = self.client.post( + reverse( + "counter:click", + kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, + ), + {"student_card_uid": " ", "action": "add_student_card"}, + ) + self.assertContains( + response, text="Ce n'est pas un UID de carte étudiante valide" + ) + + def test_delete_student_card_with_owner(self): + self.client.force_login(self.sli) + self.client.post( + reverse( + "counter:delete_student_card", + kwargs={ + "customer_id": self.sli.customer.pk, + "card_id": self.sli.customer.student_cards.first().id, + }, + ) + ) + assert not self.sli.customer.student_cards.exists() + + def test_delete_student_card_with_board_member(self): + self.client.force_login(self.skia) + self.client.post( + reverse( + "counter:delete_student_card", + kwargs={ + "customer_id": self.sli.customer.pk, + "card_id": self.sli.customer.student_cards.first().id, + }, + ) + ) + assert not self.sli.customer.student_cards.exists() + + def test_delete_student_card_with_root(self): + self.client.force_login(self.root) + self.client.post( + reverse( + "counter:delete_student_card", + kwargs={ + "customer_id": self.sli.customer.pk, + "card_id": self.sli.customer.student_cards.first().id, + }, + ) + ) + assert not self.sli.customer.student_cards.exists() + + def test_delete_student_card_fail(self): + self.client.force_login(self.krophil) + response = self.client.post( + reverse( + "counter:delete_student_card", + kwargs={ + "customer_id": self.sli.customer.pk, + "card_id": self.sli.customer.student_cards.first().id, + }, + ) + ) + assert response.status_code == 403 + assert self.sli.customer.student_cards.exists() + + def test_add_student_card_from_user_preferences(self): + # Test with owner of the card + self.client.force_login(self.sli) + self.client.post( + reverse( + "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} + ), + {"uid": "8B90734A802A8F"}, + ) + + response = self.client.get( + reverse("core:user_prefs", kwargs={"user_id": self.sli.id}) + ) + self.assertContains(response, text="8B90734A802A8F") + + # Test with board member + self.client.force_login(self.skia) + self.client.post( + reverse( + "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} + ), + {"uid": "8B90734A802A8A"}, + ) + + response = self.client.get( + reverse("core:user_prefs", kwargs={"user_id": self.sli.id}) + ) + self.assertContains(response, text="8B90734A802A8A") + + # Test card with only numbers + self.client.post( + reverse( + "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} + ), + {"uid": "04786547890123"}, + ) + response = self.client.get( + reverse("core:user_prefs", kwargs={"user_id": self.sli.id}) + ) + self.assertContains(response, text="04786547890123") + + # Test card with only letters + self.client.post( + reverse( + "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} + ), + {"uid": "ABCAAAFAAFAAAB"}, + ) + response = self.client.get( + reverse("core:user_prefs", kwargs={"user_id": self.sli.id}) + ) + self.assertContains(response, text="ABCAAAFAAFAAAB") + + # Test with root + self.client.force_login(self.root) + self.client.post( + reverse( + "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} + ), + {"uid": "8B90734A802A8B"}, + ) + + response = self.client.get( + reverse("core:user_prefs", kwargs={"user_id": self.sli.id}) + ) + self.assertContains(response, text="8B90734A802A8B") + + def test_add_student_card_from_user_preferences_fail(self): + self.client.force_login(self.sli) + # UID too short + response = self.client.post( + reverse( + "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} + ), + {"uid": "8B90734A802A8"}, + ) + + self.assertContains(response, text="Cet UID est invalide") + + # UID too long + response = self.client.post( + reverse( + "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} + ), + {"uid": "8B90734A802A8FA"}, + ) + self.assertContains(response, text="Cet UID est invalide") + + # Test with already existing card + response = self.client.post( + reverse( + "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} + ), + {"uid": "9A89B82018B0A0"}, + ) + self.assertContains( + response, text="Un objet Student card avec ce champ Uid existe déjà." + ) + + # Test with lowercase + response = self.client.post( + reverse( + "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} + ), + {"uid": "8b90734a802a9f"}, + ) + self.assertContains(response, text="Cet UID est invalide") + + # Test with white spaces + response = self.client.post( + reverse( + "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} + ), + {"uid": " " * 14}, + ) + self.assertContains(response, text="Cet UID est invalide") + + # Test with unauthorized user + self.client.force_login(self.krophil) + response = self.client.post( + reverse( + "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} + ), + {"uid": "8B90734A802A8F"}, + ) + assert response.status_code == 403 + + +class TestCustomerAccountId(TestCase): + @classmethod + def setUpTestData(cls): + cls.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_c = User.objects.create(username="c", password="plop", email="c.c@c.fr") + Customer.objects.create(user=cls.user_a, amount=10, account_id="1111a") + Customer.objects.create(user=user_b, amount=0, account_id="9999z") + Customer.objects.create(user=user_c, amount=0, account_id="12345f") + + def test_create_customer(self): + user_d = User.objects.create(username="d", password="plop") + customer, created = Customer.get_or_create(user_d) + account_id = customer.account_id + number = account_id[:-1] + assert created is True + assert number == "12346" + assert len(account_id) == 6 + assert account_id[-1] in string.ascii_lowercase + assert customer.amount == 0 + + def test_get_existing_account(self): + account, created = Customer.get_or_create(self.user_a) + assert created is False + assert account.account_id == "1111a" + assert account.amount == 10 + + +@pytest.mark.django_db +def test_update_balance(): + customers = baker.make(Customer, _quantity=5, _bulk_create=True) + refills = [ + *refill_recipe.prepare( + customer=iter(customers), + amount=iter([30, 30, 40, 50, 50]), + _quantity=len(customers), + _save_related=True, + ), + refill_recipe.prepare(customer=customers[0], amount=30, _save_related=True), + refill_recipe.prepare(customer=customers[4], amount=10, _save_related=True), + ] + Refilling.objects.bulk_create(refills) + sales = [ + *sale_recipe.prepare( + customer=iter(customers), + _quantity=len(customers), + unit_price=10, + quantity=1, + _save_related=True, + ), + *sale_recipe.prepare( + customer=iter(customers[:3]), + _quantity=3, + unit_price=5, + quantity=2, + _save_related=True, + ), + sale_recipe.prepare( + customer=customers[4], quantity=1, unit_price=50, _save_related=True + ), + ] + Selling.objects.bulk_create(sales) + # customer 0 = 40, customer 1 = 10€, customer 2 = 20€, + # customer 3 = 40€, customer 4 = 0€ + customers_qs = Customer.objects.filter(pk__in={c.pk for c in customers}) + # put everything at zero to be sure the amounts were wrong beforehand + customers_qs.update(amount=0) + customers_qs.update_amount() + for customer, amount in zip(customers, [40, 10, 20, 40, 0]): + customer.refresh_from_db() + assert customer.amount == amount diff --git a/rootplace/views.py b/rootplace/views.py index 4aefb8c3..60204634 100644 --- a/rootplace/views.py +++ b/rootplace/views.py @@ -123,7 +123,7 @@ def merge_users(u1: User, u2: User) -> User: 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() + Customer.objects.filter(pk=c_dest.pk).update_amount() if created: # swap the account numbers, so that the user keep # the id he is accustomed to From c2efc969d094c3e08f05767752cd75b733cc7423 Mon Sep 17 00:00:00 2001 From: imperosol Date: Fri, 8 Nov 2024 17:35:59 +0100 Subject: [PATCH 2/2] refactor populate.py --- .../sas/Family/{richard.jpg => rbatsbak.jpg} | Bin core/management/commands/populate.py | 1854 +++++++---------- counter/tests/test_counter.py | 5 +- 3 files changed, 729 insertions(+), 1130 deletions(-) rename core/fixtures/images/sas/Family/{richard.jpg => rbatsbak.jpg} (100%) diff --git a/core/fixtures/images/sas/Family/richard.jpg b/core/fixtures/images/sas/Family/rbatsbak.jpg similarity index 100% rename from core/fixtures/images/sas/Family/richard.jpg rename to core/fixtures/images/sas/Family/rbatsbak.jpg diff --git a/core/management/commands/populate.py b/core/management/commands/populate.py index 18a524b2..e1c8d780 100644 --- a/core/management/commands/populate.py +++ b/core/management/commands/populate.py @@ -20,11 +20,10 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - -import os -from datetime import date, datetime, timedelta -from io import BytesIO, StringIO +from datetime import date, timedelta +from io import StringIO from pathlib import Path +from typing import ClassVar from django.conf import settings from django.contrib.auth.models import Permission @@ -47,1256 +46,855 @@ from accounting.models import ( ) from club.models import Club, Membership from com.models import News, NewsDate, Sith, Weekmail -from core.models import Group, Page, PageRev, SithFile, User +from core.models import Group, Page, PageRev, RealGroup, SithFile, User from core.utils import resize_image -from counter.models import Counter, Customer, Product, ProductType, Selling, StudentCard +from counter.models import Counter, Product, ProductType, StudentCard from election.models import Candidature, Election, ElectionList, Role -from forum.models import Forum, ForumTopic +from forum.models import Forum from pedagogy.models import UV from sas.models import Album, PeoplePictureRelation, Picture from subscription.models import Subscription class Command(BaseCommand): + ROOT_PATH: ClassVar[Path] = Path(__file__).parent.parent.parent.parent + SAS_FIXTURE_PATH: ClassVar[Path] = ( + ROOT_PATH / "core" / "fixtures" / "images" / "sas" + ) + help = "Populate a new instance of the Sith AE" - def add_arguments(self, parser): - parser.add_argument("--prod", action="store_true") - def reset_index(self, *args): + if connection.vendor == "sqlite": + # sqlite doesn't support this operation + return sqlcmd = StringIO() call_command("sqlsequencereset", *args, stdout=sqlcmd) cursor = connection.cursor() cursor.execute(sqlcmd.getvalue()) def handle(self, *args, **options): - os.environ["DJANGO_COLORS"] = "nocolor" - Site(id=4000, domain=settings.SITH_URL, name=settings.SITH_NAME).save() - root_path = Path(__file__).parent.parent.parent.parent - root_group, _ = Group.objects.get_or_create(name="Root") - Group(name="Public").save() - Group(name="Subscribers").save() - Group(name="Old subscribers").save() - Group(name="Accounting admin").save() - Group(name="Communication admin").save() - Group(name="Counter admin").save() - Group(name="Banned from buying alcohol").save() - Group(name="Banned from counters").save() - Group(name="Banned to subscribe").save() - sas_admin, _ = Group.objects.get_or_create(name="SAS admin") - Group(name="Forum admin").save() - Group(name="Pedagogy admin").save() + if not settings.DEBUG and not settings.TESTING: + raise Exception("Never call this command in prod. Never.") + + Sith.objects.create(weekmail_destinations="etudiants@git.an personnel@git.an") + Site.objects.create(domain=settings.SITH_URL, name=settings.SITH_NAME) + + root_group = Group.objects.create(name="Root") + public_group = Group.objects.create(name="Public") + subscribers = Group.objects.create(name="Subscribers") + old_subscribers = Group.objects.create(name="Old subscribers") + Group.objects.create(name="Accounting admin") + Group.objects.create(name="Communication admin") + Group.objects.create(name="Counter admin") + Group.objects.create(name="Banned from buying alcohol") + Group.objects.create(name="Banned from counters") + Group.objects.create(name="Banned to subscribe") + Group.objects.create(name="SAS admin") + Group.objects.create(name="Forum admin") + Group.objects.create(name="Pedagogy admin") self.reset_index("core", "auth") change_billing = Permission.objects.get(codename="change_billinginfo") add_billing = Permission.objects.get(codename="add_billinginfo") root_group.permissions.add(change_billing, add_billing) - root = User( + root = User.objects.create_superuser( id=0, username="root", last_name="", first_name="Bibou", email="ae.info@utbm.fr", date_of_birth="1942-06-12", - is_superuser=True, - is_staff=True, + password="plop", ) - root.set_password("plop") - root.save() - profiles_root = SithFile( - parent=None, name="profiles", is_folder=True, owner=root - ) - profiles_root.save() - home_root = SithFile(parent=None, name="users", is_folder=True, owner=root) - home_root.save() + self.profiles_root = SithFile.objects.create(name="profiles", owner=root) + home_root = SithFile.objects.create(name="users", owner=root) # Page needed for club creation p = Page(name=settings.SITH_CLUB_ROOT_PAGE) - p.set_lock(root) - p.save() + p.save(force_lock=True) - club_root = SithFile(parent=None, name="clubs", is_folder=True, owner=root) - club_root.save() - sas = SithFile(parent=None, name="SAS", is_folder=True, owner=root) - sas.save() - main_club = Club( + club_root = SithFile.objects.create(name="clubs", owner=root) + sas = SithFile.objects.create(name="SAS", owner=root) + main_club = Club.objects.create( id=1, name=settings.SITH_MAIN_CLUB["name"], unix_name=settings.SITH_MAIN_CLUB["unix_name"], address=settings.SITH_MAIN_CLUB["address"], ) - main_club.save() - bar_club = Club( + bar_club = Club.objects.create( id=2, name=settings.SITH_BAR_MANAGER["name"], unix_name=settings.SITH_BAR_MANAGER["unix_name"], address=settings.SITH_BAR_MANAGER["address"], ) - bar_club.save() - launderette_club = Club( + Club.objects.create( id=84, name=settings.SITH_LAUNDERETTE_MANAGER["name"], unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"], address=settings.SITH_LAUNDERETTE_MANAGER["address"], ) - launderette_club.save() self.reset_index("club") - for b in settings.SITH_COUNTER_BARS: - g = Group(name=b[1] + " admin") - g.save() - c = Counter(id=b[0], name=b[1], club=bar_club, type="BAR") - c.save() - g.editable_counters.add(c) - g.save() + counters = [ + *[ + Counter(id=bar_id, name=bar_name, club=bar_club, type="BAR") + for bar_id, bar_name in settings.SITH_COUNTER_BARS + ], + Counter(name="Eboutic", club=main_club, type="EBOUTIC"), + Counter(name="AE", club=main_club, type="OFFICE"), + ] + Counter.objects.bulk_create(counters) + bar_groups = [] + for bar_id, bar_name in settings.SITH_COUNTER_BARS: + group = RealGroup.objects.create(name=f"{bar_name} admin") + bar_groups.append( + Counter.edit_groups.through(counter_id=bar_id, group=group) + ) + Counter.edit_groups.through.objects.bulk_create(bar_groups) self.reset_index("counter") - Counter(name="Eboutic", club=main_club, type="EBOUTIC").save() - Counter(name="AE", club=main_club, type="OFFICE").save() - ae_members = Group.objects.get(name=settings.SITH_MAIN_MEMBERS_GROUP) + subscribers.viewable_files.add(home_root, club_root) - home_root.view_groups.set([ae_members]) - club_root.view_groups.set([ae_members]) - home_root.save() - club_root.save() - - Sith(weekmail_destinations="etudiants@git.an personnel@git.an").save() Weekmail().save() - p = Page(name="Index") - p.set_lock(root) - p.save() - p.view_groups.set([settings.SITH_GROUP_PUBLIC_ID]) - p.set_lock(root) - p.save() - PageRev( - page=p, - title="Wiki index", - author=root, - content=""" -Welcome to the wiki page! -""", - ).save() - - p = Page(name="services") - p.set_lock(root) - p.save() - p.view_groups.set([settings.SITH_GROUP_PUBLIC_ID]) - p.set_lock(root) - PageRev( - page=p, - title="Services", - author=root, - content=""" -| | | | -| :---: | :---: | :---: | :---: | -| [Eboutic](/eboutic) | [Laverie](/launderette) | Matmat | [Fichiers](/file) | -| SAS | Weekmail | Forum | | - -""", - ).save() - - p = Page(name="launderette") - p.set_lock(root) - p.save() - p.set_lock(root) - PageRev( - page=p, title="Laverie", author=root, content="Fonctionnement de la laverie" - ).save() - # Here we add a lot of test datas, that are not necessary for the Sith, but that provide a basic development environment - if not options["prod"]: - self.now = timezone.now().replace(hour=12) + self.now = timezone.now().replace(hour=12) - # Adding user Skia - skia = User( - username="skia", - last_name="Kia", - first_name="S'", - email="skia@git.an", - date_of_birth="1942-06-12", - ) - skia.set_password("plop") - skia.save() - skia.view_groups = [ae_members.id] - skia.save() - skia_profile_path = ( - root_path - / "core" - / "fixtures" - / "images" - / "sas" - / "Family" - / "skia.jpg" - ) - with open(skia_profile_path, "rb") as f: - name = str(skia.id) + "_profile.jpg" - skia_profile = SithFile( - parent=profiles_root, - name=name, - file=resize_image(Image.open(BytesIO(f.read())), 400, "WEBP"), - owner=skia, - is_folder=False, - mime_type="image/webp", - size=skia_profile_path.stat().st_size, - ) - skia_profile.file.name = name - skia_profile.save() - skia.profile_pict = skia_profile - skia.save() + skia = User.objects.create_user( + username="skia", + last_name="Kia", + first_name="S'", + email="skia@git.an", + date_of_birth="1942-06-12", + password="plop", + ) + public = User.objects.create_user( + username="public", + last_name="Not subscribed", + first_name="Public", + email="public@git.an", + date_of_birth="1942-06-12", + password="plop", + ) + subscriber = User.objects.create_user( + username="subscriber", + last_name="User", + first_name="Subscribed", + email="Subscribed@git.an", + date_of_birth="1942-06-12", + password="plop", + ) + old_subscriber = User.objects.create_user( + username="old_subscriber", + last_name="Subscriber", + first_name="Old", + email="old_subscriber@git.an", + date_of_birth="1942-06-12", + password="plop", + ) + counter = User.objects.create_user( + username="counter", + last_name="Ter", + first_name="Coun", + email="counter@git.an", + date_of_birth="1942-06-12", + password="plop", + ) + comptable = User.objects.create_user( + username="comptable", + last_name="Able", + first_name="Compte", + email="compta@git.an", + date_of_birth="1942-06-12", + password="plop", + ) + User.objects.create_user( + username="guy", + last_name="Carlier", + first_name="Guy", + email="guy@git.an", + date_of_birth="1942-06-12", + password="plop", + ) + richard = User.objects.create_user( + username="rbatsbak", + last_name="Batsbak", + first_name="Richard", + email="richard@git.an", + date_of_birth="1982-06-12", + password="plop", + ) + sli = User.objects.create_user( + username="sli", + last_name="Li", + first_name="S", + email="sli@git.an", + date_of_birth="1942-06-12", + password="plop", + ) + krophil = User.objects.create_user( + username="krophil", + last_name="Phil'", + first_name="Kro", + email="krophil@git.an", + date_of_birth="1942-06-12", + password="plop", + ) + comunity = User.objects.create_user( + username="comunity", + last_name="Unity", + first_name="Com", + email="comunity@git.an", + date_of_birth="1942-06-12", + password="plop", + ) + tutu = User.objects.create_user( + username="tutu", + last_name="Tu", + first_name="Tu", + email="tutu@git.an", + date_of_birth="1942-06-12", + password="plop", + ) + User.groups.through.objects.bulk_create( + [ + User.groups.through( + realgroup_id=settings.SITH_GROUP_COUNTER_ADMIN_ID, user=counter + ), + User.groups.through( + realgroup_id=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID, user=comptable + ), + User.groups.through( + realgroup_id=settings.SITH_GROUP_COM_ADMIN_ID, user=comunity + ), + User.groups.through( + realgroup_id=settings.SITH_GROUP_PEDAGOGY_ADMIN_ID, user=tutu + ), + User.groups.through( + realgroup_id=settings.SITH_GROUP_SAS_ADMIN_ID, user=skia + ), + ] + ) + for user in richard, sli, krophil, skia: + self._create_profile_pict(user) - # Adding user public - public = User( - username="public", - last_name="Not subscribed", - first_name="Public", - email="public@git.an", - date_of_birth="1942-06-12", - is_superuser=False, - is_staff=False, - ) - public.set_password("plop") - public.save() - public.view_groups = [ae_members.id] - public.save() - # Adding user Subscriber - subscriber = User( - username="subscriber", - last_name="User", - first_name="Subscribed", - email="Subscribed@git.an", - date_of_birth="1942-06-12", - is_superuser=False, - is_staff=False, - ) - subscriber.set_password("plop") - subscriber.save() - subscriber.view_groups = [ae_members.id] - subscriber.save() - # Adding user old Subscriber - old_subscriber = User( - username="old_subscriber", - last_name="Subscriber", - first_name="Old", - email="old_subscriber@git.an", - date_of_birth="1942-06-12", - is_superuser=False, - is_staff=False, - ) - old_subscriber.set_password("plop") - old_subscriber.save() - old_subscriber.view_groups = [ae_members.id] - old_subscriber.save() - # Adding user Counter admin - counter = User( - username="counter", - last_name="Ter", - first_name="Coun", - email="counter@git.an", - date_of_birth="1942-06-12", - is_superuser=False, - is_staff=False, - ) - counter.set_password("plop") - counter.save() - counter.view_groups = [ae_members.id] - counter.groups.set( - [ - Group.objects.filter(id=settings.SITH_GROUP_COUNTER_ADMIN_ID) - .first() - .id - ] - ) - counter.save() - # Adding user Comptable - comptable = User( - username="comptable", - last_name="Able", - first_name="Compte", - email="compta@git.an", - date_of_birth="1942-06-12", - is_superuser=False, - is_staff=False, - ) - comptable.set_password("plop") - comptable.save() - comptable.view_groups = [ae_members.id] - comptable.groups.set( - [ - Group.objects.filter(id=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) - .first() - .id - ] - ) - comptable.save() - # Adding user Guy - u = User( - username="guy", - last_name="Carlier", - first_name="Guy", - email="guy@git.an", - date_of_birth="1942-06-12", - is_superuser=False, - is_staff=False, - ) - u.set_password("plop") - u.save() - u.view_groups = [ae_members.id] - u.save() - # Adding user Richard Batsbak - richard = User( - username="rbatsbak", - last_name="Batsbak", - first_name="Richard", - email="richard@git.an", - date_of_birth="1982-06-12", - ) - richard.set_password("plop") - richard.save() - richard.godfathers.add(comptable) - richard_profile_path = ( - root_path - / "core" - / "fixtures" - / "images" - / "sas" - / "Family" - / "richard.jpg" - ) - with open(richard_profile_path, "rb") as f: - name = f"{richard.id}_profile.jpg" - richard_profile = SithFile( - parent=profiles_root, - name=name, - file=resize_image(Image.open(BytesIO(f.read())), 400, "WEBP"), - owner=richard, - is_folder=False, - mime_type="image/webp", - size=richard_profile_path.stat().st_size, - ) - richard_profile.file.name = name - richard_profile.save() - richard.profile_pict = richard_profile - richard.save() - richard.view_groups = [ae_members.id] - richard.save() - # Adding syntax help page - p = Page(name="Aide_sur_la_syntaxe") - p.save(force_lock=True) - with open(root_path / "core" / "fixtures" / "SYNTAX.md", "r") as rm: - PageRev( - page=p, title="Aide sur la syntaxe", author=skia, content=rm.read() - ).save() - p.view_groups.set([settings.SITH_GROUP_PUBLIC_ID]) - p.save(force_lock=True) - p = Page(name="Services") - p.save(force_lock=True) - p.view_groups.set([settings.SITH_GROUP_PUBLIC_ID]) - p.save(force_lock=True) - PageRev( - page=p, - title="Services", - author=skia, - content=""" + User.godfathers.through.objects.bulk_create( + [ + User.godfathers.through(from_user=richard, to_user=comptable), + User.godfathers.through(from_user=root, to_user=skia), + User.godfathers.through(from_user=skia, to_user=root), + User.godfathers.through(from_user=sli, to_user=skia), + User.godfathers.through(from_user=public, to_user=richard), + User.godfathers.through(from_user=subscriber, to_user=richard), + ] + ) + + # Adding syntax help page + syntax_page = Page(name="Aide_sur_la_syntaxe") + syntax_page.save(force_lock=True) + PageRev.objects.create( + page=syntax_page, + title="Aide sur la syntaxe", + author=skia, + content=(self.ROOT_PATH / "core" / "fixtures" / "SYNTAX.md").read_text(), + ) + services_page = Page(name="Services") + services_page.save(force_lock=True) + PageRev.objects.create( + page=services_page, + title="Services", + author=skia, + content=""" | | | | | :---: | :---: | :---: | | [Eboutic](/eboutic) | [Laverie](/launderette) | Matmat | | SAS | Weekmail | Forum| """, - ).save() + ) - # Subscription - default_subscription = "un-semestre" - # Root - s = Subscription( - member=root, - subscription_type=default_subscription, - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0], - ) - s.subscription_start = s.compute_start() - s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], - start=s.subscription_start, - ) - s.save() - # Skia - s = Subscription( - member=User.objects.filter(pk=skia.pk).first(), - subscription_type=default_subscription, - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0], - ) - s.subscription_start = s.compute_start() - s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], - start=s.subscription_start, - ) - s.save() - # Counter admin - s = Subscription( - member=User.objects.filter(pk=counter.pk).first(), - subscription_type=default_subscription, - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0], - ) - s.subscription_start = s.compute_start() - s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], - start=s.subscription_start, - ) - s.save() - # Comptable - s = Subscription( - member=User.objects.filter(pk=comptable.pk).first(), - subscription_type=default_subscription, - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0], - ) - s.subscription_start = s.compute_start() - s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], - start=s.subscription_start, - ) - s.save() - # Richard - s = Subscription( - member=User.objects.filter(pk=richard.pk).first(), - subscription_type=default_subscription, - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0], - ) - s.subscription_start = s.compute_start() - s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], - start=s.subscription_start, - ) - s.save() - # User - s = Subscription( - member=User.objects.filter(pk=subscriber.pk).first(), - subscription_type=default_subscription, - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0], - ) - s.subscription_start = s.compute_start() - s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], - start=s.subscription_start, - ) - s.save() - # Old subscriber - s = Subscription( - member=User.objects.filter(pk=old_subscriber.pk).first(), - subscription_type=default_subscription, - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0], - ) - s.subscription_start = s.compute_start(datetime(year=2012, month=9, day=4)) - s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], - start=s.subscription_start, - ) - s.save() + index_page = Page(name="Index") + index_page.save(force_lock=True) + PageRev.objects.create( + page=index_page, + title="Wiki index", + author=root, + content=""" +Welcome to the wiki page! +""", + ) - # Clubs - Club( - name="Bibo'UT", - unix_name="bibout", - address="46 de la Boustifaille", - parent=main_club, - ).save() - guyut = Club( - name="Guy'UT", - unix_name="guyut", - address="42 de la Boustifaille", - parent=main_club, - ) - guyut.save() - Club( - name="Woenzel'UT", unix_name="woenzel", address="Woenzel", parent=guyut - ).save() - Membership(user=skia, club=main_club, role=3).save() - troll = Club( - name="Troll Penché", - unix_name="troll", - address="Terre Du Milieu", - parent=main_club, - ) - troll.save() - refound = Club( - name="Carte AE", - unix_name="carte_ae", - address="Jamais imprimée", - parent=main_club, - ) - refound.save() + laundry_page = Page(name="launderette") + laundry_page.save(force_lock=True) + PageRev.objects.create( + page=laundry_page, + title="Laverie", + author=root, + content="Fonctionnement de la laverie", + ) - # Counters - subscribers = Group.objects.get(name="Subscribers") - old_subscribers = Group.objects.get(name="Old subscribers") - Customer(user=skia, account_id="6568j", amount=0).save() - Customer(user=richard, account_id="4000k", amount=0).save() - p = ProductType(name="Bières bouteilles") - p.save() - c = ProductType(name="Cotisations") - c.save() - r = ProductType(name="Rechargements") - r.save() - verre = ProductType(name="Verre") - verre.save() - cotis = Product( - name="Cotis 1 semestre", - code="1SCOTIZ", - product_type=c, - purchase_price="15", - selling_price="15", - special_selling_price="15", - club=main_club, - ) - cotis.save() - cotis.buying_groups.add(subscribers) - cotis.buying_groups.add(old_subscribers) - cotis.save() - cotis2 = Product( - name="Cotis 2 semestres", - code="2SCOTIZ", - product_type=c, - purchase_price="28", - selling_price="28", - special_selling_price="28", - club=main_club, - ) - cotis2.save() - cotis2.buying_groups.add(subscribers) - cotis2.buying_groups.add(old_subscribers) - cotis2.save() - refill = Product( - name="Rechargement 15 €", - code="15REFILL", - product_type=r, - purchase_price="15", - selling_price="15", - special_selling_price="15", - club=main_club, - ) - refill.save() - refill.buying_groups.add(subscribers) - refill.save() - barb = Product( - name="Barbar", - code="BARB", - product_type=p, - purchase_price="1.50", - selling_price="1.7", - special_selling_price="1.6", - club=main_club, - limit_age=18, - ) - barb.save() - barb.buying_groups.add(subscribers) - barb.save() - cble = Product( - name="Chimay Bleue", - code="CBLE", - product_type=p, - purchase_price="1.50", - selling_price="1.7", - special_selling_price="1.6", - club=main_club, - limit_age=18, - ) - cble.save() - cble.buying_groups.add(subscribers) - cble.save() - cons = Product( - name="Consigne Eco-cup", - code="CONS", - product_type=verre, - purchase_price="1", - selling_price="1", - special_selling_price="1", - club=main_club, - ) - cons.save() - dcons = Product( - name="Déconsigne Eco-cup", - code="DECO", - product_type=verre, - purchase_price="-1", - selling_price="-1", - special_selling_price="-1", - club=main_club, - ) - dcons.save() - cors = Product( - name="Corsendonk", - code="CORS", - product_type=p, - purchase_price="1.50", - selling_price="1.7", - special_selling_price="1.6", - club=main_club, - limit_age=18, - ) - cors.save() - cors.buying_groups.add(subscribers) - cors.save() - carolus = Product( - name="Carolus", - code="CARO", - product_type=p, - purchase_price="1.50", - selling_price="1.7", - special_selling_price="1.6", - club=main_club, - limit_age=18, - ) - carolus.save() - carolus.buying_groups.add(subscribers) - carolus.save() - mde = Counter.objects.filter(name="MDE").first() - mde.products.add(barb) - mde.products.add(cble) - mde.products.add(cons) - mde.products.add(dcons) - mde.sellers.add(skia) + public_group.viewable_page.set( + [syntax_page, services_page, index_page, laundry_page] + ) - mde.save() + self._create_subscription(root) + self._create_subscription(skia) + self._create_subscription(counter) + self._create_subscription(comptable) + self._create_subscription(richard) + self._create_subscription(subscriber) + self._create_subscription(old_subscriber, start=date(year=2012, month=9, day=4)) + self._create_subscription(sli) + self._create_subscription(krophil) + self._create_subscription(comunity) + self._create_subscription(tutu) + StudentCard(uid="9A89B82018B0A0", customer=sli.customer).save() - eboutic = Counter.objects.filter(name="Eboutic").first() - eboutic.products.add(barb) - eboutic.products.add(cotis) - eboutic.products.add(cotis2) - eboutic.products.add(refill) - eboutic.save() + # Clubs + Club.objects.create( + name="Bibo'UT", + unix_name="bibout", + address="46 de la Boustifaille", + parent=main_club, + ) + guyut = Club.objects.create( + name="Guy'UT", + unix_name="guyut", + address="42 de la Boustifaille", + parent=main_club, + ) + Club.objects.create( + name="Woenzel'UT", unix_name="woenzel", address="Woenzel", parent=guyut + ) + troll = Club.objects.create( + name="Troll Penché", + unix_name="troll", + address="Terre Du Milieu", + parent=main_club, + ) + refound = Club.objects.create( + name="Carte AE", + unix_name="carte_ae", + address="Jamais imprimée", + parent=main_club, + ) - refound_counter = Counter(name="Carte AE", club=refound, type="OFFICE") - refound_counter.save() - refound_product = Product( - name="remboursement", - code="REMBOURS", - purchase_price="0", - selling_price="0", - special_selling_price="0", - club=refound, - ) - refound_product.save() - - # Accounting test values: - BankAccount(name="AE TG", club=main_club).save() - BankAccount(name="Carte AE", club=main_club).save() - ba = BankAccount(name="AE TI", club=main_club) - ba.save() - ca = ClubAccount(name="Troll Penché", bank_account=ba, club=troll) - ca.save() - gj = GeneralJournal(name="A16", start_date=date.today(), club_account=ca) - gj.save() - credit = AccountingType( - code="74", label="Subventions d'exploitation", movement_type="CREDIT" - ) - credit.save() - debit = AccountingType( - code="606", - label="Achats non stockés de matières et fournitures(*1)", - movement_type="DEBIT", - ) - debit.save() - debit2 = AccountingType( - code="604", - label="Achats d'études et prestations de services(*2)", - movement_type="DEBIT", - ) - debit2.save() - buying = AccountingType( - code="60", label="Achats (sauf 603)", movement_type="DEBIT" - ) - buying.save() - comptes = AccountingType( - code="6", label="Comptes de charge", movement_type="DEBIT" - ) - comptes.save() - simple = SimplifiedAccountingType( - label="Je fais du simple 6", accounting_type=comptes - ) - simple.save() - woenzco = Company(name="Woenzel & co") - woenzco.save() - - operation_list = [ - ( - 27, - "J'avais trop de bière", - "CASH", - None, - buying, - "USER", - skia.id, - "", - None, + Membership.objects.bulk_create( + [ + Membership(user=skia, club=main_club, role=3), + Membership( + user=comunity, + club=bar_club, + start_date=localdate(), + role=settings.SITH_CLUB_ROLES_ID["Board member"], ), - ( - 4000, - "Ceci n'est pas une opération... en fait si mais non", - "CHECK", - None, - debit, - "COMPANY", - woenzco.id, - "", - 23, + Membership( + user=sli, + club=troll, + role=9, + description="Padawan Troll", + start_date=localdate() - timedelta(days=17), ), - ( - 22, - "C'est de l'argent ?", - "CARD", - None, - credit, - "CLUB", - troll.id, - "", - None, + Membership( + user=krophil, + club=troll, + role=10, + description="Maitre Troll", + start_date=localdate() - timedelta(days=200), ), - ( - 37, - "Je paye CASH", - "CASH", - None, - debit2, - "OTHER", - None, - "tous les étudiants <3", - None, + Membership( + user=skia, + club=troll, + role=2, + description="Grand Ancien Troll", + start_date=localdate() - timedelta(days=400), + end_date=localdate() - timedelta(days=86), ), - (300, "Paiement Guy", "CASH", None, buying, "USER", skia.id, "", None), - (32.3, "Essence", "CASH", None, buying, "OTHER", None, "station", None), - ( - 46.42, - "Allumette", - "CHECK", - None, - credit, - "CLUB", - main_club.id, - "", - 57, - ), - ( - 666.42, - "Subvention de far far away", - "CASH", - None, - comptes, - "CLUB", - main_club.id, - "", - None, - ), - ( - 496, - "Ça, c'est un 6", - "CARD", - simple, - None, - "USER", - skia.id, - "", - None, - ), - ( - 17, - "La Gargotte du Korrigan", - "CASH", - None, - debit2, - "CLUB", - bar_club.id, - "", - None, + Membership( + user=richard, + club=troll, + role=2, + description="", + start_date=localdate() - timedelta(days=200), + end_date=localdate() - timedelta(days=100), ), ] - for op in operation_list: - operation = Operation( - journal=gj, - date=date.today(), - amount=op[0], - remark=op[1], - mode=op[2], - done=True, - simpleaccounting_type=op[3], - accounting_type=op[4], - target_type=op[5], - target_id=op[6], - target_label=op[7], - cheque_number=op[8], - ) - operation.clean() - operation.save() + ) - # Adding user sli - sli = User( - username="sli", - last_name="Li", - first_name="S", - email="sli@git.an", - date_of_birth="1942-06-12", - ) - sli.set_password("plop") - sli.save() - sli.view_groups = [ae_members.id] - sli.save() - sli_profile_path = ( - root_path - / "core" - / "fixtures" - / "images" - / "sas" - / "Family" - / "sli.jpg" - ) - with open(sli_profile_path, "rb") as f: - name = str(sli.id) + "_profile.jpg" - sli_profile = SithFile( - parent=profiles_root, - name=name, - file=resize_image(Image.open(BytesIO(f.read())), 400, "WEBP"), - owner=sli, - is_folder=False, - mime_type="image/webp", - size=sli_profile_path.stat().st_size, - ) - sli_profile.file.name = name - sli_profile.save() - sli.profile_pict = sli_profile - sli.save() - # Adding user Krophil - krophil = User( - username="krophil", - last_name="Phil'", - first_name="Kro", - email="krophil@git.an", - date_of_birth="1942-06-12", - ) - krophil.set_password("plop") - krophil.save() - krophil_profile_path = ( - root_path - / "core" - / "fixtures" - / "images" - / "sas" - / "Family" - / "krophil.jpg" - ) - with open(krophil_profile_path, "rb") as f: - name = str(krophil.id) + "_profile.jpg" - krophil_profile = SithFile( - parent=profiles_root, - name=name, - file=resize_image(Image.open(BytesIO(f.read())), 400, "WEBP"), - owner=krophil, - is_folder=False, - mime_type="image/webp", - size=krophil_profile_path.stat().st_size, - ) - krophil_profile.file.name = name - krophil_profile.save() - krophil.profile_pict = krophil_profile - krophil.save() - # Adding user Com Unity - comunity = User( - username="comunity", - last_name="Unity", - first_name="Com", - email="comunity@git.an", - date_of_birth="1942-06-12", - ) - comunity.set_password("plop") - comunity.save() - comunity.groups.set( - [Group.objects.filter(name="Communication admin").first().id] - ) - comunity.save() - Membership( - user=comunity, - club=bar_club, - start_date=localdate(), - role=settings.SITH_CLUB_ROLES_ID["Board member"], - ).save() - # Adding user tutu - tutu = User( - username="tutu", - last_name="Tu", - first_name="Tu", - email="tutu@git.an", - date_of_birth="1942-06-12", - ) - tutu.set_password("plop") - tutu.save() - tutu.groups.set([settings.SITH_GROUP_PEDAGOGY_ADMIN_ID]) - tutu.save() + p = ProductType.objects.create(name="Bières bouteilles") + c = ProductType.objects.create(name="Cotisations") + r = ProductType.objects.create(name="Rechargements") + verre = ProductType.objects.create(name="Verre") + cotis = Product.objects.create( + name="Cotis 1 semestre", + code="1SCOTIZ", + product_type=c, + purchase_price="15", + selling_price="15", + special_selling_price="15", + club=main_club, + ) + cotis2 = Product.objects.create( + name="Cotis 2 semestres", + code="2SCOTIZ", + product_type=c, + purchase_price="28", + selling_price="28", + special_selling_price="28", + club=main_club, + ) + refill = Product.objects.create( + name="Rechargement 15 €", + code="15REFILL", + product_type=r, + purchase_price="15", + selling_price="15", + special_selling_price="15", + club=main_club, + ) + barb = Product.objects.create( + name="Barbar", + code="BARB", + product_type=p, + purchase_price="1.50", + selling_price="1.7", + special_selling_price="1.6", + club=main_club, + limit_age=18, + ) + cble = Product.objects.create( + name="Chimay Bleue", + code="CBLE", + product_type=p, + purchase_price="1.50", + selling_price="1.7", + special_selling_price="1.6", + club=main_club, + limit_age=18, + ) + cons = Product.objects.create( + name="Consigne Eco-cup", + code="CONS", + product_type=verre, + purchase_price="1", + selling_price="1", + special_selling_price="1", + club=main_club, + ) + dcons = Product.objects.create( + name="Déconsigne Eco-cup", + code="DECO", + product_type=verre, + purchase_price="-1", + selling_price="-1", + special_selling_price="-1", + club=main_club, + ) + cors = Product.objects.create( + name="Corsendonk", + code="CORS", + product_type=p, + purchase_price="1.50", + selling_price="1.7", + special_selling_price="1.6", + club=main_club, + limit_age=18, + ) + carolus = Product.objects.create( + name="Carolus", + code="CARO", + product_type=p, + purchase_price="1.50", + selling_price="1.7", + special_selling_price="1.6", + club=main_club, + limit_age=18, + ) + subscribers.products.add(cotis, cotis2, refill, barb, cble, cors, carolus) + old_subscribers.products.add(cotis, cotis2) - # Adding subscription for sli - s = Subscription( - member=User.objects.filter(pk=sli.pk).first(), - subscription_type=next(iter(settings.SITH_SUBSCRIPTIONS.keys())), - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0], - ) - s.subscription_start = s.compute_start() - s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], - start=s.subscription_start, - ) - s.save() - StudentCard(uid="9A89B82018B0A0", customer=sli.customer).save() - # Adding subscription for Krophil - s = Subscription( - member=User.objects.filter(pk=krophil.pk).first(), - subscription_type=next(iter(settings.SITH_SUBSCRIPTIONS.keys())), - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0], - ) - s.subscription_start = s.compute_start() - s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], - start=s.subscription_start, - ) - s.save() - # Com Unity - s = Subscription( - member=comunity, - subscription_type=default_subscription, - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0], - ) - s.subscription_start = s.compute_start() - s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], - start=s.subscription_start, - ) - s.save() - # Tutu - s = Subscription( - member=tutu, - subscription_type=default_subscription, - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0], - ) - s.subscription_start = s.compute_start() - s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], - start=s.subscription_start, - ) - s.save() + mde = Counter.objects.get(name="MDE") + mde.products.add(barb, cble, cons, dcons) - Selling( - label=dcons.name, - product=dcons, - counter=mde, - unit_price=dcons.selling_price, - club=main_club, - quantity=settings.SITH_ECOCUP_LIMIT + 3, - seller=skia, - customer=krophil.customer, - ).save() + eboutic = Counter.objects.get(name="Eboutic") + eboutic.products.add(barb, cotis, cotis2, refill) - # Add barman to counter - c = Counter.objects.get(id=2) - c.sellers.add(User.objects.get(pk=krophil.pk)) - mde.sellers.add(sli) - c.save() + Counter.objects.create(name="Carte AE", club=refound, type="OFFICE") + Product.objects.create( + name="remboursement", + code="REMBOURS", + purchase_price="0", + selling_price="0", + special_selling_price="0", + club=refound, + ) - # Create an election - public_group = Group.objects.get(id=settings.SITH_GROUP_PUBLIC_ID) - subscriber_group = Group.objects.get(name=settings.SITH_MAIN_MEMBERS_GROUP) - ae_board_group = Group.objects.get(name=settings.SITH_MAIN_BOARD_GROUP) - el = Election( - title="Élection 2017", - description="La roue tourne", - start_candidature="1942-06-12 10:28:45+01", - end_candidature="2042-06-12 10:28:45+01", - start_date="1942-06-12 10:28:45+01", - end_date="7942-06-12 10:28:45+01", - ) - el.save() - el.view_groups.add(public_group) - el.edit_groups.add(ae_board_group) - el.candidature_groups.add(subscriber_group) - el.vote_groups.add(subscriber_group) - el.save() - liste = ElectionList(title="Candidature Libre", election=el) - liste.save() - listeT = ElectionList(title="Troll", election=el) - listeT.save() - pres = Role(election=el, title="Président AE", description="Roi de l'AE") - pres.save() - resp = Role( - election=el, title="Co Respo Info", max_choice=2, description="Ghetto++" - ) - resp.save() - cand = Candidature( - role=resp, user=skia, election_list=liste, program="Refesons le site AE" - ) - cand.save() - cand = Candidature( - role=resp, - user=sli, - election_list=liste, - program="Vasy je deviens mon propre adjoint", - ) - cand.save() - cand = Candidature( - role=resp, user=krophil, election_list=listeT, program="Le Pôle Troll !" - ) - cand.save() - cand = Candidature( - role=pres, - user=sli, - election_list=listeT, - program="En fait j'aime pas l'info, je voulais faire GMC", - ) - cand.save() + # Accounting test values: + BankAccount.objects.create(name="AE TG", club=main_club) + BankAccount.objects.create(name="Carte AE", club=main_club) + ba = BankAccount.objects.create(name="AE TI", club=main_club) + ca = ClubAccount.objects.create( + name="Troll Penché", bank_account=ba, club=troll + ) + gj = GeneralJournal.objects.create( + name="A16", start_date=date.today(), club_account=ca + ) + credit = AccountingType.objects.create( + code="74", label="Subventions d'exploitation", movement_type="CREDIT" + ) + debit = AccountingType.objects.create( + code="606", + label="Achats non stockés de matières et fournitures(*1)", + movement_type="DEBIT", + ) + debit2 = AccountingType.objects.create( + code="604", + label="Achats d'études et prestations de services(*2)", + movement_type="DEBIT", + ) + buying = AccountingType.objects.create( + code="60", label="Achats (sauf 603)", movement_type="DEBIT" + ) + comptes = AccountingType.objects.create( + code="6", label="Comptes de charge", movement_type="DEBIT" + ) + SimplifiedAccountingType.objects.create( + label="Je fais du simple 6", accounting_type=comptes + ) + woenzco = Company.objects.create(name="Woenzel & co") - # Forum - room = Forum( - name="Salon de discussions", - description="Pour causer de tout", - is_category=True, + operation_list = [ + (27, "J'avais trop de bière", "CASH", buying, "USER", skia.id, None), + (4000, "Pas une opération", "CHECK", debit, "COMPANY", woenzco.id, 23), + (22, "C'est de l'argent ?", "CARD", credit, "CLUB", troll.id, None), + (37, "Je paye CASH", "CASH", debit2, "OTHER", None, None), + (300, "Paiement Guy", "CASH", buying, "USER", skia.id, None), + (32.3, "Essence", "CASH", buying, "OTHER", None, None), + (46.42, "Allumette", "CHECK", credit, "CLUB", main_club.id, 57), + (666.42, "Subvention club", "CASH", comptes, "CLUB", main_club.id, None), + (496, "Ça, c'est un 6", "CARD", comptes, "USER", skia.id, None), + (17, "La Gargotte du Korrigan", "CASH", debit2, "CLUB", bar_club.id, None), + ] + operations = [ + Operation( + number=index, + journal=gj, + date=localdate(), + amount=op[0], + remark=op[1], + mode=op[2], + done=True, + accounting_type=op[3], + target_type=op[4], + target_id=op[5], + target_label="" if op[4] != "OTHER" else "Autre source", + cheque_number=op[6], ) - room.save() - Forum(name="AE", description="Réservé au bureau AE", parent=room).save() - Forum(name="BdF", description="Réservé au bureau BdF", parent=room).save() - hall = Forum( - name="Hall de discussions", - description="Pour toutes les discussions", - parent=room, - ) - hall.save() - various = Forum( - name="Divers", description="Pour causer de rien", is_category=True - ) - various.save() - Forum( - name="Promos", description="Réservé aux Promos", parent=various - ).save() - ForumTopic(forum=hall) + for index, op in enumerate(operation_list, start=1) + ] + for operation in operations: + operation.clean() + Operation.objects.bulk_create(operations) - # News - friday = self.now - while friday.weekday() != 4: - friday += timedelta(hours=6) - friday.replace(hour=20, minute=0, second=0) - # Event - n = News( - title="Apero barman", - summary="Viens boire un coup avec les barmans", - content="Glou glou glou glou glou glou glou", - type="EVENT", - club=bar_club, - author=subscriber, - is_moderated=True, - moderator=skia, - ) - n.save() + # Add barman to counter + Counter.sellers.through.objects.bulk_create( + [ + Counter.sellers.through(counter_id=2, user=krophil), + Counter.sellers.through(counter=mde, user=skia), + ] + ) + + # Create an election + ae_board_group = Group.objects.get(name=settings.SITH_MAIN_BOARD_GROUP) + el = Election.objects.create( + title="Élection 2017", + description="La roue tourne", + start_candidature="1942-06-12 10:28:45+01", + end_candidature="2042-06-12 10:28:45+01", + start_date="1942-06-12 10:28:45+01", + end_date="7942-06-12 10:28:45+01", + ) + el.view_groups.add(public_group) + el.edit_groups.add(ae_board_group) + el.candidature_groups.add(subscribers) + el.vote_groups.add(subscribers) + liste = ElectionList.objects.create(title="Candidature Libre", election=el) + listeT = ElectionList.objects.create(title="Troll", election=el) + pres = Role.objects.create( + election=el, title="Président AE", description="Roi de l'AE" + ) + resp = Role.objects.create( + election=el, title="Co Respo Info", max_choice=2, description="Ghetto++" + ) + Candidature.objects.bulk_create( + [ + Candidature( + role=resp, + user=skia, + election_list=liste, + program="Refesons le site AE", + ), + Candidature( + role=resp, + user=sli, + election_list=liste, + program="Vasy je deviens mon propre adjoint", + ), + Candidature( + role=resp, + user=krophil, + election_list=listeT, + program="Le Pôle Troll !", + ), + Candidature( + role=pres, + user=sli, + election_list=listeT, + program="En fait j'aime pas l'info, je voulais faire GMC", + ), + ] + ) + + # Forum + room = Forum.objects.create( + name="Salon de discussions", + description="Pour causer de tout", + is_category=True, + ) + various = Forum.objects.create( + name="Divers", description="Pour causer de rien", is_category=True + ) + Forum.objects.bulk_create( + [ + Forum(name="AE", description="Réservé au bureau AE", parent=room), + Forum(name="BdF", description="Réservé au bureau BdF", parent=room), + Forum(name="Promos", description="Réservé aux Promos", parent=various), + Forum( + name="Hall de discussions", + description="Pour toutes les discussions", + parent=room, + ), + ] + ) + + # News + friday = self.now + while friday.weekday() != 4: + friday += timedelta(hours=6) + friday.replace(hour=20, minute=0, second=0) + # Event + news_dates = [] + n = News.objects.create( + title="Apero barman", + summary="Viens boire un coup avec les barmans", + content="Glou glou glou glou glou glou glou", + type="EVENT", + club=bar_club, + author=subscriber, + is_moderated=True, + moderator=skia, + ) + news_dates.append( NewsDate( news=n, start_date=self.now + timedelta(hours=70), end_date=self.now + timedelta(hours=72), - ).save() - n = News( - title="Repas barman", - summary="Enjoy la fin du semestre!", - content=( - "Viens donc t'enjailler avec les autres barmans aux " - "frais du BdF! \\o/" - ), - type="EVENT", - club=bar_club, - author=subscriber, - is_moderated=True, - moderator=skia, ) - n.save() + ) + n = News.objects.create( + title="Repas barman", + summary="Enjoy la fin du semestre!", + content=( + "Viens donc t'enjailler avec les autres barmans aux " + "frais du BdF! \\o/" + ), + type="EVENT", + club=bar_club, + author=subscriber, + is_moderated=True, + moderator=skia, + ) + news_dates.append( NewsDate( news=n, start_date=self.now + timedelta(hours=72), end_date=self.now + timedelta(hours=84), - ).save() - n = News( - title="Repas fromager", - summary="Wien manger du l'bon fromeug'", - content="Fô viendre mangey d'la bonne fondue!", - type="EVENT", - club=bar_club, - author=subscriber, - is_moderated=True, - moderator=skia, ) - n.save() + ) + News.objects.create( + title="Repas fromager", + summary="Wien manger du l'bon fromeug'", + content="Fô viendre mangey d'la bonne fondue!", + type="EVENT", + club=bar_club, + author=subscriber, + is_moderated=True, + moderator=skia, + ) + news_dates.append( NewsDate( news=n, start_date=self.now + timedelta(hours=96), end_date=self.now + timedelta(hours=100), - ).save() - n = News( - title="SdF", - summary="Enjoy la fin des finaux!", - content="Viens faire la fête avec tout plein de gens!", - type="EVENT", - club=bar_club, - author=subscriber, - is_moderated=True, - moderator=skia, ) - n.save() + ) + n = News.objects.create( + title="SdF", + summary="Enjoy la fin des finaux!", + content="Viens faire la fête avec tout plein de gens!", + type="EVENT", + club=bar_club, + author=subscriber, + is_moderated=True, + moderator=skia, + ) + news_dates.append( NewsDate( news=n, start_date=friday + timedelta(hours=24 * 7 + 1), end_date=self.now + timedelta(hours=24 * 7 + 9), - ).save() - # Weekly - n = News( - title="Jeux sans faim", - summary="Viens jouer!", - content="Rejoins la fine équipe du Troll Penché et viens " - "d'amuser le Vendredi soir!", - type="WEEKLY", - club=troll, - author=subscriber, - is_moderated=True, - moderator=skia, ) - n.save() - for i in range(10): + ) + # Weekly + n = News.objects.create( + title="Jeux sans faim", + summary="Viens jouer!", + content="Rejoins la fine équipe du Troll Penché et viens " + "t'amuser le Vendredi soir!", + type="WEEKLY", + club=troll, + author=subscriber, + is_moderated=True, + moderator=skia, + ) + news_dates.extend( + [ NewsDate( news=n, - start_date=friday + timedelta(hours=24 * 7 * i), - end_date=friday + timedelta(hours=24 * 7 * i + 8), - ).save() + start_date=friday + timedelta(days=7 * i), + end_date=friday + timedelta(days=7 * i, hours=8), + ) + for i in range(10) + ] + ) + NewsDate.objects.bulk_create(news_dates) - # Create som data for pedagogy + # Create som data for pedagogy - UV( - code="PA00", - author=User.objects.get(id=0), - credit_type=settings.SITH_PEDAGOGY_UV_TYPE[3][0], - manager="Laurent HEYBERGER", - semester=settings.SITH_PEDAGOGY_UV_SEMESTER[3][0], - language=settings.SITH_PEDAGOGY_UV_LANGUAGE[0][0], - department=settings.SITH_PROFILE_DEPARTMENTS[-2][0], - credits=5, - title="Participation dans une association étudiante", - objectives="* Permettre aux étudiants de réaliser, pendant un semestre, un projet culturel ou associatif et de le valoriser.", - program="""* Semestre précédent proposition d'un projet et d'un cahier des charges + UV( + code="PA00", + author=User.objects.get(id=0), + credit_type=settings.SITH_PEDAGOGY_UV_TYPE[3][0], + manager="Laurent HEYBERGER", + semester=settings.SITH_PEDAGOGY_UV_SEMESTER[3][0], + language=settings.SITH_PEDAGOGY_UV_LANGUAGE[0][0], + department=settings.SITH_PROFILE_DEPARTMENTS[-2][0], + credits=5, + title="Participation dans une association étudiante", + objectives="* Permettre aux étudiants de réaliser, pendant un semestre, un projet culturel ou associatif et de le valoriser.", + program="""* Semestre précédent proposition d'un projet et d'un cahier des charges * Evaluation par un jury de six membres * Si accord réalisation dans le cadre de l'UV * Compte-rendu de l'expérience * Présentation""", - skills="""* Gérer un projet associatif ou une action éducative en autonomie: + skills="""* Gérer un projet associatif ou une action éducative en autonomie: * en produisant un cahier des charges qui -définit clairement le contexte du projet personnel -pose les jalons de ce projet -estime de manière réaliste les moyens et objectifs du projet -définit exactement les livrables attendus * en étant capable de respecter ce cahier des charges ou, le cas échéant, de réviser le cahier des charges de manière argumentée. * Relater son expérience dans un rapport: * qui permettra à d'autres étudiants de poursuivre les actions engagées * qui montre la capacité à s'auto-évaluer et à adopter une distance critique sur son action.""", - key_concepts="""* Autonomie + key_concepts="""* Autonomie * Responsabilité * Cahier des charges * Gestion de projet""", - hours_THE=121, - hours_TE=4, - ).save() + hours_THE=121, + hours_TE=4, + ).save() - # SAS - skia.groups.add(sas_admin.id) - sas_fixtures_path = root_path / "core" / "fixtures" / "images" / "sas" - for f in sas_fixtures_path.glob("*"): - if f.is_dir(): - album = Album( - parent=sas, - name=f.name, + # SAS + for f in self.SAS_FIXTURE_PATH.glob("*"): + if f.is_dir(): + album = Album( + parent=sas, + name=f.name, + owner=root, + is_folder=True, + is_in_sas=True, + is_moderated=True, + ) + album.clean() + album.save() + for p in f.iterdir(): + file = resize_image(Image.open(p), 1000, "WEBP") + pict = Picture( + parent=album, + name=p.name, + file=file, owner=root, - is_folder=True, + is_folder=False, is_in_sas=True, is_moderated=True, + mime_type="image/webp", + size=file.size, ) - album.clean() - album.save() - for p in f.iterdir(): - pict = Picture( - parent=album, - name=p.name, - file=resize_image( - Image.open(BytesIO(p.read_bytes())), 1000, "WEBP" - ), - owner=root, - is_folder=False, - is_in_sas=True, - is_moderated=True, - mime_type="image/webp", - size=p.stat().st_size, - ) - pict.file.name = p.name - pict.clean() - pict.generate_thumbnails() - pict.save() + pict.file.name = p.name + pict.clean() + pict.generate_thumbnails() - p = Picture.objects.get(name="skia.jpg") - PeoplePictureRelation(user=skia, picture=p).save() - p = Picture.objects.get(name="sli.jpg") - PeoplePictureRelation(user=sli, picture=p).save() - p = Picture.objects.get(name="krophil.jpg") - PeoplePictureRelation(user=krophil, picture=p).save() - p = Picture.objects.get(name="skia_sli.jpg") - PeoplePictureRelation(user=skia, picture=p).save() - PeoplePictureRelation(user=sli, picture=p).save() - p = Picture.objects.get(name="skia_sli_krophil.jpg") - PeoplePictureRelation(user=skia, picture=p).save() - PeoplePictureRelation(user=sli, picture=p).save() - PeoplePictureRelation(user=krophil, picture=p).save() - p = Picture.objects.get(name="richard.jpg") - PeoplePictureRelation(user=richard, picture=p).save() + img_skia = Picture.objects.get(name="skia.jpg") + img_sli = Picture.objects.get(name="sli.jpg") + img_krophil = Picture.objects.get(name="krophil.jpg") + img_skia_sli = Picture.objects.get(name="skia_sli.jpg") + img_skia_sli_krophil = Picture.objects.get(name="skia_sli_krophil.jpg") + img_richard = Picture.objects.get(name="rbatsbak.jpg") + PeoplePictureRelation.objects.bulk_create( + [ + PeoplePictureRelation(user=skia, picture=img_skia), + PeoplePictureRelation(user=sli, picture=img_sli), + PeoplePictureRelation(user=krophil, picture=img_krophil), + PeoplePictureRelation(user=skia, picture=img_skia_sli), + PeoplePictureRelation(user=sli, picture=img_skia_sli), + PeoplePictureRelation(user=skia, picture=img_skia_sli_krophil), + PeoplePictureRelation(user=sli, picture=img_skia_sli_krophil), + PeoplePictureRelation(user=krophil, picture=img_skia_sli_krophil), + PeoplePictureRelation(user=richard, picture=img_richard), + ] + ) - with open(skia_profile_path, "rb") as f: - name = str(skia.id) + "_profile.jpg" - skia_profile = SithFile( - parent=profiles_root, - name=name, - file=resize_image(Image.open(BytesIO(f.read())), 400, "WEBP"), - owner=skia, - is_folder=False, - mime_type="image/webp", - size=skia_profile_path.stat().st_size, - ) - skia_profile.file.name = name - skia_profile.save() - skia.profile_pict = skia_profile - skia.save() + def _create_profile_pict(self, user: User): + path = self.SAS_FIXTURE_PATH / "Family" / f"{user.username}.jpg" + file = resize_image(Image.open(path), 400, "WEBP") + name = f"{user.id}_profile.webp" + profile = SithFile( + parent=self.profiles_root, + name=name, + file=file, + owner=user, + is_folder=False, + mime_type="image/webp", + size=file.size, + ) + profile.file.name = name + profile.save() + user.profile_pict = profile + user.save() - # Create some additional data for galaxy to work with - root.godfathers.add(skia) - skia.godfathers.add(root) - sli.godfathers.add(skia) - richard.godchildren.add(subscriber) - richard.godchildren.add(public) - Membership( - user=sli, - club=troll, - role=9, - description="Padawan Troll", - start_date=localdate() - timedelta(days=17), - ).save() - Membership( - user=krophil, - club=troll, - role=10, - description="Maitre Troll", - start_date=localdate() - timedelta(days=200), - ).save() - Membership( - user=skia, - club=troll, - role=2, - description="Grand Ancien Troll", - start_date=localdate() - timedelta(days=400), - end_date=localdate() - timedelta(days=86), - ).save() - Membership( - user=richard, - club=troll, - role=2, - description="", - start_date=localdate() - timedelta(days=200), - end_date=localdate() - timedelta(days=100), - ).save() + def _create_subscription( + self, + user: User, + subscription_type: str = "un-semestre", + start: date | None = None, + ): + s = Subscription( + member=user, + subscription_type=subscription_type, + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0], + ) + s.subscription_start = s.compute_start(start) + s.subscription_end = s.compute_end( + duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], + start=s.subscription_start, + ) + s.save() diff --git a/counter/tests/test_counter.py b/counter/tests/test_counter.py index cdc10835..855b43f6 100644 --- a/counter/tests/test_counter.py +++ b/counter/tests/test_counter.py @@ -43,6 +43,7 @@ class TestCounter(TestCase): cls.skia = User.objects.filter(username="skia").first() cls.sli = User.objects.filter(username="sli").first() cls.krophil = User.objects.filter(username="krophil").first() + cls.richard = User.objects.filter(username="rbatsbak").first() cls.mde = Counter.objects.filter(name="MDE").first() cls.foyer = Counter.objects.get(id=2) @@ -63,7 +64,7 @@ class TestCounter(TestCase): response = self.client.post( reverse("counter:details", kwargs={"counter_id": self.mde.id}), - {"code": "4000k", "counter_token": counter_token}, + {"code": self.richard.customer.account_id, "counter_token": counter_token}, ) counter_url = response.get("location") response = self.client.get(response.get("location")) @@ -134,7 +135,7 @@ class TestCounter(TestCase): response = self.client.post( reverse("counter:details", kwargs={"counter_id": self.foyer.id}), - {"code": "4000k", "counter_token": counter_token}, + {"code": self.richard.customer.account_id, "counter_token": counter_token}, ) counter_url = response.get("location")