Apply Multi attribute search in Django - django

I am trying to implement Search in my list view for multiple attributes.
I don't want to use multiple if-else for each attribute.
Here is my current code for search in list view:
def get_queryset(self):
city = self.request.GET.get('city_name') or ''
user = self.request.GET.get('user_name') or ''
if (city != '' or user!=''):
userqueries = user.split()
cityqueries = city.split()
if len(userqueries) and len(cityqueries):
qset1 = functools.reduce(operator.__or__, [
Q(first_name__icontains=query) | Q(last_name__icontains=query) for query in userqueries])
qset2 = functools.reduce(operator.__or__, [Q(city__name__icontains=query) for query in cityqueries])
object_list = self.model.objects.filter(qset1 , qset2)
elif len(userqueries):
qset1 = functools.reduce(operator.__or__, [
Q(first_name__icontains=query) | Q(last_name__icontains=query) for query in userqueries])
object_list = self.model.objects.filter(qset1)
elif len(cityqueries):
qset1 = functools.reduce(operator.__or__, [Q(city__name__icontains=query) for query in cityqueries])
object_list = self.model.objects.filter(qset1)
else:
object_list = self.model.objects.all()
return object_list
If I will add one attribute:
city = self.request.GET.get('city_name') or ''
user = self.request.GET.get('user_name') or ''
state = self.request.GET.get('state_name') or ''
if (city != '' or user!='' or state!=''):
userqueries = user.split()
cityqueries = city.split()
statequeries = state.split()
if len(userqueries) and len(cityqueries):
qset1 = functools.reduce(operator.__or__, [
Q(first_name__icontains=query) | Q(last_name__icontains=query) for query in userqueries])
qset2 = functools.reduce(operator.__or__, [Q(city__name__icontains=query) for query in cityqueries])
object_list = self.model.objects.filter(qset1 , qset2)
elif len(userqueries) and len(statequeries):
qset1 = functools.reduce(operator.__or__, [
Q(first_name__icontains=query) | Q(last_name__icontains=query) for query in userqueries])
qset2 = functools.reduce(operator.__or__, [Q(city__state__name__icontains=query) for query in statequeries])
object_list = self.model.objects.filter(qset1 , qset2)
elif len(userqueries):
qset1 = functools.reduce(operator.__or__, [
Q(first_name__icontains=query) | Q(last_name__icontains=query) for query in userqueries])
object_list = self.model.objects.filter(qset1)
elif len(cityqueries):
qset1 = functools.reduce(operator.__or__, [Q(city__name__icontains=query) for query in cityqueries])
object_list = self.model.objects.filter(qset1)
elif len(statequeries):
qset1 = functools.reduce(operator.__or__, [Q(city__state__name__icontains=query) for query in statequeries])
object_list = self.model.objects.filter(qset1)
I want to merge all these condition in one:
if len(userqueries) and len(cityqueries):
elif len(userqueries):
elif len(cityqueries):

We probably better make a helper function that constructs a Q object that is the disjunction of several elements, like:
from django.db.models import Q
from functools import reduce
from operator import or_
def q_or_otherwise_true(iterable, *keys):
iterable = list(iterable)
if iterable:
return reduce(or_, [Q(**{key: val}) for val in iterable for key in keys])
else:
return Q()
This thus generates Q objects like:
>>> q_or_otherwise_true(['foo'], 'col1__icontains', 'col2__icontains')
<Q: (OR: ('col1__icontains', 'foo'), ('col2__icontains', 'foo'))>
>>> q_or_otherwise_true(['foo', 'bar'], 'col1__icontains', 'col2__icontains')
<Q: (OR: ('col1__icontains', 'foo'), ('col2__icontains', 'foo'), ('col1__icontains', 'bar'), ('col2__icontains', 'bar'))>
>>> q_or_otherwise_true([], 'col1__icontains', 'col2__icontains')
<Q: (AND: )>
then we can generate this like:
def get_queryset(self):
city = self.request.GET.get('city_name') or ''
user = self.request.GET.get('user_name') or ''
userqueries = user.split()
cityqueries = city.split()
return self.model.objects.filter(
q_or_otherwise_true(userqueries, 'first_name__icontains', 'last_name__icontains'),
q_or_otherwise_true(cityqueries, 'city__name__icontains'),
)
This works because or q_or_otherwise_true makes a disjunction of elements, given the iterable contains any elements. If not it constructs a Q() object, which - in a .filter(..) call - does not filter out anything. So that means we can thus make a conjucntion of these two.
The function can easily be extended to more calls, by simply making an extra q_or_otherwise_true call.

