directly work on group ids

add tests
This commit is contained in:
imperosol
2026-03-08 16:04:03 +01:00
parent 654ba383b4
commit e188acc78b
2 changed files with 96 additions and 44 deletions

View File

@@ -157,14 +157,7 @@ class Customer(models.Model):
@property @property
def can_buy(self) -> bool: def can_buy(self) -> bool:
"""Check if whether this customer has the right to purchase any item. """Check if whether this customer has the right to purchase any item."""
This must be not confused with the Product.can_be_sold_to(user)
method as the present method returns an information
about a customer whereas the other tells something
about the relation between a User (not a Customer,
don't mix them) and a Product.
"""
subscription = self.user.subscriptions.order_by("subscription_end").last() subscription = self.user.subscriptions.order_by("subscription_end").last()
if subscription is None: if subscription is None:
return False return False
@@ -416,38 +409,6 @@ class Product(models.Model):
pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID
) or user.is_in_group(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID) ) or user.is_in_group(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID)
def can_be_sold_to(self, user: User) -> bool:
"""Check if whether the user given in parameter has the right to buy
this product or not.
This must be not confused with the Customer.can_buy()
method as the present method returns an information
about the relation between a User and a Product,
whereas the other tells something about a Customer
(and not a user, they are not the same model).
Returns:
True if the user can buy this product else False
Warning:
This performs a db query, thus you can quickly have
a N+1 queries problem if you call it in a loop.
Hopefully, you can avoid that if you prefetch the buying_groups :
```python
user = User.objects.get(username="foobar")
products = [
p
for p in Product.objects.prefetch_related("buying_groups")
if p.can_be_sold_to(user)
]
```
"""
buying_groups = list(self.buying_groups.all())
if not buying_groups:
return True
return any(user.is_in_group(pk=group.id) for group in buying_groups)
class PriceQuerySet(models.QuerySet): class PriceQuerySet(models.QuerySet):
def for_user(self, user: User) -> Self: def for_user(self, user: User) -> Self:

View File

@@ -9,14 +9,16 @@ from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import Client, TestCase from django.test import Client, TestCase
from django.urls import reverse from django.urls import reverse
from model_bakery import baker from model_bakery import baker
from model_bakery.recipe import Recipe
from PIL import Image from PIL import Image
from pytest_django.asserts import assertNumQueries, assertRedirects from pytest_django.asserts import assertNumQueries, assertRedirects
from club.models import Club from club.models import Club
from core.baker_recipes import board_user, subscriber_user from core.baker_recipes import board_user, subscriber_user
from core.models import Group, User from core.models import Group, User
from counter.forms import ProductForm from counter.baker_recipes import product_recipe
from counter.models import Product, ProductType from counter.forms import ProductForm, ProductPriceFormSet
from counter.models import Price, Product, ProductType
@pytest.mark.django_db @pytest.mark.django_db
@@ -112,7 +114,7 @@ class TestCreateProduct(TestCase):
"action-INITIAL_FORMS": 0, "action-INITIAL_FORMS": 0,
} }
def test_form(self): def test_form_simple(self):
form = ProductForm(data=self.data) form = ProductForm(data=self.data)
assert form.is_valid() assert form.is_valid()
instance = form.save() instance = form.save()
@@ -120,7 +122,7 @@ class TestCreateProduct(TestCase):
assert instance.product_type == self.product_type assert instance.product_type == self.product_type
assert instance.name == "foo" assert instance.name == "foo"
def test_view(self): def test_view_simple(self):
self.client.force_login(self.counter_admin) self.client.force_login(self.counter_admin)
url = reverse("counter:new_product") url = reverse("counter:new_product")
response = self.client.get(url) response = self.client.get(url)
@@ -131,3 +133,92 @@ class TestCreateProduct(TestCase):
assert product.name == "foo" assert product.name == "foo"
assert product.club == self.club assert product.club == self.club
assert product.product_type == self.product_type assert product.product_type == self.product_type
class TestPriceFormSet(TestCase):
@classmethod
def setUpTestData(cls):
cls.product = product_recipe.make()
cls.counter_admin = baker.make(
User, groups=[Group.objects.get(id=settings.SITH_GROUP_COUNTER_ADMIN_ID)]
)
cls.groups = baker.make(Group, _quantity=3)
def test_add_price(self):
data = {
"prices-0-amount": 2,
"prices-0-label": "foo",
"prices-0-groups": [self.groups[0].id, self.groups[1].id],
"prices-0-is_always_shown": True,
"prices-1-amount": 1.5,
"prices-1-label": "",
"prices-1-groups": [self.groups[1].id, self.groups[2].id],
"prices-1-is_always_shown": False,
"prices-TOTAL_FORMS": 2,
"prices-INITIAL_FORMS": 0,
}
form = ProductPriceFormSet(instance=self.product, data=data)
assert form.is_valid()
form.save()
prices = list(self.product.prices.order_by("amount"))
assert len(prices) == 2
assert prices[0].amount == 1.5
assert prices[0].label == ""
assert prices[0].is_always_shown is False
assert set(prices[0].groups.all()) == {self.groups[1], self.groups[2]}
assert prices[1].amount == 2
assert prices[1].label == "foo"
assert prices[1].is_always_shown is True
assert set(prices[1].groups.all()) == {self.groups[0], self.groups[1]}
def test_change_prices(self):
price_a = baker.make(
Price, product=self.product, amount=1.5, groups=self.groups[:1]
)
price_b = baker.make(
Price, product=self.product, amount=2, groups=self.groups[1:]
)
data = {
"prices-0-id": price_a.id,
"prices-0-DELETE": True,
"prices-1-id": price_b.id,
"prices-1-DELETE": False,
"prices-1-amount": 3,
"prices-1-label": "foo",
"prices-1-groups": [self.groups[1].id],
"prices-1-is_always_shown": True,
"prices-TOTAL_FORMS": 2,
"prices-INITIAL_FORMS": 2,
}
form = ProductPriceFormSet(instance=self.product, data=data)
assert form.is_valid()
form.save()
prices = list(self.product.prices.order_by("amount"))
assert len(prices) == 1
assert prices[0].amount == 3
assert prices[0].label == "foo"
assert prices[0].is_always_shown is True
assert set(prices[0].groups.all()) == {self.groups[1]}
assert not Price.objects.filter(id=price_a.id).exists()
@pytest.mark.django_db
def test_price_for_user():
groups = baker.make(Group, _quantity=4)
users = [
baker.make(User, groups=groups[:2]),
baker.make(User, groups=groups[1:3]),
baker.make(User, groups=[groups[3]]),
]
recipe = Recipe(Price, product=product_recipe.make())
prices = [
recipe.make(amount=5, groups=groups, is_always_shown=True),
recipe.make(amount=4, groups=[groups[0]], is_always_shown=True),
recipe.make(amount=3, groups=[groups[1]], is_always_shown=False),
recipe.make(amount=2, groups=[groups[3]], is_always_shown=False),
recipe.make(amount=1, groups=[groups[1]], is_always_shown=False),
]
qs = Price.objects.order_by("-amount")
assert set(qs.for_user(users[0])) == {prices[0], prices[1], prices[4]}
assert set(qs.for_user(users[1])) == {prices[0], prices[4]}
assert set(qs.for_user(users[2])) == {prices[0], prices[3]}