GET url in search and sort logic - django

I have this url after you hit the search button:
127.0.0.1:8000/results/?name=blab&city=bla&km=12
my view:
def search(request):
name = request.GET.get('name')
city = request.GET.get('city')
km = request.GET.get('km')
if name!="" and name != None:
locations = Location.objects.filter(name__istartswith=name)
return render_to_response("result-page.html",{'locations':locations},context_instance=RequestContext(request))
if city!="" and city != None:
locations = Location.objects.filter(city__istartswith=city)
return render_to_response("result-page.html",{'locations':locations},context_instance=RequestContext(request))
but now, if i look for both name and city, it is giving ony the results search after name. e.g. the first paramater. second one is not being taken.
what is the best logic for this? i also want to able to sort the search result. can you please give me some hints how to this kind of things in clean logic.
thanks

You are returning on the first if, if you want to filter on either or both or no parameters try using one QuerySet with dynamic filters e.g. something like
search_kwargs = {}
if request.GET.get('name'):
search_kwargs['name__istartswith'] = request.GET.get('name')
if request.GET.get('city'):
search_kwargs['city__istartswith'] = request.GET.get('city')
locations = Location.objects.filter(**search_kwargs)
return render_to_response("result-page.html",{'locations':locations},context_instance=RequestContext(request))
or even something like
filter_fields = ['city','name']
for f in filter_fields:
if f in request.GET:
search_kwargs['%s__istartswith' % f] = request.GET.get(f)

Related

Pass SQLAlchemy table column to Flask route during search

I am rather new to Flask and web development in general and am struggling to figure out how to set up arbitrary column searching.
My form:
class SearchForm(FlaskForm):
choices = [('facilityid', 'facilityid'),
('facilityname', 'facilityname'),
('networkid', 'networkid'),
('contractid', 'contractid'),
('address', 'address')]
select = SelectField('Search for something', choices=choices)
id_search = StringField('')
submit = SubmitField('Search')
My Route:
#app.route('/results')
def results(search):
search_string = search.data['id_search']
search_field = search.data['select']
# TODO: Remove redundant code
if search_string == '':
query = FGymNetworkFacilities.query.order_by(func.rand()).offset(20).limit(10).all()
table = FacilityTable(query)
table.border = True
elif search_field == 'address' and search_field != '':
query = FGymNetworkFacilities.query.filter(FGymNetworkFacilities.address.like(f'%{search_string}%')).all()
table = FacilityTable(query)
table.border = True
if not results:
flash('no results!')
return redirect('/')
return render_template('facilities.html', table=table)
This table model has around 80 columns and I'd like to not make 80 if/else statements. I need to find a way to pass the column name to the line (in this example address)
query = FGymNetworkFacilities.query.filter(FGymNetworkFacilities.address.like(f'%{search_string}%')).all()
But if there is a better way to do this, I'm definitely open to that. Unfortunately Elastic Search is not something I can implement given the constraints of this project and am forced to use this type of search method for now.
Thanks in advance.

Function that takes any queryset and a string and that does operation on queryset.string

it's probably more of a python object programming problem but I'm not sure.
I have tried to create the tupling_object function below but adding a string parameter "country_name" in the case of the countries object but it didn't work. The first functions are working fine but I would like to generalize it.
countries = country.objects.all()
categories = category.objects.all()
sectors = sector.objects.all()
def tupling_countries(countries):
list = [('','')]
for country in countries:
list.append((country.country_name, country.country_name))
return list
def tupling_categories(categories):
list = [('','')]
for category in categories:
list.append((category.category_name, category.category_name))
return list
def tupling_sectors(sectors):
list = [('','')]
for sector in sectors:
list.append((sector.sector_name, sector.sector_name))
return list
#here's what I would like to do
def tupling_object(objects):
list = [('','')]
for object in objects:
list.append((object.something ,object.something ))
return list
#
COUNTRY_CHOICES = tupling_countries(countries)
CATEGORY_CHOICES = tupling_categories(categories)
SECTOR_CHOICES = tupling_sectors(sectors)
country = forms.CharField(label='Select a country', widget=forms.Select(choices=COUNTRY_CHOICES))
category = forms.CharField(label='Select a category of activities', widget=forms.Select(choices=CATEGORY_CHOICES))
sector = forms.CharField(label='Select a sector', widget=forms.Select(choices=SECTOR_CHOICES))
Also is there a better way to create dropdown form than tupling my data like that.
Thanks for your time.
You're approaching this in the wrong way. There is a way to directly create choices from querysets, which is to use ModelChoiceField. So replace your entire code with:
country = forms.ModelChoiceField(label='Select a country', queryset=country.objects.all())
category = forms.ModelChoiceField(label='Select a category of activities', queryset=category.objects.all())
sector = forms.ModelChoiceField(label='Select a sector', queryset=sector.objects.all())

Multiple filter parameters in API endpoint - DRF

