Merge pull request #839 from ae-utbm/user-ordering

User ordering
This commit is contained in:
thomas girod 2024-09-25 17:51:25 +02:00 committed by GitHub
commit bbcc7ffeaa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 95 additions and 4 deletions

View File

@ -2,6 +2,7 @@ from typing import Annotated
import annotated_types import annotated_types
from django.conf import settings from django.conf import settings
from django.db.models import F
from django.http import HttpResponse from django.http import HttpResponse
from ninja import Query from ninja import Query
from ninja_extra import ControllerBase, api_controller, paginate, route from ninja_extra import ControllerBase, api_controller, paginate, route
@ -49,10 +50,16 @@ class UserController(ControllerBase):
def fetch_profiles(self, pks: Query[set[int]]): def fetch_profiles(self, pks: Query[set[int]]):
return User.objects.filter(pk__in=pks) return User.objects.filter(pk__in=pks)
@route.get("/search", response=PaginatedResponseSchema[UserProfileSchema]) @route.get(
"/search",
response=PaginatedResponseSchema[UserProfileSchema],
url_name="search_users",
)
@paginate(PageNumberPaginationExtra, page_size=20) @paginate(PageNumberPaginationExtra, page_size=20)
def search_users(self, filters: Query[UserFilterSchema]): def search_users(self, filters: Query[UserFilterSchema]):
return filters.filter(User.objects.all()) return filters.filter(
User.objects.order_by(F("last_login").desc(nulls_last=True))
)
DepthValue = Annotated[int, annotated_types.Ge(0), annotated_types.Le(10)] DepthValue = Annotated[int, annotated_types.Ge(0), annotated_types.Le(10)]

View File

@ -66,7 +66,6 @@ class UserFilterSchema(FilterSchema):
SearchQuerySet() SearchQuerySet()
.models(User) .models(User)
.autocomplete(auto=slugify(value).replace("-", " ")) .autocomplete(auto=slugify(value).replace("-", " "))
.order_by("-last_update")
.values_list("pk", flat=True) .values_list("pk", flat=True)
) )
) )

85
core/tests/test_user.py Normal file
View File

@ -0,0 +1,85 @@
from datetime import timedelta
from django.core.management import call_command
from django.test import TestCase
from django.urls import reverse
from django.utils.timezone import now
from model_bakery import baker, seq
from model_bakery.recipe import Recipe
from core.baker_recipes import subscriber_user
from core.models import User
class TestSearchUsers(TestCase):
@classmethod
def setUpTestData(cls):
User.objects.all().delete()
user_recipe = Recipe(
User,
first_name=seq("First", suffix="Name"),
last_name=seq("Last", suffix="Name"),
nick_name=seq("Nick", suffix="Name"),
)
cls.users = [
user_recipe.make(last_login=None),
*user_recipe.make(
last_login=seq(now() - timedelta(days=30), timedelta(days=1)),
_quantity=10,
_bulk_create=True,
),
]
call_command("update_index", "core", "--remove")
@classmethod
def tearDownClass(cls):
super().tearDownClass()
# restore the index
call_command("update_index", "core", "--remove")
def test_order(self):
"""Test that users are ordered by last login date."""
self.client.force_login(subscriber_user.make())
response = self.client.get(reverse("api:search_users") + "?search=First")
assert response.status_code == 200
assert response.json()["count"] == 11
# The users are ordered by last login date, so we need to reverse the list
assert [r["id"] for r in response.json()["results"]] == [
u.id for u in self.users[::-1]
]
def test_search_case_insensitive(self):
"""Test that the search is case insensitive."""
self.client.force_login(subscriber_user.make())
expected = [u.id for u in self.users[::-1]]
for term in ["first", "First", "FIRST"]:
response = self.client.get(reverse("api:search_users") + f"?search={term}")
assert response.status_code == 200
assert response.json()["count"] == 11
assert [r["id"] for r in response.json()["results"]] == expected
def test_search_nick_name(self):
"""Test that the search can be done on the nick name."""
self.client.force_login(subscriber_user.make())
# this should return users with nicknames Nick11, Nick10 and Nick1
response = self.client.get(reverse("api:search_users") + "?search=Nick1")
assert response.status_code == 200
assert [r["id"] for r in response.json()["results"]] == [
self.users[10].id,
self.users[9].id,
self.users[0].id,
]
def test_search_special_characters(self):
"""Test that the search can be done on special characters."""
belix = baker.make(User, nick_name="Bélix")
call_command("update_index", "core")
self.client.force_login(subscriber_user.make())
# this should return users with first names First1 and First10
response = self.client.get(reverse("api:search_users") + "?search=bél")
assert response.status_code == 200
assert [r["id"] for r in response.json()["results"]] == [belix.id]

View File

@ -85,7 +85,7 @@ def search_user(query):
SearchQuerySet() SearchQuerySet()
.models(User) .models(User)
.autocomplete(auto=query) .autocomplete(auto=query)
.order_by("-last_update")[:20] .order_by("-last_login")[:20]
) )
return [r.object for r in res] return [r.object for r in res]
except TypeError: except TypeError: