I hope you're well. I've created a global search with more than one model in different app.
Post models belongs to Nutriscore folder
UserProfile models belongs to User folder
If I use a word which cover Post and UserProfile search, for instance Main courses. I got some search result (3 results) for UserProfile but nothing appears for Post (whereas count
gives me 6 results). So I don't know what's wrong in code?
user/views.py
# global search
class GlobalSearchView(ListView):
template_name = 'search_global.html'
count = 0
countnutri = 0
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['count'] = self.count or 0
context['countnutri'] = self.countnutri or 0
context['query'] = self.request.GET.get('q')
return context
def get_queryset(self): # new
query = self.request.GET.get('q', None)
if query is not None:
nutriscore = Post.objects.filter(
Q(title__icontains=query) | Q(slug__icontains=query) | Q(typederepas__name__icontains=query) | Q(prixrepas__name__icontains=query) | Q(vitesserepas__name__icontains=query) | Q(force__name__icontains=query) | Q(bienfaitrepas__name__icontains=query)
).distinct()
user = UserProfile.objects.filter(
Q(pays__icontains=query) | Q(town__icontains=query) | Q(user__username__icontains=query) | Q(mealtrend__name__icontains=query) | Q(pricetrend__name__icontains=query) | Q(speedtrend__name__icontains=query)| Q(strengthtrend__name__icontains=query) | Q(wellnesstrend__name__icontains=query)
).distinct()
results = chain(nutriscore,user)
qs = sorted(user,
key=lambda instance: instance.pk,
reverse=True)
self.count = len(qs)
qn = sorted(nutriscore,
key=lambda instance: instance.pk,
reverse=True)
self.countnutri = len(qn)
return qs
return qn
return results
user/urls.py
path('search/', GlobalSearchView.as_view(),name="global_search"),
user/templates/global_search.html I got a menu with display / collapse function in css.
<div class="row" id="collapseNutriscore">
{% for object in object_list %}
{% with object|class_name as klass %}
{% if klass == 'Post' %}
{{ object.title }}
{% endif %}
{% endwith %}
{% endfor %}
</div>
<br>
<br>
<div class="row collapse" id="collapseCooker">
{% for object in object_list %}
{% with object|class_name as klass %}
{% if klass == 'UserProfile' %}
{{ object.user.username }}
{% endif %}
{% endwith %}
{% endfor %}
</div>
Disclaimer: This answer may not fit in the OP's views or models, but, it will work as-is with the examples.
For the sake of clarity, I am assuming we have two models, Musician
and Album
which are located in the sample
app.
# sample/models.py
from django.db import models
class Musician(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return f'{self.name}'
class Album(models.Model):
artist = models.ForeignKey(Musician, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
description = models.TextField()
def __str__(self):
return f'{self.name} : {self.artist}'
Then, we have to create a mixin class, for a better OOP experience as well as for the extensibility across multiple views.
#sample/mixins.py
from django.apps import apps
from django.db.models import Q
from functools import reduce
from operator import or_
class SearchMixin:
search_keyword_arg = 'q'
search_settings = {}
lookup_expr = 'icontains'
def get_search_term(self):
return self.request.GET.get(self.search_keyword_arg)
def build_search_query(self, model_ref, term):
return reduce(or_, [Q(**{f'{field}__{self.lookup_expr}': term}) for field in self.search_settings[model_ref]])
def get_search_results(self):
has_search_result = False
search_term = self.get_search_term()
if not search_term:
return {'has_search_result': has_search_result}
results = {}
for model_ref, fields in self.search_settings.items():
app_name, model_str = model_ref.split('.')
ModelKlass = apps.get_model(app_label=app_name, model_name=model_str)
qs = ModelKlass.objects.filter(self.build_search_query(model_ref, search_term))
results[model_ref.replace('.', '_').lower()] = qs
if has_search_result is False and qs.exists():
has_search_result = True
results['has_search_result'] = has_search_result
return results
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['search_result'] = self.get_search_results()
return context
This SearchMixin
class have the search functionality that we want. We can add this class to any Django view to get the result.
For that, we are inheriting the SearchMixin
clas to a ListView
as,
# sample/views.py
from django.views.generic import TemplateView
from sample.mixins import SearchMixin
class GlobalSearchView(SearchMixin, TemplateView):
template_name = 'sample/global_search.html'
search_settings = {
'sample.Musician': ['name'],
'sample.Album': ['name', 'description'],
}
Notes:
TemplateView
which is more suitable in this particular case.search_settings
which is used to determine the search fields.How the search_settings
attribute should be?
It must be a dict
(or dict
like object)
The key of the dict
object should be in the app_name.ModelClassName
format
value of the dict
must be an iterable of model fields
How does it look like in the template?
The SearchMixin
class adding the search results to a context variable named search_result
and which has another variable has_search_result
(search_result.has_search_result
) which can be used to check whether we have got "any match".
Also, each QuerySets are accessible by separate variables. The format of the variable is,
app_name<underscore><model_name_in_lower_case>
.
For example, for the sample.Musician
model, the search results can be obtained (if any) in template as, {{ search_result.sample_musician }}
# sample/templates/sample/global_search.html
{% if search_result.has_search_result %}
<strong>Musician result</strong><br>
{% for musician in search_result.sample_musician %}<br>
{{ musician.name }}
{% endfor %}
<br><br>
<strong>Album result</strong><br>
{% for album in search_result.sample_album %}
{{ album.name }} -- {{ album.description }}<br>
{% endfor %}
{% else %}
No match
{% endif %}
Now, connect the view in urls.py
and search the by using the query parameter as,
/foo-bar/global-search/?q=xx
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With