Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tests for TypesenseSearchAdminMixin #78

72 changes: 41 additions & 31 deletions django_typesense/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@
from django.forms import forms
from django.http import JsonResponse

from django_typesense.mixins import TypesenseModelMixin
from django_typesense.utils import typesense_search, export_documents
from django_typesense.paginator import TypesenseSearchPaginator
from django_typesense.utils import typesense_search

logger = logging.getLogger(__name__)

Expand All @@ -17,9 +16,8 @@ class TypesenseSearchAdminMixin(admin.ModelAdmin):
typesense_search_fields = []

def get_typesense_search_fields(self, request):
"""
Return a sequence containing the fields to be searched whenever
somebody submits a search query.
"""Return a sequence containing the fields to
be searched wheneversomebody submits a search query.
"""
return self.typesense_search_fields

Expand All @@ -33,9 +31,7 @@ def media(self):

@csrf_protect_m
def changelist_view(self, request, extra_context=None):
"""
The 'change list' admin view for this model.
"""
"""The 'change list' admin view for this model."""
template_response = super().changelist_view(request, extra_context)

is_ajax = request.META.get("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest"
Expand All @@ -46,13 +42,16 @@ def changelist_view(self, request, extra_context=None):
return template_response

def get_sortable_by(self, request):
"""
Get sortable fields; these are fields that sort is defaulted or set to True.
"""Get sortable fields; these are fields that sort is defaulted or set to True.

Args:
request: the HttpRequest
Parameters
----------
request: HttpRequest
The current request object.

Returns:
Returns
-------
list
A list of field names
"""

Expand All @@ -62,13 +61,17 @@ def get_sortable_by(self, request):
)

def get_results(self, request):
"""
Get all indexed data without any filtering or specific search terms. Works like `ModelAdmin.get_queryset()`
"""Get all indexed data without any filtering or specific
search terms. Works like `ModelAdmin.get_queryset()`.

Args:
request: the HttpRequest
Parameters
----------
request: HttpRequest
The current request object.

Returns:
Returns
-------
list
A list of the typesense results
"""

Expand All @@ -79,9 +82,7 @@ def get_results(self, request):
)

def get_changelist(self, request, **kwargs):
"""
Return the ChangeList class for use on the changelist page.
"""
"""Return the ChangeList class for use on the changelist page."""
from django_typesense.changelist import TypesenseChangeList

return TypesenseChangeList
Expand All @@ -108,16 +109,25 @@ def get_typesense_search_results(
sort_by: str = "",
):
"""
Get the results from typesense with the provided filtering, sorting, pagination and search parameters applied

Args:
search_term: The search term provided in the search form
request: the current request object
page_num: The requested page number
filter_by: The filtering parameters
sort_by: The sort parameters

Returns:
Get the results from typesense with the provided filtering,
sorting, pagination and search parameters applied.

Parameters
----------
request: HttpRequest
The current request object.
search_term: str
The search term provided in the search form.
page_num: int
The requested page number.
filter_by: str
The filtering parameters.
sort_by: str
The sort parameters.

