mirror of
https://github.com/ae-utbm/sith.git
synced 2025-02-25 17:07:13 +00:00
api to fetch albums
This commit is contained in:
parent
feeff5970d
commit
cfefd9bd5b
28
sas/api.py
28
sas/api.py
@ -1,6 +1,3 @@
|
||||
from typing import Annotated
|
||||
|
||||
from annotated_types import MinLen
|
||||
from django.conf import settings
|
||||
from django.db.models import F
|
||||
from django.urls import reverse
|
||||
@ -16,6 +13,8 @@ from core.auth.api_permissions import CanAccessLookup, CanView, IsInGroup, IsRoo
|
||||
from core.models import Notification, User
|
||||
from sas.models import Album, PeoplePictureRelation, Picture
|
||||
from sas.schemas import (
|
||||
AlbumAutocompleteSchema,
|
||||
AlbumFilterSchema,
|
||||
AlbumSchema,
|
||||
IdentifiedUserSchema,
|
||||
ModerationRequestSchema,
|
||||
@ -31,11 +30,30 @@ class AlbumController(ControllerBase):
|
||||
@route.get(
|
||||
"/search",
|
||||
response=PaginatedResponseSchema[AlbumSchema],
|
||||
permissions=[IsAuthenticated],
|
||||
url_name="search-album",
|
||||
)
|
||||
@paginate(PageNumberPaginationExtra, page_size=50)
|
||||
def fetch_album(self, filters: Query[AlbumFilterSchema]):
|
||||
"""General-purpose album search."""
|
||||
return filters.filter(Album.objects.viewable_by(self.context.request.user))
|
||||
|
||||
@route.get(
|
||||
"/autocomplete-search",
|
||||
response=PaginatedResponseSchema[AlbumAutocompleteSchema],
|
||||
permissions=[CanAccessLookup],
|
||||
)
|
||||
@paginate(PageNumberPaginationExtra, page_size=50)
|
||||
def search_album(self, search: Annotated[str, MinLen(1)]):
|
||||
return Album.objects.filter(name__icontains=search)
|
||||
def autocomplete_album(self, filters: Query[AlbumFilterSchema]):
|
||||
"""Search route to use exclusively on autocomplete input fields.
|
||||
|
||||
This route is separated from `GET /sas/album/search` because
|
||||
getting the path of an album may need an absurd amount of db queries.
|
||||
|
||||
If you don't need the path of the albums,
|
||||
do NOT use this route.
|
||||
"""
|
||||
return filters.filter(Album.objects.viewable_by(self.context.request.user))
|
||||
|
||||
|
||||
@api_controller("/sas/picture")
|
||||
|
@ -1,6 +1,8 @@
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Annotated
|
||||
|
||||
from annotated_types import MinLen
|
||||
from django.urls import reverse
|
||||
from ninja import FilterSchema, ModelSchema, Schema
|
||||
from pydantic import Field, NonNegativeInt
|
||||
@ -9,7 +11,37 @@ from core.schemas import SimpleUserSchema, UserProfileSchema
|
||||
from sas.models import Album, Picture, PictureModerationRequest
|
||||
|
||||
|
||||
class AlbumFilterSchema(FilterSchema):
|
||||
search: Annotated[str, MinLen(1)] | None = Field(None, q="name__icontains")
|
||||
before_date: datetime | None = Field(None, q="event_date__lte")
|
||||
after_date: datetime | None = Field(None, q="event_date__gte")
|
||||
parent_id: int | None = Field(None, q="parent_id")
|
||||
|
||||
|
||||
class AlbumSchema(ModelSchema):
|
||||
class Meta:
|
||||
model = Album
|
||||
fields = ["id", "name", "is_moderated"]
|
||||
|
||||
thumbnail: str | None
|
||||
sas_url: str
|
||||
|
||||
@staticmethod
|
||||
def resolve_thumbnail(obj: Album) -> str | None:
|
||||
# Album thumbnails aren't stored in `Album.thumbnail` but in `Album.file`
|
||||
# Don't ask me why.
|
||||
if not obj.file:
|
||||
return None
|
||||
return obj.get_download_url()
|
||||
|
||||
@staticmethod
|
||||
def resolve_sas_url(obj: Album) -> str:
|
||||
return obj.get_absolute_url()
|
||||
|
||||
|
||||
class AlbumAutocompleteSchema(ModelSchema):
|
||||
"""Schema to use on album autocomplete input field."""
|
||||
|
||||
class Meta:
|
||||
model = Album
|
||||
fields = ["id", "name"]
|
||||
|
@ -2,7 +2,7 @@ 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 AlbumSchema, albumSearchAlbum } from "#openapi";
|
||||
import { type AlbumAutocompleteSchema, albumSearchAlbum } from "#openapi";
|
||||
|
||||
@registerComponent("album-ajax-select")
|
||||
export class AlbumAjaxSelect extends AjaxSelect {
|
||||
@ -18,13 +18,13 @@ export class AlbumAjaxSelect extends AjaxSelect {
|
||||
return [];
|
||||
}
|
||||
|
||||
protected renderOption(item: AlbumSchema, sanitize: typeof escape_html) {
|
||||
protected renderOption(item: AlbumAutocompleteSchema, sanitize: typeof escape_html) {
|
||||
return `<div class="select-item">
|
||||
<span class="select-item-text">${sanitize(item.path)}</span>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
protected renderItem(item: AlbumSchema, sanitize: typeof escape_html) {
|
||||
protected renderItem(item: AlbumAutocompleteSchema, sanitize: typeof escape_html) {
|
||||
return `<span>${sanitize(item.path)}</span>`;
|
||||
}
|
||||
}
|
||||
|
@ -228,3 +228,16 @@ class TestPictureModeration(TestSas):
|
||||
assert res.status_code == 200
|
||||
assert len(res.json()) == 1
|
||||
assert res.json()[0]["author"]["id"] == self.user_a.id
|
||||
|
||||
|
||||
class TestAlbumSearch(TestSas):
|
||||
def test_num_queries(self):
|
||||
"""Check the number of queries is stable"""
|
||||
self.client.force_login(subscriber_user.make())
|
||||
cache.clear()
|
||||
with self.assertNumQueries(7):
|
||||
# - 2 for authentication
|
||||
# - 3 to check permissions
|
||||
# - 1 for pagination
|
||||
# - 1 for the actual results
|
||||
self.client.get(reverse("api:search-album"))
|
||||
|
@ -2,7 +2,7 @@ from pydantic import TypeAdapter
|
||||
|
||||
from core.views.widgets.select import AutoCompleteSelect, AutoCompleteSelectMultiple
|
||||
from sas.models import Album
|
||||
from sas.schemas import AlbumSchema
|
||||
from sas.schemas import AlbumAutocompleteSchema
|
||||
|
||||
_js = ["bundled/sas/components/ajax-select-index.ts"]
|
||||
|
||||
@ -10,7 +10,7 @@ _js = ["bundled/sas/components/ajax-select-index.ts"]
|
||||
class AutoCompleteSelectAlbum(AutoCompleteSelect):
|
||||
component_name = "album-ajax-select"
|
||||
model = Album
|
||||
adapter = TypeAdapter(list[AlbumSchema])
|
||||
adapter = TypeAdapter(list[AlbumAutocompleteSchema])
|
||||
|
||||
js = _js
|
||||
|
||||
@ -18,6 +18,6 @@ class AutoCompleteSelectAlbum(AutoCompleteSelect):
|
||||
class AutoCompleteSelectMultipleAlbum(AutoCompleteSelectMultiple):
|
||||
component_name = "album-ajax-select"
|
||||
model = Album
|
||||
adapter = TypeAdapter(list[AlbumSchema])
|
||||
adapter = TypeAdapter(list[AlbumAutocompleteSchema])
|
||||
|
||||
js = _js
|
||||
|
Loading…
x
Reference in New Issue
Block a user