My view.py is like this
def searchnews(request):
name = request.GET.get('name', 'default')
categry = request.GET.get('category', 'default')
NewTable.objects.filter(headline__contains=name).filter(category=categry)
What I want is that when the value of any element in querystring is empty
like
if category="" I want to get all the category result
You can make use of an ability to chain filters in Django.
Set the default to an empty string in case it is not found in request.GET. Check for not emptiness before adding an additional filter():
category = request.GET.get('category', '')
results = NewTable.objects.filter(headline__contains=name)
if category:
results = results.filter(category=category)
Another option is to build the filter as a kwargs dictionary
kwargs = {'headline__contains': name}
if 'category' in request.GET and request.GET['cateogry'] is not '':
kwargs['category': request.GET['category']
queryset = NewTable.objects.filter(**kwargs)
You can just make a condition to apply the filter only if a category is provided:
categry = request.GET.get('category', '')
queryset = NewTable.objects.filter(headline__contains=name)
if categry:
queryset = queryset.filter(category=categry)
Related
I have a normal Django view that returns the API for a query set. It takes query params from the URL and filters the database based on the parameters. It also outputs a maximum length of 3 "Part" objects.
I would like to add something so that it returns information on whether the queryset is clipped by the maximum length of 3. The idea is that since the inputs the query parameters, if the parameters are too vague, then there will be too much data being queried from the database. So it is clipped but then the user needs to know that it was clipped.
The current code looks like this
class PartList(generics.ListAPIView):
serializer_class = PartSerializer
def get_queryset(self):
"""
Optionally restricts the returned purchases to a given user,
by filtering against a `username` query parameter in the URL.
"""
queryset = Part.objects.all()
querydict = self.request.query_params
for (k, value) in querydict.items():
search_type = 'contains'
filter = k + '__' + search_type
queryset = queryset.filter(**{filter: value})
query_max_limit = 3
return queryset[:min(len(queryset), query_max_limit)]
You can try to fetch four elements, and in case it returns four, you display the first three, and specify that the data is clipped, like:
def get_queryset(self):
"""
Optionally restricts the returned purchases to a given user,
by filtering against a `username` query parameter in the URL.
"""
queryset = Part.objects.all()
querydict = self.request.query_params
for (k, value) in querydict.items():
search_type = 'contains'
filter = k + '__' + search_type
queryset = queryset.filter(**{filter: value})
query_max_limit = 3
qs = queryset[:query_max_limit+1]
self.clipped = clipped = len(qs) > query_max_limit
if clipped:
return list(qs)[:query_max_limit]
else:
return qs
So here the get_queryset will return a collection (not per se a QuerySet), containing at most three elements, and it will set an attribute self.clipped that specifies if the data was clipped.
Or a more elegant approach would be to first count, and then slice:
def get_queryset(self):
"""
Optionally restricts the returned purchases to a given user,
by filtering against a `username` query parameter in the URL.
"""
queryset = Part.objects.all()
querydict = self.request.query_params
for (k, value) in querydict.items():
search_type = 'contains'
filter = k + '__' + search_type
queryset = queryset.filter(**{filter: value})
query_max_limit = 3
qs = queryset[:query_max_limit+1]
self.clipped = clipped = qs.count() > query_max_limit
if clipped:
return queryset[:query_max_limit]
else:
return qs
It might be better to move this "clipping" logic to a dedicated function, and return if it is clipped, instead of setting an attribute.
It's perfectly fine to pass metadata along with your results, like so:
{
"is_clipped": true,
"results": [
…
]
}
Willem's answer is a good way to set is_clipped.
But I think you are interested in pagination, which is a standard way to communicate to clients that the results are clipped. It's possible combine your queryset filering with pagination. By the way, I suggest you use django-filter instead of rolling your own filtering.
We have 2 approach ideas that we are trying to consider here for the Django REST api.
At the moment, we have implemented 1 filter option:
class APINewsViewSet(viewsets.ModelViewSet):
serializer_class = APINewsSerialiser
def get_queryset(self):
queryset = NewsBackup.objects.all()
source = self.request.query_params.get('source', None)
if source is not None:
queryset = queryset.filter(news_source = source)
return queryset
This achieves: 127.0.0.1/news?source=xxx
Something additional that we would like to do is have other filter options that the user can type in such as 127.0.0.1/news?source=xxx&sourcename=xxx which would then return them the JSON object with only data that has a source_id of xx and source_name of xx.
Is this possible with the Django REST framework? We have tried to add other options within the method:
query_params.get()
You can place the filters in a dictionary and then use the dictionary as the filter. Even better you can create a valid filters that safeguards invalid filters. Something like below:
# {'queryparam': 'db_field'}
valid_filters = {
'source': 'news_source',
'source_name': 'source_name',
}
filters = {valid_filters[key]: value for key, value in self.request.query_params.items() if key in valid_filters.keys()}
# So if the query_params is like this: source=xxx&source_name=xxx
# filters value will look something like below:
{
'news_source': 'xxx',
'source_name': 'xxx',
}
# **filters transforms the filters to: (news_source='xxx', source_name='xxx')
queryset = queryset.filter(**filters)
Or if you don't want to customize it heavily, you can use third party packages like Django Filter
UPDATE
If you want to get the multiple values for example: id=1&id=2, you can use getlist
ids = self.request.query_params.getlist('id')
# ids will have the list of id
# ids = [1, 2]
# you can then query the id
queryset = queryset.filter(id__in=ids)
UPDATE 2
If you want to support multiple filters, you can create a dictionary again:
keys = [
'id',
'source_name',
]
filters = {}
for key in keys:
filter = query_params.getlist(key)
if filter:
filters['{}__in'.format(key)] = filter
queryset = queryset.filter(**filters)
You can add the validation if you want.
Here is my views.py:
class my4appCompanyData(generics.ListAPIView):
serializer_class = my4appSerializer
def get_queryset(self,request):
"""Optionally restricts the returned data to ofa company,
by filtering against a `id` query parameter in the URL. """
queryset = companies_csrhub.objects.all()
#url_id = self.request.query_params.get('id', None)
url_id = request.GET.get('id', None)
if id is not None:
queryset = queryset.filter(id=url_id)
elif id is ALL:
queryset = companies_csrhub.objects.all()
else:
queryset = "Error data not found"
return queryset
And my urls.py:
router.register(r'api/my4app/company/$', views.my4appCompanyData.as_view(),base_name="company")
URL used for checking: mywebsite/api/my4app/company/?id=100227
Planning to add multiple filters with default values but not working. Please help.
class my4appCompanyData(generics.ListAPIView):
serializer_class = my4appSerializer
def get_queryset(self,request):
"""Optionally restricts the returned data to ofa company,
by filtering against a `id` query parameter in the URL. """
queryset = companies_csrhub.objects.all()
url_id = request.query_params.get('id', None)
if id is not None:
queryset = queryset.filter(id=url_id)
elif id is ALL:
queryset = companies_csrhub.objects.all()
else:
queryset = []
return queryset
Delete the return id since id is not a queryset, therefore it'd give an error. Also in the else part of if statement, you return string but you can't do that also since string is not a queryset.
According the official docs (http://www.django-rest-framework.org/api-guide/filtering/#filtering-against-query-parameters)
I think your code is not working because you are using:
url_id = request.query_params.get('id', None)
Instead of:
url_id = self.request.query_params.get('id', None)
In the documentation you can find that get_queryset function just receives self param, you must remove request param.
def index(request):
expiring_list = probe.objects.filter("isExpired=True")
output = ', '.join([p.serial for p in expiring_list])
return HttpResponse(output)
isExpired is a Boolean function. How should I modify the filter so that the filter does not raise a ValueError?
You are making the query in a wrong format.
Your query should be of the form:
expiring_list = probe.objects.filter(isExpired = True)
This was the query you needed to make in case isExpired was your model field. But since you say its a function, assuming that function is inside the class you need to get all the objects in the following way:
expiring_list = []
objects = probe.objects.all()
for obj in objects:
if obj.isExpired() == True:
expiring_list.append(obj)
The expiring_list will now contain all the objects of the model probe where isExpired function returns True
I think isExpired is not a field in your models, as reference to your previous question Refresh a field from another table [Django]
I think exp_date is the field which you are looking for.
Try this:
import datetime
def index(request):
expiring_list = probe.objects.filter(exp_date__lt=datetime.date.today())
output = ', '.join([p.serial for p in expiring_list])
return HttpResponse(output)
I am trying to iterate over form results and I can't help but think that I am re-inventing the wheel here.
filterlist = []
if request.POST:
form = FilterForm(request.POST)
if form.is_valid():
for key, value in form.cleaned_data.iteritems():
filterlist.append(key)
filterlist.append(value)
This works, but seems very awkward and creates lots of other problems. For example the values come back with u' so I have to use value.encode("utf8") but then if a value is None it throws in error. So now I have to check if it is None, if not then encode. There has to be a better way.
EDIT: What I am trying to do.
I am trying to filter what is shown on a page. The problem I am running into is that if a value is empty (the user don't fill the box because they only want to filter against one object) then I get no results. For example a user wants to search for all books by the author name "Smith" but doesn't want to search against a genre.
results = Books.objects.filter(author=author, genre=genre)
The user would get no results because this is an AND search. But, if a user put in "Smith" for the author and "mystery" for the genre then it works exactly like I want it to, only giving results where both are true.
So, I am trying to eliminate the empty stuff by iterating over the form results. Like I said I am probably re-inventing the wheel here.
In Python 3 use:
for key, value in form.cleaned_data.items():
If the field names are the same in the model and the form, try this:
filter = {}
if request.method == 'POST':
form = FilterForm(request.POST)
if form.is_valid():
for key, value in form.cleaned_data.iteritems():
if value:
filter[key] = value
results = Books.objects.filter(**filter)
Python is one of the few languages having named parameters. You can assemble a dict with the non-empty form fields and pass it to the filter method using the kwargs unpacking operator **.
For example:
kwargs = {"author": "Freud"}
results = Books.objects.filter(**kwargs)
Is the same as:
results = Books.objects.filter(author="Freud")
I think the problem is that by default the Model form is not valid if a form field does not have a value entered by the user, if you don`t require the field every time from the user you need to set the required field to false in the ModelForm class in forms.py as shown in the code below. Remember that the field is set false only in the model form not in the model itself
class myForm(forms.ModelForm):
myfield_id = forms.CharField(required=False)
myfield_foo = forms.CharField(required=False)
myfield_bar = forms.CharField(required=False)
myfield_name = forms.CharField(required=False)
class Meta:
model = myModel
exclude = ('myfield_ex','myfield_file')
fields = ['myfield_id','myfield_foo','myfield_bar','myfield_name',]
After you have the form entered by the user what you need is use the Q object which can be used to create complex queries as described in the manula page here
https://docs.djangoproject.com/en/1.7/topics/db/queries/#complex-lookups-with-q
A simple example code would look like
if form.is_valid():
qgroup = []
for key,value in form.cleaned_data.iteritems():
if value:
q_name = Q(**{"%s"%format(filterKey[key]) : value})
qgroup.append(q_name)
q = None
# can use the reduce as shown here qgroup = reduce(operator.or_, (Q(**{"{0}".format(filterKey[key]): value}) for (key,value) in form.cleaned_data.iteritems()))
for key,value in form.cleaned_data.iteritems():
if value:
q_name = Q(**{"%s"%format(filterKey[key]) : value})
qgroup.append(q_name)
for x in qgroup:
q &= x ### Or use the OR operator or
if q:
resultL = myModel.objects.filter(q).select_related()
The filterKey can look something on the lines of
filterKey = {'myfield_id' : "myfield_id",
'myfield_foo' : "myfield_foo__icontains",
'myfield_bar' : "myfield_bar__relative_field__icontains",
}