I am using Django rest framework for my REST API. I have endpoint where are some parameters for filtering, something like this:
# in my view
from_date = self.request.query_params.get('from', None)
to_date = self.request.query_params.get('to', None)
category = self.request.query_params.get('category', None)
color = self.request.query_params.get('color', None)
Then I have if/else code where I am selecting the data. For the first time there was only a two parameters so it was ok, but then I added more parameters for filtering and now I think this code is not looking very good and I think there is a need for some optimization. Moreover combination of different parameters is not working now.
Actual code (in my view):
if from_date is not None and to_date is not None:
something = Something.objects.filter(send_date__range=(from_date, to_date))
elif category:
something = Something.objects.filter(values__something__category__name__iexact=category)
elif color:
something = Something.objects.filter(values__something__color__name__iexact=color)
else:
something = Something.objects.all()
Is there any option, built-in to the DRF or some type of combined filtering in Django how can I combine parameters into one big query taking into account that values can be None?
For example, if user send from_date and to_date query filter data using dates. But user also want filter data using from_date, to_date and color or other combination of parameters. Some parameters can be omitted. The code is only minimal version. I have more parameters and I don't wanna write if and else for every combination of parameters.
Something like (but if one of the values is None query will work without this value):
Something.objects.filter(send_date__range=(from_date, to_date))
.filter(values__something__category__name__iexact=category)
.filter(values__something__color__name__iexact=color)
Translating parameters into query filters cannot be fully automated as only you know what parameters should filter on what field in which way. But unless you have crazy combinations of or and and criteria, it should be pretty straightforward as you can chain .filter() calls without any restrictions as long as you want to and all criteria:
something = Something.objects.all()
if from_date and to_date:
something = something.filter(send_date__range=(from_date, to_date))
if category:
something = something.filter(values__something__category__name__iexact=category)
if color:
something = something.filter(values__something__color__name__iexact=color)
This covers all combinations and looks quite manageable, no?
If you need to or (some of) your criteria, you need Q objects, which however you can chain in the same way:
from django.db.models.query_utils import Q
qq = Q()
if category:
qq = qq | Q(values__something__category__name__iexact=category)
if color:
qq = qq | Q(values__something__color__name__iexact=color)
something = Something.objects.filter(qq)
This is the pattern I use for my custom filtering. If you end up needing more custom implementation around each filter param you could use something like the strategy design pattern which is good to get rid of if-else chains.
FILTER_PARAMS = {'from': 'from_date', 'to': 'to_date', 'category': 'values__something__category__name__iexact', 'color': 'values__something__color__name__iexact'}
def get(self):
filter_params = self.get_filter_params(self.request.query_params)
qs = queryset.filter(**filter_params)
def get_filter_params(self, query_params):
fields = {}
for k, v in query_params.items():
if k in self.FILTER_PARAMS:
fields[self.FILTER_PARAMS[k]] = v
return fields

Append specific entry to the end of a Queryset

I'm trying to order a queryset by the CharField 'name', but I want to exclude the 'Other' entry and add it as a last option for the user to choose...
self.fields["acquisition"].queryset = AcquisitionChannel.objects.exclude(name = "Other").order_by('name')
The above is obviously not good because it excludes the 'Other'...
Could it be done in Django? or I must use Javascript?
Thanks!
To manually add objects to a QuerySet, try _result_cache:
other = AcquisitionChannel.objects.get(name="Other")
objs = AcquisitionChannel.objects.exclude(name="Other").order_by("name")
len(objs) #hit the db to evaluate the queryset.
objs = objs._result_cache.append(other)
self.fields["acquisition"].queryset = objs
Quick and simple: add an order column that takes a special value using extra():
self.fields["acquisition"].queryset = (
AcquisitionChannel.objects
.extra(select={'order': '(CASE name WHEN %s THEN 1 ELSE 0 END)'},
select_params=['Other'],
order_by=['order', 'name']))
Using params, this method works on any DB (that supports CASE).
It's something like:
def get_choices():
choices = AcquisitionChannel.objects.exclude(name = "Other").order_by('name').values_list('value_column', 'text_column')
other = AcquisitionChannel.objects.filter(name = "Other").values_list('value_column', 'text_column')
choices = list(choices)
other = list(other)
return choices + other
then:
acquisition = forms.ChoiceField(choices=get_choices())

Django filter only if the "category" isset!

How can I do this?
I only what to filter if the request is set.
ex. if gender not set, the filter will be:
Test.objects.filter(categories=category, brands=brand)
def index(request):
gender = request.GET.get('gender')
category = request.GET.get('category')
brand = request.GET.get('brand')
Test.objects.filter(genders=gender, categories=category, brands=brand)
If you realize that filter conditions can be passed in as keyword arguments the solution becomes easier to visualize. For e.g. consider the snippet below. This is a verbose way of doing it:
conditions = dict()
for filter_key, form_key in (('genders', 'gender'), ('categories', 'category'), ('brands', 'brand')):
value = request.GET.get(form_key, None)
if value:
conditions[filter_key] = value
Test.objects.filter(**conditions)
Of course another way of doing this would be to use a form rather than pick up the values directly from the GET request.
def index(request):
gender = request.GET.get('gender', None)
category = request.GET.get('category')
brand = request.GET.get('brand')
results = Test.objects.filter(categories=category, brands=brand)
if gender:
results = results.filter(genders=gender)
If gender is not specified, gender variable will set to None, so following if gender block will not be executed.