diff --git a/accounting/api.py b/accounting/api.py new file mode 100644 index 00000000..a16fb7ab --- /dev/null +++ b/accounting/api.py @@ -0,0 +1,23 @@ +from typing import Annotated + +from annotated_types import MinLen +from ninja_extra import ControllerBase, api_controller, paginate, route +from ninja_extra.pagination import PageNumberPaginationExtra +from ninja_extra.schemas import PaginatedResponseSchema + +from accounting.models import ClubAccount, Company +from accounting.schemas import ClubAccountSchema, CompanySchema +from core.api_permissions import CanAccessLookup + + +@api_controller("/lookup", permissions=[CanAccessLookup]) +class AccountingController(ControllerBase): + @route.get("/club-account", response=PaginatedResponseSchema[ClubAccountSchema]) + @paginate(PageNumberPaginationExtra, page_size=50) + def search_club_account(self, search: Annotated[str, MinLen(1)]): + return ClubAccount.objects.filter(name__icontains=search).values() + + @route.get("/company", response=PaginatedResponseSchema[CompanySchema]) + @paginate(PageNumberPaginationExtra, page_size=50) + def search_company(self, search: Annotated[str, MinLen(1)]): + return Company.objects.filter(name__icontains=search).values() diff --git a/accounting/schemas.py b/accounting/schemas.py new file mode 100644 index 00000000..3d9edbcc --- /dev/null +++ b/accounting/schemas.py @@ -0,0 +1,15 @@ +from ninja import ModelSchema + +from accounting.models import ClubAccount, Company + + +class ClubAccountSchema(ModelSchema): + class Meta: + model = ClubAccount + fields = ["id", "name"] + + +class CompanySchema(ModelSchema): + class Meta: + model = Company + fields = ["id", "name"] diff --git a/club/api.py b/club/api.py new file mode 100644 index 00000000..9a680154 --- /dev/null +++ b/club/api.py @@ -0,0 +1,22 @@ +from typing import Annotated + +from annotated_types import MinLen +from ninja_extra import ControllerBase, api_controller, paginate, route +from ninja_extra.pagination import PageNumberPaginationExtra +from ninja_extra.schemas import PaginatedResponseSchema + +from club.models import Club +from club.schemas import ClubSchema +from core.api_permissions import CanAccessLookup + + +@api_controller("/club") +class ClubController(ControllerBase): + @route.get( + "/search", + response=PaginatedResponseSchema[ClubSchema], + permissions=[CanAccessLookup], + ) + @paginate(PageNumberPaginationExtra, page_size=50) + def search_club(self, search: Annotated[str, MinLen(1)]): + return Club.objects.filter(name__icontains=search).values() diff --git a/club/schemas.py b/club/schemas.py new file mode 100644 index 00000000..cbd35988 --- /dev/null +++ b/club/schemas.py @@ -0,0 +1,9 @@ +from ninja import ModelSchema + +from club.models import Club + + +class ClubSchema(ModelSchema): + class Meta: + model = Club + fields = ["id", "name"] diff --git a/core/api.py b/core/api.py index 7df689bc..b8f22a3e 100644 --- a/core/api.py +++ b/core/api.py @@ -11,11 +11,15 @@ from ninja_extra.pagination import PageNumberPaginationExtra from ninja_extra.schemas import PaginatedResponseSchema from club.models import Mailing -from core.api_permissions import CanView, IsLoggedInCounter, IsOldSubscriber, IsRoot -from core.models import User +from core.api_permissions import ( + CanAccessLookup, + CanView, +) +from core.models import SithFile, User from core.schemas import ( FamilyGodfatherSchema, MarkdownSchema, + SithFileSchema, UserFamilySchema, UserFilterSchema, UserProfileSchema, @@ -44,7 +48,7 @@ class MailingListController(ControllerBase): return data -@api_controller("/user", permissions=[IsOldSubscriber | IsRoot | IsLoggedInCounter]) +@api_controller("/user", permissions=[CanAccessLookup]) class UserController(ControllerBase): @route.get("", response=list[UserProfileSchema]) def fetch_profiles(self, pks: Query[set[int]]): @@ -62,6 +66,18 @@ class UserController(ControllerBase): ) +@api_controller("/file") +class SithFileController(ControllerBase): + @route.get( + "/search", + response=PaginatedResponseSchema[SithFileSchema], + permissions=[CanAccessLookup], + ) + @paginate(PageNumberPaginationExtra, page_size=50) + def search_files(self, query: Annotated[str, annotated_types.MinLen(1)]): + return SithFile.objects.filter(is_in_sas=False).filter(name__icontains=query) + + DepthValue = Annotated[int, annotated_types.Ge(0), annotated_types.Le(10)] DEFAULT_DEPTH = 4 diff --git a/core/api_permissions.py b/core/api_permissions.py index 9ef26164..f4da67af 100644 --- a/core/api_permissions.py +++ b/core/api_permissions.py @@ -127,9 +127,12 @@ class IsLoggedInCounter(BasePermission): """Check that a user is logged in a counter.""" def has_permission(self, request: HttpRequest, controller: ControllerBase) -> bool: - if "/counter/" not in request.META["HTTP_REFERER"]: + if "/counter/" not in request.META.get("HTTP_REFERER", ""): return False token = request.session.get("counter_token") if not token: return False return Counter.objects.filter(token=token).exists() + + +CanAccessLookup = IsOldSubscriber | IsRoot | IsLoggedInCounter diff --git a/core/lookups.py b/core/lookups.py index 93d15df8..fc650efa 100644 --- a/core/lookups.py +++ b/core/lookups.py @@ -30,7 +30,7 @@ class RightManagedLookupChannel(LookupChannel): raise PermissionDenied -@register("users") +@register("users") # Migrated class UsersLookup(RightManagedLookupChannel): model = User @@ -44,7 +44,7 @@ class UsersLookup(RightManagedLookupChannel): return item.get_display_name() -@register("customers") +@register("customers") # Never used class CustomerLookup(RightManagedLookupChannel): model = Customer @@ -58,7 +58,7 @@ class CustomerLookup(RightManagedLookupChannel): return f"{obj.user.get_display_name()} ({obj.account_id})" -@register("groups") +@register("groups") # Migrated class GroupsLookup(RightManagedLookupChannel): model = Group @@ -72,7 +72,7 @@ class GroupsLookup(RightManagedLookupChannel): return item.name -@register("clubs") +@register("clubs") # Migrated class ClubLookup(RightManagedLookupChannel): model = Club @@ -86,7 +86,7 @@ class ClubLookup(RightManagedLookupChannel): return item.name -@register("counters") +@register("counters") # Migrated class CountersLookup(RightManagedLookupChannel): model = Counter @@ -97,7 +97,7 @@ class CountersLookup(RightManagedLookupChannel): return item.name -@register("products") +@register("products") # Migrated class ProductsLookup(RightManagedLookupChannel): model = Product @@ -111,7 +111,7 @@ class ProductsLookup(RightManagedLookupChannel): return "%s (%s)" % (item.name, item.code) -@register("files") +@register("files") # Migrated class SithFileLookup(RightManagedLookupChannel): model = SithFile @@ -119,7 +119,7 @@ class SithFileLookup(RightManagedLookupChannel): return self.model.objects.filter(name__icontains=q)[:50] -@register("club_accounts") +@register("club_accounts") # Migrated class ClubAccountLookup(RightManagedLookupChannel): model = ClubAccount @@ -130,7 +130,7 @@ class ClubAccountLookup(RightManagedLookupChannel): return item.name -@register("companies") +@register("companies") # Migrated class CompaniesLookup(RightManagedLookupChannel): model = Company diff --git a/core/schemas.py b/core/schemas.py index 386a326f..d5c46ea7 100644 --- a/core/schemas.py +++ b/core/schemas.py @@ -8,7 +8,7 @@ from haystack.query import SearchQuerySet from ninja import FilterSchema, ModelSchema, Schema from pydantic import AliasChoices, Field -from core.models import User +from core.models import Group, SithFile, User class SimpleUserSchema(ModelSchema): @@ -45,6 +45,18 @@ class UserProfileSchema(ModelSchema): return obj.profile_pict.get_download_url() +class SithFileSchema(ModelSchema): + class Meta: + model = SithFile + fields = ["id", "name"] + + +class GroupSchema(ModelSchema): + class Meta: + model = Group + fields = ["id", "name"] + + class UserFilterSchema(FilterSchema): search: Annotated[str, MinLen(1)] exclude: list[int] | None = Field( diff --git a/counter/api.py b/counter/api.py index 834852d4..851fdb24 100644 --- a/counter/api.py +++ b/counter/api.py @@ -12,11 +12,25 @@ # OR WITHIN THE LOCAL FILE "LICENSE" # # -from ninja_extra import ControllerBase, api_controller, route +from typing import Annotated -from core.api_permissions import CanView, IsRoot -from counter.models import Counter -from counter.schemas import CounterSchema +from annotated_types import MinLen +from django.db.models import Q +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, IsRoot +from core.models import Group +from core.schemas import GroupSchema +from counter.models import Counter, Product +from counter.schemas import ( + CounterFilterSchema, + CounterSchema, + ProductSchema, + SimplifiedCounterSchema, +) @api_controller("/counter") @@ -37,3 +51,42 @@ class CounterController(ControllerBase): for c in counters: self.check_object_permissions(c) return counters + + @route.get( + "/search", + response=PaginatedResponseSchema[SimplifiedCounterSchema], + permissions=[CanAccessLookup], + ) + @paginate(PageNumberPaginationExtra, page_size=50) + def search_counter(self, filters: Query[CounterFilterSchema]): + return filters.filter(Counter.objects.all()) + + +@api_controller("/product") +class ProductController(ControllerBase): + @route.get( + "/search", + response=PaginatedResponseSchema[ProductSchema], + permissions=[CanAccessLookup], + ) + @paginate(PageNumberPaginationExtra, page_size=50) + def search_products(self, search: Annotated[str, MinLen(1)]): + return ( + Product.objects.filter( + Q(name__icontains=search) | Q(code__icontains=search) + ) + .filter(archived=False) + .values() + ) + + +@api_controller("/group") +class GroupController(ControllerBase): + @route.get( + "/search", + response=PaginatedResponseSchema[GroupSchema], + permissions=[CanAccessLookup], + ) + @paginate(PageNumberPaginationExtra, page_size=50) + def search_group(self, search: Annotated[str, MinLen(1)]): + return Group.objects.filter(name__icontains=search).values() diff --git a/counter/schemas.py b/counter/schemas.py index afe2455d..ec1a842d 100644 --- a/counter/schemas.py +++ b/counter/schemas.py @@ -1,7 +1,10 @@ -from ninja import ModelSchema +from typing import Annotated + +from annotated_types import MinLen +from ninja import Field, FilterSchema, ModelSchema from core.schemas import SimpleUserSchema -from counter.models import Counter +from counter.models import Counter, Product class CounterSchema(ModelSchema): @@ -11,3 +14,19 @@ class CounterSchema(ModelSchema): class Meta: model = Counter fields = ["id", "name", "type", "club", "products"] + + +class CounterFilterSchema(FilterSchema): + search: Annotated[str, MinLen(1)] = Field(None, q="name__icontains") + + +class SimplifiedCounterSchema(ModelSchema): + class Meta: + model = Counter + fields = ["id", "name"] + + +class ProductSchema(ModelSchema): + class Meta: + model = Product + fields = ["id", "name", "code"]