mirror of
https://github.com/ae-utbm/sith.git
synced 2024-12-22 07:41:14 +00:00
Add ProductTypeController
This commit is contained in:
parent
483670e798
commit
c79c251ba7
@ -14,22 +14,31 @@
|
||||
#
|
||||
from django.conf import settings
|
||||
from django.db.models import F
|
||||
from django.shortcuts import get_object_or_404
|
||||
from ninja import Query
|
||||
from ninja_extra import ControllerBase, api_controller, paginate, route
|
||||
from ninja_extra.pagination import PageNumberPaginationExtra
|
||||
from ninja_extra.schemas import PaginatedResponseSchema
|
||||
|
||||
from core.api_permissions import CanAccessLookup, CanView, IsInGroup, IsRoot
|
||||
from counter.models import Counter, Product
|
||||
from counter.models import Counter, Product, ProductType
|
||||
from counter.schemas import (
|
||||
CounterFilterSchema,
|
||||
CounterSchema,
|
||||
ProductFilterSchema,
|
||||
ProductSchema,
|
||||
ProductTypeSchema,
|
||||
ReorderProductTypeSchema,
|
||||
SimpleProductSchema,
|
||||
SimplifiedCounterSchema,
|
||||
)
|
||||
|
||||
IsCounterAdmin = (
|
||||
IsRoot
|
||||
| IsInGroup(settings.SITH_GROUP_COUNTER_ADMIN_ID)
|
||||
| IsInGroup(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
|
||||
)
|
||||
|
||||
|
||||
@api_controller("/counter")
|
||||
class CounterController(ControllerBase):
|
||||
@ -80,11 +89,7 @@ class ProductController(ControllerBase):
|
||||
@route.get(
|
||||
"/search/detailed",
|
||||
response=PaginatedResponseSchema[ProductSchema],
|
||||
permissions=[
|
||||
IsRoot
|
||||
| IsInGroup(settings.SITH_GROUP_COUNTER_ADMIN_ID)
|
||||
| IsInGroup(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
|
||||
],
|
||||
permissions=[IsCounterAdmin],
|
||||
url_name="search_products_detailed",
|
||||
)
|
||||
@paginate(PageNumberPaginationExtra, page_size=50)
|
||||
@ -100,3 +105,40 @@ class ProductController(ControllerBase):
|
||||
"name",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@api_controller("/product-type", permissions=[IsCounterAdmin])
|
||||
class ProductTypeController(ControllerBase):
|
||||
@route.get("", response=list[ProductTypeSchema], url_name="fetch-product-types")
|
||||
def fetch_all(self):
|
||||
return ProductType.objects.order_by("order")
|
||||
|
||||
@route.patch("/{type_id}/move")
|
||||
def reorder(self, type_id: int, other_id: Query[ReorderProductTypeSchema]):
|
||||
"""Change the order of a product type.
|
||||
|
||||
To use this route, give either the id of the product type
|
||||
this one should be above of,
|
||||
of the id of the product type this one should be below of.
|
||||
|
||||
Order affects the display order of the product types.
|
||||
|
||||
Examples:
|
||||
```
|
||||
GET /api/counter/product-type
|
||||
=> [<1: type A>, <2: type B>, <3: type C>]
|
||||
|
||||
PATCH /api/counter/product-type/3/move?below=1
|
||||
|
||||
GET /api/counter/product-type
|
||||
=> [<1: type A>, <3: type C>, <2: type B>]
|
||||
```
|
||||
"""
|
||||
product_type: ProductType = self.get_object_or_exception(
|
||||
ProductType, pk=type_id
|
||||
)
|
||||
other = get_object_or_404(ProductType, pk=other_id.above or other_id.below)
|
||||
if other_id.below is not None:
|
||||
product_type.below(other)
|
||||
else:
|
||||
product_type.above(other)
|
||||
|
@ -1,8 +1,9 @@
|
||||
from typing import Annotated
|
||||
from typing import Annotated, Self
|
||||
|
||||
from annotated_types import MinLen
|
||||
from django.urls import reverse
|
||||
from ninja import Field, FilterSchema, ModelSchema
|
||||
from ninja import Field, FilterSchema, ModelSchema, Schema
|
||||
from pydantic import model_validator
|
||||
|
||||
from club.schemas import ClubSchema
|
||||
from core.schemas import GroupSchema, SimpleUserSchema
|
||||
@ -29,11 +30,36 @@ class SimplifiedCounterSchema(ModelSchema):
|
||||
|
||||
|
||||
class ProductTypeSchema(ModelSchema):
|
||||
class Meta:
|
||||
model = ProductType
|
||||
fields = ["id", "name", "description", "comment", "icon", "order"]
|
||||
|
||||
url: str
|
||||
|
||||
@staticmethod
|
||||
def resolve_url(obj: ProductType) -> str:
|
||||
return reverse("counter:producttype_edit", kwargs={"type_id": obj.id})
|
||||
|
||||
|
||||
class SimpleProductTypeSchema(ModelSchema):
|
||||
class Meta:
|
||||
model = ProductType
|
||||
fields = ["id", "name"]
|
||||
|
||||
|
||||
class ReorderProductTypeSchema(Schema):
|
||||
below: int | None = None
|
||||
above: int | None = None
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_exclusive(self) -> Self:
|
||||
if self.below is None and self.above is None:
|
||||
raise ValueError("Either 'below' or 'above' must be set.")
|
||||
if self.below is not None and self.above is not None:
|
||||
raise ValueError("Only one of 'below' or 'above' must be set.")
|
||||
return self
|
||||
|
||||
|
||||
class SimpleProductSchema(ModelSchema):
|
||||
class Meta:
|
||||
model = Product
|
||||
@ -57,7 +83,7 @@ class ProductSchema(ModelSchema):
|
||||
|
||||
buying_groups: list[GroupSchema]
|
||||
club: ClubSchema
|
||||
product_type: ProductTypeSchema | None
|
||||
product_type: SimpleProductTypeSchema | None
|
||||
url: str
|
||||
|
||||
@staticmethod
|
||||
|
91
counter/tests/test_product_type.py
Normal file
91
counter/tests/test_product_type.py
Normal file
@ -0,0 +1,91 @@
|
||||
import pytest
|
||||
from django.conf import settings
|
||||
from django.test import Client
|
||||
from django.urls import reverse
|
||||
from model_bakery import baker, seq
|
||||
from ninja_extra.testing import TestClient
|
||||
|
||||
from core.baker_recipes import board_user, subscriber_user
|
||||
from core.models import RealGroup, User
|
||||
from counter.api import ProductTypeController
|
||||
from counter.models import ProductType
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def product_types(db) -> list[ProductType]:
|
||||
"""All existing product types, ordered by their `order` field"""
|
||||
# delete product types that have been created in the `populate` command
|
||||
ProductType.objects.all().delete()
|
||||
return baker.make(ProductType, _quantity=5, order=seq(0))
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_fetch_product_types(product_types: list[ProductType]):
|
||||
"""Test that the API returns the right products in the right order"""
|
||||
client = TestClient(ProductTypeController)
|
||||
response = client.get("")
|
||||
assert response.status_code == 200
|
||||
assert [i["id"] for i in response.json()] == [t.id for t in product_types]
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_move_below_product_type(product_types: list[ProductType]):
|
||||
"""Test that moving a product below another works"""
|
||||
client = TestClient(ProductTypeController)
|
||||
response = client.patch(
|
||||
f"/{product_types[-1].id}/move", query={"below": product_types[0].id}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
new_order = [i["id"] for i in client.get("").json()]
|
||||
assert new_order == [
|
||||
product_types[0].id,
|
||||
product_types[-1].id,
|
||||
*[t.id for t in product_types[1:-1]],
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_move_above_product_type(product_types: list[ProductType]):
|
||||
"""Test that moving a product above another works"""
|
||||
client = TestClient(ProductTypeController)
|
||||
response = client.patch(
|
||||
f"/{product_types[1].id}/move", query={"above": product_types[0].id}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
new_order = [i["id"] for i in client.get("").json()]
|
||||
assert new_order == [
|
||||
product_types[1].id,
|
||||
product_types[0].id,
|
||||
*[t.id for t in product_types[2:]],
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize(
|
||||
("user_factory", "status_code"),
|
||||
[
|
||||
(lambda: baker.make(User, is_superuser=True), 200),
|
||||
(subscriber_user.make, 403),
|
||||
(board_user.make, 403),
|
||||
(
|
||||
lambda: baker.make(
|
||||
User,
|
||||
groups=[RealGroup.objects.get(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID)],
|
||||
),
|
||||
200,
|
||||
),
|
||||
(
|
||||
lambda: baker.make(
|
||||
User,
|
||||
groups=[
|
||||
RealGroup.objects.get(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
|
||||
],
|
||||
),
|
||||
200,
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_controller_permissions(client: Client, user_factory, status_code):
|
||||
client.force_login(user_factory())
|
||||
response = client.get(reverse("api:fetch-product-types"))
|
||||
assert response.status_code == status_code
|
Loading…
Reference in New Issue
Block a user