Skip to content

Commit

Permalink
added filters updated search
Browse files Browse the repository at this point in the history
- Updated search templates
- Added search filters
- Updated search View
- Added tests
  • Loading branch information
daniel-gray-tangent committed Jul 11, 2024
1 parent 35cce54 commit a1460cd
Show file tree
Hide file tree
Showing 6 changed files with 415 additions and 66 deletions.
32 changes: 31 additions & 1 deletion app/app/views.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import os

from django.contrib.postgres.search import SearchHeadline, SearchQuery, SearchRank
from django.core.paginator import Paginator
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
from django.db.models import Count
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, render
from django.utils.translation import gettext as _

from general.filters import DocumentFileFilter
from general.models import DocumentFile, Institution, Language, Project, Subject


Expand Down Expand Up @@ -266,3 +267,32 @@ def search(request):
}

return render(request, template_name=template, context=context)


def search(request):
page_number = request.GET.get("page", "1")
if not page_number.isdigit():
page_number = "1"

f = DocumentFileFilter(request.GET, queryset=DocumentFile.objects.all())

template = "app/search.html"

ordered_queryset = f.qs.order_by("id")

paginator = Paginator(ordered_queryset, 5) # 5 documents per page

try:
page_obj = paginator.page(page_number)
except PageNotAnInteger:
page_obj = paginator.page(1)
except EmptyPage:
page_obj = paginator.page(paginator.num_pages)

context = {
"search_results": paginator.page(page_obj.number),
"filter": f,
"documents": page_obj,
}

return render(request, template_name=template, context=context)
54 changes: 54 additions & 0 deletions app/general/filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import django_filters
from django import forms
from django.contrib.postgres.search import SearchHeadline, SearchQuery, SearchRank
from django.db.models import F
from django_filters import ModelMultipleChoiceFilter, MultipleChoiceFilter

from general.models import DocumentFile, Institution, Language, Subject


class DocumentFileFilter(django_filters.FilterSet):
search = django_filters.CharFilter(method="filter_search", label="Search")

institution = ModelMultipleChoiceFilter(
queryset=Institution.objects.all(), widget=forms.CheckboxSelectMultiple
)
document_type = MultipleChoiceFilter(
choices=DocumentFile.document_type_choices, widget=forms.CheckboxSelectMultiple
)
subjects = ModelMultipleChoiceFilter(
queryset=Subject.objects.all(), widget=forms.CheckboxSelectMultiple
)
languages = ModelMultipleChoiceFilter(
queryset=Language.objects.all(), widget=forms.CheckboxSelectMultiple
)

class Meta:
model = DocumentFile
fields = [
"document_type",
"institution",
"subjects",
"languages",
]

def filter_search(self, queryset, name, value):
if value:
queue = SearchQuery(value.strip())
search_rank = SearchRank(F("search_vector"), queue)
search_headline = SearchHeadline("document_data", queue)

queryset = (
queryset.annotate(
rank=search_rank,
search_headline=search_headline,
)
.defer("document_data")
.select_related("institution")
.filter(search_vector=queue)
.order_by("-rank", "id")
)
return queryset

else:
return queryset
91 changes: 91 additions & 0 deletions app/general/tests/test_filter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import unittest

from django.test import TestCase

from general.filters import DocumentFileFilter
from general.models import DocumentFile, Institution, Language, Subject


class TestSearchFilter(TestCase):
def setUp(self):
self.institution1 = Institution.objects.create(name="Institution 1")
self.institution2 = Institution.objects.create(name="Institution 2")

# Create languages
self.language1 = Language.objects.create(name="English", iso_code="EN")
self.language2 = Language.objects.create(name="Afrikaans", iso_code="AF")

# Create subjects
self.subject1 = Subject.objects.create(name="Science")
self.subject2 = Subject.objects.create(name="Math")

# Create DocumentFiles
self.doc1 = DocumentFile.objects.create(
title="Document 1",
document_data="Document 1 content",
institution=self.institution1,
document_type="glossary",
)
self.doc1.subjects.add(self.subject1)
self.doc1.languages.add(self.language1)

self.doc2 = DocumentFile.objects.create(
title="Document 2",
document_data="Document 2 content",
institution=self.institution2,
document_type="glossary",
)
self.doc2.subjects.add(self.subject2)
self.doc2.languages.add(self.language2)

