mirror of
https://github.com/ae-utbm/sith.git
synced 2025-07-10 03:49:24 +00:00
@ -12,11 +12,23 @@
|
||||
# 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 counter.models import Counter, Product
|
||||
from counter.schemas import (
|
||||
CounterFilterSchema,
|
||||
CounterSchema,
|
||||
ProductSchema,
|
||||
SimplifiedCounterSchema,
|
||||
)
|
||||
|
||||
|
||||
@api_controller("/counter")
|
||||
@ -37,3 +49,30 @@ 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()
|
||||
)
|
||||
|
@ -1,10 +1,15 @@
|
||||
from ajax_select import make_ajax_field
|
||||
from ajax_select.fields import AutoCompleteSelectField, AutoCompleteSelectMultipleField
|
||||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from phonenumber_field.widgets import RegionalPhoneNumberWidget
|
||||
|
||||
from club.widgets.select import AutoCompleteSelectClub
|
||||
from core.views.forms import NFCTextInput, SelectDate, SelectDateTime
|
||||
from core.views.widgets.select import (
|
||||
AutoCompleteSelect,
|
||||
AutoCompleteSelectMultipleGroup,
|
||||
AutoCompleteSelectMultipleUser,
|
||||
AutoCompleteSelectUser,
|
||||
)
|
||||
from counter.models import (
|
||||
BillingInfo,
|
||||
Counter,
|
||||
@ -14,6 +19,11 @@ from counter.models import (
|
||||
Refilling,
|
||||
StudentCard,
|
||||
)
|
||||
from counter.widgets.select import (
|
||||
AutoCompleteSelectMultipleCounter,
|
||||
AutoCompleteSelectMultipleProduct,
|
||||
AutoCompleteSelectProduct,
|
||||
)
|
||||
|
||||
|
||||
class BillingInfoForm(forms.ModelForm):
|
||||
@ -68,8 +78,11 @@ class GetUserForm(forms.Form):
|
||||
required=False,
|
||||
widget=NFCTextInput,
|
||||
)
|
||||
id = AutoCompleteSelectField(
|
||||
"users", required=False, label=_("Select user"), help_text=None
|
||||
id = forms.CharField(
|
||||
label=_("Select user"),
|
||||
help_text=None,
|
||||
widget=AutoCompleteSelectUser,
|
||||
required=False,
|
||||
)
|
||||
|
||||
def as_p(self):
|
||||
@ -122,8 +135,10 @@ class CounterEditForm(forms.ModelForm):
|
||||
model = Counter
|
||||
fields = ["sellers", "products"]
|
||||
|
||||
sellers = make_ajax_field(Counter, "sellers", "users", help_text="")
|
||||
products = make_ajax_field(Counter, "products", "products", help_text="")
|
||||
widgets = {
|
||||
"sellers": AutoCompleteSelectMultipleUser,
|
||||
"products": AutoCompleteSelectMultipleProduct,
|
||||
}
|
||||
|
||||
|
||||
class ProductEditForm(forms.ModelForm):
|
||||
@ -145,44 +160,37 @@ class ProductEditForm(forms.ModelForm):
|
||||
"tray",
|
||||
"archived",
|
||||
]
|
||||
widgets = {
|
||||
"parent_product": AutoCompleteSelectMultipleProduct,
|
||||
"product_type": AutoCompleteSelect,
|
||||
"buying_groups": AutoCompleteSelectMultipleGroup,
|
||||
"club": AutoCompleteSelectClub,
|
||||
}
|
||||
|
||||
parent_product = AutoCompleteSelectField(
|
||||
"products", show_help_text=False, label=_("Parent product"), required=False
|
||||
)
|
||||
buying_groups = AutoCompleteSelectMultipleField(
|
||||
"groups",
|
||||
show_help_text=False,
|
||||
help_text="",
|
||||
label=_("Buying groups"),
|
||||
required=True,
|
||||
)
|
||||
club = AutoCompleteSelectField("clubs", show_help_text=False)
|
||||
counters = AutoCompleteSelectMultipleField(
|
||||
"counters",
|
||||
show_help_text=False,
|
||||
help_text="",
|
||||
counters = forms.ModelMultipleChoiceField(
|
||||
help_text=None,
|
||||
label=_("Counters"),
|
||||
required=False,
|
||||
widget=AutoCompleteSelectMultipleCounter,
|
||||
queryset=Counter.objects.all(),
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.instance.id:
|
||||
self.fields["counters"].initial = [
|
||||
str(c.id) for c in self.instance.counters.all()
|
||||
]
|
||||
self.fields["counters"].initial = self.instance.counters.all()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
ret = super().save(*args, **kwargs)
|
||||
if self.fields["counters"].initial:
|
||||
for cid in self.fields["counters"].initial:
|
||||
c = Counter.objects.filter(id=int(cid)).first()
|
||||
c.products.remove(self.instance)
|
||||
c.save()
|
||||
for cid in self.cleaned_data["counters"]:
|
||||
c = Counter.objects.filter(id=int(cid)).first()
|
||||
c.products.add(self.instance)
|
||||
c.save()
|
||||
# Remove the product from all counter it was added to
|
||||
# It will then only be added to selected counters
|
||||
for counter in self.fields["counters"].initial:
|
||||
counter.products.remove(self.instance)
|
||||
counter.save()
|
||||
for counter in self.cleaned_data["counters"]:
|
||||
counter.products.add(self.instance)
|
||||
counter.save()
|
||||
return ret
|
||||
|
||||
|
||||
@ -199,8 +207,7 @@ class EticketForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Eticket
|
||||
fields = ["product", "banner", "event_title", "event_date"]
|
||||
widgets = {"event_date": SelectDate}
|
||||
|
||||
product = AutoCompleteSelectField(
|
||||
"products", show_help_text=False, label=_("Product"), required=True
|
||||
)
|
||||
widgets = {
|
||||
"product": AutoCompleteSelectProduct,
|
||||
"event_date": SelectDate,
|
||||
}
|
||||
|
@ -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"]
|
||||
|
@ -0,0 +1,60 @@
|
||||
import { AjaxSelect } from "#core:core/components/ajax-select-base";
|
||||
import { registerComponent } from "#core:utils/web-components";
|
||||
import type { TomOption } from "tom-select/dist/types/types";
|
||||
import type { escape_html } from "tom-select/dist/types/utils";
|
||||
import {
|
||||
type CounterSchema,
|
||||
type ProductSchema,
|
||||
counterSearchCounter,
|
||||
productSearchProducts,
|
||||
} from "#openapi";
|
||||
|
||||
@registerComponent("product-ajax-select")
|
||||
export class ProductAjaxSelect extends AjaxSelect {
|
||||
protected valueField = "id";
|
||||
protected labelField = "name";
|
||||
protected searchField = ["code", "name"];
|
||||
|
||||
protected async search(query: string): Promise<TomOption[]> {
|
||||
const resp = await productSearchProducts({ query: { search: query } });
|
||||
if (resp.data) {
|
||||
return resp.data.results;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
protected renderOption(item: ProductSchema, sanitize: typeof escape_html) {
|
||||
return `<div class="select-item">
|
||||
<span class="select-item-text">${sanitize(item.code)} - ${sanitize(item.name)}</span>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
protected renderItem(item: ProductSchema, sanitize: typeof escape_html) {
|
||||
return `<span>${sanitize(item.code)} - ${sanitize(item.name)}</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
@registerComponent("counter-ajax-select")
|
||||
export class CounterAjaxSelect extends AjaxSelect {
|
||||
protected valueField = "id";
|
||||
protected labelField = "name";
|
||||
protected searchField = ["code", "name"];
|
||||
|
||||
protected async search(query: string): Promise<TomOption[]> {
|
||||
const resp = await counterSearchCounter({ query: { search: query } });
|
||||
if (resp.data) {
|
||||
return resp.data.results;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
protected renderOption(item: CounterSchema, sanitize: typeof escape_html) {
|
||||
return `<div class="select-item">
|
||||
<span class="select-item-text">${sanitize(item.name)}</span>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
protected renderItem(item: CounterSchema, sanitize: typeof escape_html) {
|
||||
return `<span>${sanitize(item.name)}</span>`;
|
||||
}
|
||||
}
|
35
counter/widgets/select.py
Normal file
35
counter/widgets/select.py
Normal file
@ -0,0 +1,35 @@
|
||||
from pydantic import TypeAdapter
|
||||
|
||||
from core.views.widgets.select import AutoCompleteSelect, AutoCompleteSelectMultiple
|
||||
from counter.models import Counter, Product
|
||||
from counter.schemas import ProductSchema, SimplifiedCounterSchema
|
||||
|
||||
_js = ["webpack/counter/components/ajax-select-index.ts"]
|
||||
|
||||
|
||||
class AutoCompleteSelectCounter(AutoCompleteSelect):
|
||||
component_name = "counter-ajax-select"
|
||||
model = Counter
|
||||
adapter = TypeAdapter(list[SimplifiedCounterSchema])
|
||||
js = _js
|
||||
|
||||
|
||||
class AutoCompleteSelectMultipleCounter(AutoCompleteSelectMultiple):
|
||||
component_name = "counter-ajax-select"
|
||||
model = Counter
|
||||
adapter = TypeAdapter(list[SimplifiedCounterSchema])
|
||||
js = _js
|
||||
|
||||
|
||||
class AutoCompleteSelectProduct(AutoCompleteSelect):
|
||||
component_name = "product-ajax-select"
|
||||
model = Product
|
||||
adapter = TypeAdapter(list[ProductSchema])
|
||||
js = _js
|
||||
|
||||
|
||||
class AutoCompleteSelectMultipleProduct(AutoCompleteSelectMultiple):
|
||||
component_name = "product-ajax-select"
|
||||
model = Product
|
||||
adapter = TypeAdapter(list[ProductSchema])
|
||||
js = _js
|
Reference in New Issue
Block a user