Related

how to merge two views functions with minor differences

I just wrote two view functions for two different models but they are very similar and only different in some names. what is the best way to merge these two view functions to prevent code repetition?
these are the view functions:
def manager_scientific(request, *args, **kwargs):
context = {}
if request.user.role == 'manager':
template = loader.get_template('reg/scientific-manager-dashboard.html')
users = User.objects.filter(role='applicant', isPreRegistered=True, scientificinfo__is_interviewed=True).order_by('-date_joined')
approved = ScientificInfo.objects.filter(is_approved__exact='0').all()
context['users'] = users
context['all_users'] = len(users)
context['approved'] = len(approved)
context['survey_choices'] = SURVEY_CHOICES
if request.GET.get('user', 'all') == 'all':
users = users
if request.GET.get('user', 'all') == 'new':
users = users.filter(scientificinfo__is_approved__exact='0')
field_list = request.GET.getlist('field')
if field_list:
if 'None' in field_list:
users = users.filter(fields__title=None) | users.filter(fields__title__in=field_list)
else:
users = users.filter(fields__title__in=field_list)
gender_list = request.GET.getlist('gender')
if gender_list:
users = users.filter(gender__in=gender_list)
education_list = request.GET.getlist('education')
if education_list:
users = users.filter(educationalinfo__grade__in=education_list)
work_list = request.GET.getlist('work')
if work_list:
users = users.filter(workinfo__position__in=work_list)
province_list = request.GET.getlist('province')
if province_list:
if 'None' in province_list:
users = users.filter(prevaddress__address_province__in=province_list) | users.filter(
prevaddress__address_province=None)
else:
users = users.filter(prevaddress__address_province__in=province_list)
query_string = request.GET.get('query_string')
if query_string:
name_query = None
for term in query_string.split():
if name_query:
name_query = name_query & (Q(first_name__contains=term) | Q(last_name__contains=term))
else:
name_query = Q(first_name__contains=term) | Q(last_name__contains=term)
users = users.filter(name_query |
Q(educationalinfo__field__contains=query_string) |
Q(educationalinfo__tendency__contains=query_string) |
Q(educationalinfo__university_name__contains=query_string) |
Q(workinfo__organization__contains=query_string) |
Q(ngoinfo__ngo_name__contains=query_string) |
Q(melli_code__contains=query_string))
users = users.distinct()
context['grade_choices'] = []
for g, grade in EDUCATIONAL_GRADE_CHOICES:
count = EducationalInfo.objects.filter(user__in=users, grade=g).count()
context['grade_choices'].append((g, grade, count))
context['work_position_choices'] = []
for p, position in WORK_POSITION_CHOICES:
count = WorkInfo.objects.filter(user__in=users, position=p).count()
context['work_position_choices'].append((p, position, count))
provinces = users.values_list('prevaddress__address_province')
provinces = Counter([d[0] for d in provinces])
context['provinces'] = provinces.items()
paginator = Paginator(users, 25) # Show 25 contacts per page.
page_number = request.GET.get('page', 1)
page_obj = paginator.get_page(page_number)
context['users'] = page_obj
context['is_interviewed'] = ScientificInfo.objects.filter(is_approved__exact='0', user__is_staff=False).count()
context['not_interviewed'] = ScientificInfo.objects.filter(is_interviewed=True, user__is_staff=False).count()
context['men'] = users.filter(gender="male").count()
context['women'] = users.filter(gender="female").count()
context['query_string'] = query_string
return HttpResponse(template.render(request=request, context=context))
and the other one:
def manager_religious(request, *args, **kwargs):
context = {}
if request.user.role == 'manager':
template = loader.get_template('reg/religious-manager-dashboard.html')
users = User.objects.filter(role='applicant', isPreRegistered=True, religiousinfo__is_interviewed=True).order_by('-date_joined')
approved = ReligiousInfo.objects.filter(is_approved__exact='0').all()
context['users'] = users
context['all_users'] = len(users)
context['approved'] = len(approved)
context['survey_choices'] = SURVEY_CHOICES
if request.GET.get('user', 'all') == 'all':
users = users
if request.GET.get('user', 'all') == 'new':
users = users.filter(religiousinfo__is_approved__exact='0')
field_list = request.GET.getlist('field')
if field_list:
if 'None' in field_list:
users = users.filter(fields__title=None) | users.filter(fields__title__in=field_list)
else:
users = users.filter(fields__title__in=field_list)
gender_list = request.GET.getlist('gender')
if gender_list:
users = users.filter(gender__in=gender_list)
education_list = request.GET.getlist('education')
if education_list:
users = users.filter(educationalinfo__grade__in=education_list)
work_list = request.GET.getlist('work')
if work_list:
users = users.filter(workinfo__position__in=work_list)
province_list = request.GET.getlist('province')
if province_list:
if 'None' in province_list:
users = users.filter(prevaddress__address_province__in=province_list) | users.filter(
prevaddress__address_province=None)
else:
users = users.filter(prevaddress__address_province__in=province_list)
query_string = request.GET.get('query_string')
if query_string:
name_query = None
for term in query_string.split():
if name_query:
name_query = name_query & (Q(first_name__contains=term) | Q(last_name__contains=term))
else:
name_query = Q(first_name__contains=term) | Q(last_name__contains=term)
users = users.filter(name_query |
Q(educationalinfo__field__contains=query_string) |
Q(educationalinfo__tendency__contains=query_string) |
Q(educationalinfo__university_name__contains=query_string) |
Q(workinfo__organization__contains=query_string) |
Q(ngoinfo__ngo_name__contains=query_string) |
Q(melli_code__contains=query_string))
users = users.distinct()
context['grade_choices'] = []
for g, grade in EDUCATIONAL_GRADE_CHOICES:
count = EducationalInfo.objects.filter(user__in=users, grade=g).count()
context['grade_choices'].append((g, grade, count))
context['work_position_choices'] = []
for p, position in WORK_POSITION_CHOICES:
count = WorkInfo.objects.filter(user__in=users, position=p).count()
context['work_position_choices'].append((p, position, count))
provinces = users.values_list('prevaddress__address_province')
provinces = Counter([d[0] for d in provinces])
context['provinces'] = provinces.items()
paginator = Paginator(users, 25) # Show 25 contacts per page.
page_number = request.GET.get('page', 1)
page_obj = paginator.get_page(page_number)
context['users'] = page_obj
context['is_interviewed'] = ReligiousInfo.objects.filter(is_approved__exact='0', user__is_staff=False).count()
context['not_interviewed'] = ReligiousInfo.objects.filter(is_interviewed=True, user__is_staff=False).count()
context['men'] = users.filter(gender="male").count()
context['women'] = users.filter(gender="female").count()
context['query_string'] = query_string
return HttpResponse(template.render(request=request, context=context))
the only differences are in model names and template addresses.
and also how can I rewrite them in a class based format?
You can create one common class that inherits from View Class and then two separate classes that inherit from the previous one. e.g
class ManagerView(View)
template_name = None
model = None
def get(self, request):
...
template = loader.get_template(self.template_name)
approved = self.model.objects.filter(is_approved__exact='0').all()
...
class ManagerReligiousView(ManagerView)
template_name = 'reg/religious-manager-dashboard.html'
model = ReligiousInfo
class ManagerScientificView(ManagerView)
template_name ='reg/scientific-manager-dashboard.html'
model = ScientificInfo
Another way with a class-based view is to emply the rarely-used ability to pass configuration options to it in the url.
class SomeView( AnyClassBasedView):
foo = 'bar'
In urls.py
path( '/do_bar/<int:pk>', SomeView.as_view(), name='do_bar'),
path( '/do_baz/<int:pk>', SomeView.as_view( foo='baz'), name='do_baz'),
And in SomeView, conditional tests on self.foo. Note, you have to declare any such parameter with a default value in the class definition before you can use it in urls.py.
A variant is to use write a view that can handle >1 method of accessing an object by interrogating self.kwargs
path( '/x/<int:pk>', X_View.as_view(), name='x_by_pk'),
path( '/x/<str:order_number>', X_View.as_view(), name='x_by_order'),
where typically the get_object method is subclassed:
class X_View( DetailView):
...
def get_object( self, queryset=None):
if 'pk' in self.kwargs:
obj = get_object_or_404( X, pk=self.kwargs['pk'] )
elif 'order_number' in self.kwargs:
obj = get_object_or_404( X, order_number=self.kwargs['order_number'] )
...
else:
raise ConfigurationError( 'Check urls.py. No valid kwarg was parsed')
return obj
NB this is a simplified get_object which ignores queryset. See the code of get_object for a better template to slot this pattern into. Classy CBVs to the rescue.