Returns
-------
list
A list of typesense results
"""

Expand Down
2 changes: 1 addition & 1 deletion django_typesense/changelist.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def __init__(
list_editable,
model_admin,
sortable_by,
search_help_text,
search_help_text="",
):
self.model = model
self.opts = model._meta
Expand Down
6 changes: 6 additions & 0 deletions tests/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.contrib import admin

from django_typesense.admin import TypesenseSearchAdminMixin
from tests.models import Song

admin.site.register(Song, TypesenseSearchAdminMixin)
1 change: 1 addition & 0 deletions tests/collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

class SongCollection(TypesenseCollection):
query_by_fields = "title,artist_names,genre_name"
sortable_fields = ["id", "title"]

title = fields.TypesenseCharField()
genre_name = fields.TypesenseCharField(value="genre.name")
Expand Down
2 changes: 2 additions & 0 deletions tests/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,5 @@
USE_TZ = True

DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

ROOT_URLCONF = "tests.urls"
104 changes: 104 additions & 0 deletions tests/test_typesense_admin_mixin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import json
from http import HTTPStatus

from django.contrib.admin.sites import AdminSite
from django.contrib.auth import get_user_model
from django.core.paginator import Paginator
from django.test import TestCase
from django.test.client import RequestFactory
from django.urls import reverse

from django_typesense.admin import TypesenseSearchAdminMixin, TypesenseSearchPaginator
from django_typesense.mixins import TypesenseQuerySet

from .factories import SongFactory
from .models import Song

User = get_user_model()


class TestTypesenseSearchAdminMixin(TestCase):
def setUp(self):
self.site = AdminSite()
self.factory = RequestFactory()
self.url = reverse(
f"admin:{Song._meta.app_label}_{Song._meta.model_name}_changelist"
)

self.songs_count = 10
SongFactory.create_batch(size=self.songs_count)

def _create_user(self, username, email, is_superuser=True):
user = User.objects.create(username=username, email=email, password="password")
if is_superuser:
user.is_staff = True
user.is_superuser = True
user.save(update_fields=["is_staff", "is_superuser"])

return user

def _create_mock_request(self, url, user, request_type="GET", headers={}, data={}):
if request_type == "GET":
request = self.factory.get(url, data, **headers)

if request_type == "POST":
request = self.factory.post(url, data, **headers)

request.user = user
return request

def test_model_admin_changelist_view(self):
model_admin = TypesenseSearchAdminMixin(Song, self.site)

admin_user = self._create_user("admin", "[email protected]")
request = self._create_mock_request(self.url, admin_user)

response = model_admin.changelist_view(request)
self.assertEqual(response.status_code, HTTPStatus.OK)
response.render()
self.assertIn(f"{self.songs_count} songs", str(response.content))

headers = {"HTTP_X_REQUESTED_WITH": "XMLHttpRequest"}
request = self._create_mock_request(self.url, admin_user, headers=headers)

response = model_admin.changelist_view(request)
self.assertEqual(response.status_code, HTTPStatus.OK)
response_data = json.loads(response.content)
self.assertCountEqual(response_data.keys(), ["html"])
self.assertIn(f"{self.songs_count} songs", response_data["html"])

def test_get_paginator(self):
model_admin = TypesenseSearchAdminMixin(Song, self.site)

admin_user = self._create_user("admin", "[email protected]")
request = self._create_mock_request(self.url, admin_user)

songs = Song.objects.all().order_by("pk")
response = model_admin.get_paginator(request, songs, self.songs_count)
self.assertFalse(isinstance(response, TypesenseSearchPaginator))
self.assertTrue(isinstance(response, Paginator))

def test_get_search_results(self):
model_admin = TypesenseSearchAdminMixin(Song, self.site)

admin_user = self._create_user("admin", "[email protected]")
request = self._create_mock_request(self.url, admin_user, request_type="POST")

songs = Song.objects.all().order_by("pk")
query_set, may_have_duplicates = model_admin.get_search_results(
request, songs, "*"
)
self.assertTrue(isinstance(query_set, TypesenseQuerySet))
self.assertEqual(query_set.count(), self.songs_count)
self.assertFalse(may_have_duplicates)

request = self._create_mock_request(
self.url, admin_user, request_type="POST", data={"action": "delete"}
)
query_set, may_have_duplicates = model_admin.get_search_results(
request, songs, "*"
)

self.assertTrue(isinstance(query_set, TypesenseQuerySet))
self.assertEqual(query_set.count(), self.songs_count)
self.assertFalse(may_have_duplicates)
4 changes: 4 additions & 0 deletions tests/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from django.contrib import admin
from django.urls import path

urlpatterns = [path("admin/", admin.site.urls)]
Loading