mirror of
https://github.com/ae-utbm/sith.git
synced 2025-07-10 03:49:24 +00:00
Use select2 for user picture identification
This commit is contained in:
21
core/api.py
21
core/api.py
@ -3,16 +3,21 @@ from typing import Annotated
|
||||
import annotated_types
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponse
|
||||
from ninja_extra import ControllerBase, api_controller, route
|
||||
from ninja import Query
|
||||
from ninja_extra import ControllerBase, api_controller, paginate, route
|
||||
from ninja_extra.exceptions import PermissionDenied
|
||||
from ninja_extra.pagination import PageNumberPaginationExtra
|
||||
from ninja_extra.schemas import PaginatedResponseSchema
|
||||
|
||||
from club.models import Mailing
|
||||
from core.api_permissions import CanView
|
||||
from core.api_permissions import CanView, IsLoggedInCounter, IsOldSubscriber, IsRoot
|
||||
from core.models import User
|
||||
from core.schemas import (
|
||||
FamilyGodfatherSchema,
|
||||
MarkdownSchema,
|
||||
UserFamilySchema,
|
||||
UserFilterSchema,
|
||||
UserProfileSchema,
|
||||
)
|
||||
from core.templatetags.renderer import markdown
|
||||
|
||||
@ -38,6 +43,18 @@ class MailingListController(ControllerBase):
|
||||
return data
|
||||
|
||||
|
||||
@api_controller("/user", permissions=[IsOldSubscriber | IsRoot | IsLoggedInCounter])
|
||||
class UserController(ControllerBase):
|
||||
@route.get("", response=list[UserProfileSchema])
|
||||
def fetch_profiles(self, pks: Query[set[int]]):
|
||||
return User.objects.filter(pk__in=pks)
|
||||
|
||||
@route.get("/search", response=PaginatedResponseSchema[UserProfileSchema])
|
||||
@paginate(PageNumberPaginationExtra, page_size=20)
|
||||
def search_users(self, filters: Query[UserFilterSchema]):
|
||||
return filters.filter(User.objects.all())
|
||||
|
||||
|
||||
DepthValue = Annotated[int, annotated_types.Ge(0), annotated_types.Le(10)]
|
||||
DEFAULT_DEPTH = 4
|
||||
|
||||
|
@ -42,6 +42,8 @@ from django.http import HttpRequest
|
||||
from ninja_extra import ControllerBase
|
||||
from ninja_extra.permissions import BasePermission
|
||||
|
||||
from counter.models import Counter
|
||||
|
||||
|
||||
class IsInGroup(BasePermission):
|
||||
"""Check that the user is in the group whose primary key is given."""
|
||||
@ -120,3 +122,15 @@ class IsOwner(BasePermission):
|
||||
self, request: HttpRequest, controller: ControllerBase, obj: Any
|
||||
) -> bool:
|
||||
return request.user.is_owner(obj)
|
||||
|
||||
|
||||
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"]:
|
||||
return False
|
||||
token = request.session.get("counter_token")
|
||||
if not token:
|
||||
return False
|
||||
return Counter.objects.filter(token=token).exists()
|
||||
|
@ -49,8 +49,7 @@ class CustomerLookup(RightManagedLookupChannel):
|
||||
model = Customer
|
||||
|
||||
def get_query(self, q, request):
|
||||
users = search_user(q)
|
||||
return [user.customer for user in users]
|
||||
return list(Customer.objects.filter(user__in=search_user(q)))
|
||||
|
||||
def format_match(self, obj):
|
||||
return obj.user.get_mini_item()
|
||||
|
@ -1,5 +1,12 @@
|
||||
from typing import Annotated
|
||||
|
||||
from annotated_types import MinLen
|
||||
from django.contrib.staticfiles.storage import staticfiles_storage
|
||||
from ninja import ModelSchema, Schema
|
||||
from django.db.models import Q
|
||||
from django.utils.text import slugify
|
||||
from haystack.query import SearchQuerySet
|
||||
from ninja import FilterSchema, ModelSchema, Schema
|
||||
from pydantic import AliasChoices, Field
|
||||
|
||||
from core.models import User
|
||||
|
||||
@ -12,10 +19,6 @@ class SimpleUserSchema(ModelSchema):
|
||||
fields = ["id", "nick_name", "first_name", "last_name"]
|
||||
|
||||
|
||||
class MarkdownSchema(Schema):
|
||||
text: str
|
||||
|
||||
|
||||
class UserProfileSchema(ModelSchema):
|
||||
"""The necessary information to show a user profile"""
|
||||
|
||||
@ -42,6 +45,42 @@ class UserProfileSchema(ModelSchema):
|
||||
return obj.profile_pict.get_download_url()
|
||||
|
||||
|
||||
class UserFilterSchema(FilterSchema):
|
||||
search: Annotated[str, MinLen(1)]
|
||||
exclude: list[int] | None = Field(
|
||||
None, validation_alias=AliasChoices("exclude", "exclude[]")
|
||||
)
|
||||
|
||||
def filter_search(self, value: str | None) -> Q:
|
||||
if not value:
|
||||
return Q()
|
||||
if len(value) < 4:
|
||||
# For small queries, full text search isn't necessary
|
||||
return (
|
||||
Q(first_name__istartswith=value)
|
||||
| Q(last_name__istartswith=value)
|
||||
| Q(nick_name__istartswith=value)
|
||||
)
|
||||
return Q(
|
||||
id__in=list(
|
||||
SearchQuerySet()
|
||||
.models(User)
|
||||
.autocomplete(auto=slugify(value).replace("-", " "))
|
||||
.order_by("-last_update")
|
||||
.values_list("pk", flat=True)
|
||||
)
|
||||
)
|
||||
|
||||
def filter_exclude(self, value: set[int] | None) -> Q:
|
||||
if not value:
|
||||
return Q()
|
||||
return ~Q(id__in=value)
|
||||
|
||||
|
||||
class MarkdownSchema(Schema):
|
||||
text: str
|
||||
|
||||
|
||||
class FamilyGodfatherSchema(Schema):
|
||||
godfather: int
|
||||
godchild: int
|
||||
|
@ -227,7 +227,6 @@ function remote_data_source(source, options) {
|
||||
if (!!options.overrides) {
|
||||
Object.assign(params, options.overrides);
|
||||
}
|
||||
console.log(params);
|
||||
return { ajax: params };
|
||||
}
|
||||
|
||||
|
@ -616,36 +616,36 @@ a:not(.button) {
|
||||
}
|
||||
|
||||
|
||||
.select2 {
|
||||
.select2 {
|
||||
margin: 0;
|
||||
max-width: 100%;
|
||||
min-width: 100%;
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
max-width: 100%;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
}
|
||||
textarea {
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
textarea {
|
||||
background-color: inherit;
|
||||
}
|
||||
.select2-container--default {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
.select2-results {
|
||||
.select-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
|
||||
.select2-container--default {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
.select2-results {
|
||||
.select-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
max-height: 40px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
img {
|
||||
max-height: 40px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#news_details {
|
||||
display: inline-block;
|
||||
|
@ -11,6 +11,7 @@
|
||||
<link rel="stylesheet" href="{{ scss('core/markdown.scss') }}">
|
||||
<link rel="stylesheet" href="{{ scss('core/header.scss') }}">
|
||||
<link rel="stylesheet" href="{{ scss('core/navbar.scss') }}">
|
||||
<link rel="stylesheet" href="{{ static('core/select2/select2.min.css') }}">
|
||||
|
||||
{% block jquery_css %}
|
||||
{# Thile file is quite heavy (around 250kb), so declaring it in a block allows easy removal #}
|
||||
@ -24,6 +25,9 @@
|
||||
<script src="{{ static('vendored/jquery/jquery-3.6.2.min.js') }}"></script>
|
||||
<!-- Put here to always have acces to those functions on django widgets -->
|
||||
<script src="{{ static('core/js/script.js') }}"></script>
|
||||
<script defer src="{{ static('core/select2/select2.min.js') }}"></script>
|
||||
<script defer src="{{ static('core/js/sith-select2.js') }}"></script>
|
||||
|
||||
|
||||
{% block additional_css %}{% endblock %}
|
||||
{% block additional_js %}{% endblock %}
|
||||
|
Reference in New Issue
Block a user