How to chain a multi-feature search in Django

I have a 3 filter search for a job. One is for the job title/decription/company, one for job category for e.g Banking and one for the location for e.g New York
How do I chain the query such that it should render me the appropriate results if I specified any one filter and if I specified all 3 it should perform an AND. I tried doing it with if else, but it is becoming too long. Is there another way?
Here is my code:
views.py
if request.method == "POST":
internship_desc = request.POST['internship_desc']
internship_ind = request.POST['internship_industry']
internship_loc = request.POST['internship_location']
results = []
if internship_desc != "" and internship_desc is not None:
query_results = Internship.objects.filter(
Q(internship_title__icontains=internship_desc) |
Q(internship_desc__icontains=internship_desc) |
Q(recruiter__company_name__icontains=internship_desc)
)
if internship_ind !="" and internship_ind is not None:
if internship_desc != "" and internship_desc is not None:
query_results = query_results.objects.filter(
industry_type__iexact=internship_ind)
else:
query_results = Internship.objects.filter(industry_type__iexact=internship_ind)
if internship_loc !="" and internship_loc is not None:
if internship_desc != "" and internship_desc is not None and internship_ind !=""
and internship_ind is not None:
query_results = query_results.objects.filter(
industry_type__iexact=internship_ind)
query_results = query_results.objects.filter(
recruiter__company_region__iexact=internship_loc)
I think this is what you are trying to do.
result = Internship.objects.all()
if internship_desc:
result = result.filter(internship_desc__icontains=internship_desc)
if internship_ind:
result = result.filter(industry_type__iexact=internship_ind)
if internship_loc:
result = result.filter(recruiter__company_region__iexact=internship_loc)
Your best bet is using django_filters for these sort of filtering.
import django_filters
class InternshipFilter(django_filters.FilterSet):
company_name = django_filters.CharFilter(
field_name='recruiter__company_name',
lookup_expr='icontains'
)
class Meta:
model = Internship
fields = {
'internship_title': ['icontains'],
'internship_desc': ['icontains'],
}
and pass it to template like this:
context['filter_form'] = InternshipFilter().form
and use it in your view to return the filtered objects:
InternshipFilter(self.request.GET, queryset=Internship.objects.all()).qs
more info here