def test_institution_filter(self):
data = {"institution": [self.institution1.id]}
filter = DocumentFileFilter(data=data)
self.assertEqual(len(filter.qs), 1)
self.assertIn(self.doc1, filter.qs)

def test_document_type_filter(self):
data = {"document_type": ["glossary"]}
filter = DocumentFileFilter(data=data)
self.assertEqual(len(filter.qs), 2)
self.assertIn(self.doc1, filter.qs)
self.assertIn(self.doc2, filter.qs)

def test_subjects_filter(self):
data = {"subjects": [self.subject1.id]}
filter = DocumentFileFilter(data=data)
self.assertEqual(len(filter.qs), 1)
self.assertIn(self.doc1, filter.qs)

def test_languages_filter(self):
data = {"languages": [self.language1.id]}
filter = DocumentFileFilter(data=data)
self.assertEqual(len(filter.qs), 1)
self.assertIn(self.doc1, filter.qs)

def test_combined_filters(self):
data = {
"institution": [self.institution1.id],
"document_type": ["glossary"],
"subjects": [self.subject1.id],
"languages": [self.language1.id],
}
filter = DocumentFileFilter(data=data)
self.assertEqual(len(filter.qs), 1)
self.assertIn(self.doc1, filter.qs)

def test_search_filter(self):
data = {"search": "Document"}
filter = DocumentFileFilter(data=data)
self.assertEqual(len(filter.qs), 2)
self.assertIn(self.doc1, filter.qs)
self.assertIn(self.doc2, filter.qs)

data = {"search": "Document 1"}
filter = DocumentFileFilter(data=data)
self.assertEqual(len(filter.qs), 1)
self.assertIn(self.doc1, filter.qs)


if __name__ == "__main__":
unittest.main()
75 changes: 75 additions & 0 deletions app/general/tests/test_view_search.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import unittest

from django.test import Client, TestCase
from django.urls import reverse

from general.models import DocumentFile, Institution, Language, Subject


class SearchViewTest(TestCase):
def setUp(self):
# Create institutions
self.institution1 = Institution.objects.create(name="Institution 1")
self.institution2 = Institution.objects.create(name="Institution 2")

# Create languages
self.language1 = Language.objects.create(name="English", iso_code="EN")
self.language2 = Language.objects.create(name="Afrikaans", iso_code="AF")

# Create subjects
self.subject1 = Subject.objects.create(name="Science")
self.subject2 = Subject.objects.create(name="Math")

# Create DocumentFiles
for i in range(10):
doc = DocumentFile.objects.create(
title=f"Document {i + 1}",
institution=self.institution1 if i % 2 == 0 else self.institution2,
document_type="report" if i % 2 == 0 else "article",
document_data="Document {i + 1} content",
)
doc.subjects.add(self.subject1 if i % 2 == 0 else self.subject2)
doc.languages.add(self.language1 if i % 2 == 0 else self.language2)

def test_search_pagination(self):
client = Client()
response = client.get(reverse("search"), {"page": "1"})
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.context["documents"]), 5)

response = client.get(reverse("search"), {"page": "2"})
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.context["documents"]), 5)

response = client.get(reverse("search"), {"page": "3"})
self.assertEqual(response.status_code, 200)

def test_search_filtering(self):
client = Client()
response = client.get(reverse("search"), {"search": "Document 1"})
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context["documents"][0].title, "Document 1")

def test_invalid_page_number(self):
client = Client()
response = client.get(reverse("search"), {"page": "invalid"})
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.context["documents"]), 5)

def test_combined_filters(self):
client = Client()
response = client.get(
reverse("search"),
{
"institution": self.institution1.id,
"document_type": "report",
"subjects": self.subject1.id,
"languages": self.language1.id,
},
)
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.context["documents"]), 5)


if __name__ == "__main__":
unittest.main()
13 changes: 13 additions & 0 deletions app/static/css/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -611,3 +611,16 @@ html {
display: flex;
}
}

/*Search Css*/
.subjects-checkbox-container {
cursor: pointer;
max-height: 130px; /* Adjust based on your line-height and padding to show only 4 rows */
overflow-y: auto; /* Enable vertical scrollbar when content overflows */
border: 1px solid #ced4da; /* Bootstrap's form control border color */
border-radius: 0.25rem; /* Bootstrap's form control border radius */
}

.subjects-checkbox-container input[type="checkbox"] {
cursor: pointer;
}
Loading

0 comments on commit a1460cd

Please sign in to comment.