Pagiantor on a search function

I tried to add a Paginator of 5 posts to the current function but I always have developed with the ListView functions, in which is way easier to implement a pagiantor. Any suggestion?
def search(request):
query = request.GET.get("q", None)
qs = DeathAd.objects.all()
if query is not None:
qs = qs.annotate(
full_name = Concat('nome', Value(' '), 'cognome'),
full_cognome = Concat('cognome', Value(' '), 'nome')
).filter(
Q(nome__icontains=query) |
Q(cognome__icontains=query) |
Q(full_name__icontains=query) |
Q(full_cognome__icontains=query)
)
context = {
"object_list": qs,
}
template = "search.html"
return render(request, template, context)

Filter with ChoiceField (Django)

forms.py
status_list = (
('', ''),
('c', 'cancelado'),
('elab', 'em elaboração'),
('p', 'pendente'),
('co', 'concluido'),
('a', 'aprovado')
)
class StatusSearchForm(forms.Form):
status = forms.ChoiceField(
choices=status_list, widget=forms.Select(attrs={'class': 'form-control'}))
template
Status {% for radio in status_search_form.status %} {{ radio }} {% endfor %}
views.py
class ProposalList(ListView):
template_name = 'core/proposal/proposal_list.html'
model = Proposal
paginate_by = 10
def get_context_data(self, **kwargs):
context = super(ProposalList, self).get_context_data(**kwargs)
context.update({'status_search_form': StatusSearchForm(), })
return context
def get_queryset(self):
p = Proposal.objects.all().select_related()
q = self.request.GET.get('search_box')
if q is not None:
try:
p = p.filter(
Q(id__icontains=q) |
Q(work__name_work__icontains=q) |
Q(work__customer__first_name__icontains=q) |
Q(category__category__startswith=q) |
Q(employee__user__first_name__startswith=q) |
Q(seller__employee__user__first_name__startswith=q) |
Q(created__year=q))
except ValueError:
pass
s = self.request.GET.get('status')
if s is not None:
p = p.filter(status__exact=s)
elif s == '':
p = p
return p
Question: I wanted when I chose the first option of 'status' , which in this case is empty, he returned all records normally , the problem is that it is returning
http://localhost:8000/proposal/?status=&search_box=
And it does not return anything . But in this case I want to return all .
What would be the best solution?
def get_context_data(self, **kwargs):
status_classes = {'c': 'fa-close status-cancelado',
'elab': 'fa-circle status-elab',
'p': 'fa-circle status-pendente',
'co': 'fa-check status-concluido',
'a': 'fa-star status-aprovado'}
context = super(ProposalMixin, self).get_context_data(**kwargs)
context.update({'status_search_form': StatusSearchForm(), })
context['status'] = [(item, item_display, status_classes[item])
for item, item_display in STATUS_FILTER]
return context
def get_queryset(self):
super(ProposalMixin, self).get_queryset()
p = Proposal.objects.select_related().all()
status = self.request.GET.get('status')
if status in ('c', 'elab', 'p', 'co', 'a'):
p = p.filter(status=status)
# http://pt.stackoverflow.com/a/77694/761
q = self.request.GET.get('search_box')
if not q in [None, '']:
p = p.filter(
Q(id__startswith=q) |
Q(work__name_work__icontains=q) |
Q(work__customer__first_name__icontains=q) |
Q(category__startswith=q) |
Q(employee__first_name__startswith=q))
return p

Django / GET form / Q filtering

I'm trying to create filter for my results that would take multiple values from html form.
This current setup gives me urls such as this /?language=French&language=German however the results would not show French and German records but only French. Additionally, adding new filtering criteria such as "level" /?language=French&level=Beginner doesn't work either.
Could anybody please help and point me in the right direction?
thanks
sikor
My form looks as follows:
RESOURCES_LANGUAGE = (('English', 'English'),
('Spanish', 'Spanish'),
('French', 'French'),
('German', 'German'))
RESOURCES_LEVEL = (('Beginner', 'Beginner'),
('Intermediate', 'Intermediate'),
('Advanced', 'Advanced'))
SORTBY = (('likes', 'Likes'),
('name', 'Name'),
('latest', 'Latest'))
class FiltersAndSortingForm(forms.Form):
language = forms.MultipleChoiceField(required=False, label='Language', widget=forms.CheckboxSelectMultiple, choices=RESOURCES_LANGUAGE)
level = forms.MultipleChoiceField(required=False, label='Level', widget=forms.CheckboxSelectMultiple, choices=RESOURCES_LEVEL)
provider = forms.ModelMultipleChoiceField(queryset=Provider.objects.all().order_by('name'), label='Provider', required=False,)
sortby = forms.MultipleChoiceField(required=False, label='Sort by', widget=forms.CheckboxSelectMultiple, choices=SORTBY)
My view:
def resources(request):
if request.GET:
language = request.GET.get('language', '')
level = request.GET.get('level', '')
provider = request.GET.get('provider', '')
sortby = request.GET.get('sortby', '')
if sortby == 'name':
orderby = 'name'
elif sortby == 'latest':
orderby = '-dt_added'
elif sortby == 'likes':
orderby = '-facebook_likes'
else:
orderby = '-facebook_likes'
qset = (
Q(language=language)
# &
# Q(level=level)
)
resources = Resource.objects.filter(inactive=0).filter(qset).order_by(orderby)
form = FiltersAndSortingForm()
else:
form = FiltersAndSortingForm()
resources = Resource.objects.filter(inactive=0).order_by('-facebook_likes')
OK, eventually after looking at this thread django dynamically filtering with q objects I got it working like this. Maybe it is not the cleanest way but seems like it is doing the job. Unless anybody could suggest better solution?
thanks
-s
def resources(request):
if request.GET:
type = request.GET.getlist('type', '')
language = request.GET.getlist('language', '')
level = request.GET.getlist('level', '')
provider = request.GET.getlist('provider', '')
sortby = request.GET.get('sortby', '')
if sortby == 'name':
orderby = 'name'
elif sortby == 'latest':
orderby = '-dt_added'
elif sortby == 'likes':
orderby = '-facebook_likes'
else:
orderby = '-facebook_likes'
qset_type = Q() # Create an empty Q object to start with
for x in type:
qset_type |= Q(provider__tags__name=x) # 'or' the Q objects together
qset_language = Q()
for x in language:
qset_language |= Q(language=x)
qset_level = Q()
for x in level:
qset_level |= Q(level=x)
qset_provider = Q()
for x in provider:
qset_provider |= Q(provider=x)
qset = qset_language & qset_level & qset_type & qset_provider
resources = Resource.objects.filter(inactive=0).filter(qset).order_by(orderby)
form = FiltersAndSortingForm()
else:
form = FiltersAndSortingForm()
resources = Resource.objects.filter(inactive=0).order_by('-facebook_likes')
I don't see additional criteria being added to the qset, that's why filtering doesn't work on those parameters. Also I would recommend that you use a dictionary for your search criteria and then unpack it. Check this question out for a detailed explanation Django: How do I use a string as the keyword in a Q() statement?. Let me know if you need any clarification.