django-rest-framework : list parameters in URL - python

I am pretty new to django and django-rest-framework, but I am trying to pass lists into url parameters to then filter my models by them.
Lets say the client application is sending a request that looks something like this...
url: "api.com/?something=string,string2,string3&?subthings=sub,sub2,sub3&?year=2014,2015,2016/"
I want to pass in those parameters "things", "subthings", and "years" with their values.
Where the url looks something like this?
NOTE: Trick is that it won't be always an array of length 3 for each parameter.
Can someone point me in the right direction for how my url regex should be handing the lists and also retrieving the query lists in my views.
Thanks!

To show how I did this thanks to the document links above.
Note: I used pipes as my url delimiter and not commas -> '|'.
in my urls.py
url(r'^$', SomethingAPIView.as_view(), name='something'),
in my views.py
class SomethingAPIView(ListAPIView):
# whatever serializer class
def get_queryset(self):
query_params = self.request.query_params
somethings = query_params.get('something', None)
subthings = query_params.get('subthing', None)
years = query_params.get('year', None)
# create an empty list for parameters to be filters by
somethingParams = []
subthingsParams = []
yearParams = []
# create the list based on the query parameters
if somethings is not None:
for something in somethings.split('|'):
countryParams.append(int(something))
if subthings is not None:
for subthing in subthings.split('|'):
subthingsParams.append(int(subthing))
if years is not None:
for year in years.split('|'):
yearParams.append(int(year))
if somethings and subthings and years is not None:
queryset_list = Model.objects.all()
queryset_list = queryset_list.filter(something_id__in=countryParams)
queryset_list = queryset_list.filter(subthing_id__in=subthingsParams)
queryset_list = queryset_list.filter(year__in=yearParams)
return queryset_list
I do need to check for an empty result if they are not valid. But here is starting point for people looking to pass in multiple values in query parameters.
A valid url here would be /?something=1|2|3&subthing=4|5|6&year=2015|2016.

Checkout this doc http://www.django-rest-framework.org/api-guide/filtering/
Query params are normally not validated by url regex

Related

How to get multiple queryset from textarea in django

I have a text area in which user inputs multiple values in different line and i want to get object for every single line in text area and send it to template. How i should do it.?
if request.method == 'GET':
search = request.GET.get('search')
slist = []
for i in range(4):
slist.append(search.splitlines()[i])
sdata = Stock.objects.all().filter(slug=slist)
return render(request, 'stocks/searchbar.html', {'sdata':sdata})
I'm trying to do it in this way.
You need to do something like this:
sdata = Stock.objects.filter(slug__in=search.splitlines())
Since search.splitlines() returns a list and slug is, I assume, a CharField, you need the in clause in your query.

What is the proper way to verify if a filter was used by the user to be added to filters in the backend Django?

I am asking for a suggestion abount how to detect which filters are being used by the user, a filtering system can have different options to get the data, but using if statements to check if a value comes in a POST and then add it to a filters set is not really a good option specially when there a lot of them.
# Some if statements detecting if a filter is used (if it is not null in the POST)
# Adding the filter to filters
filters = {
# filters after being added
'{}__{}'.format('categories', 'exact'): request.POST['category'],
'{}__{}'.format('price', 'gte'): request.POST['price'], # Only an example
}
products = Product.objects.filter(**filters)
This works, but i just want to know what would you recommend.
If I understood your question correctly, I would chain filters instead:
queryset = Product.objects.all()
if 'category' in request.POST:
queryset.filter(categories__exact=request.POST['category'])
if 'price' in request.POST:
queryset.filter(price__gte=request.POST['price'])
To expand on Gasanov's suggestion:
possible_filters = {
'category': 'exact',
'price': 'gte',
# etc. Not sure if this can be done any smarter
# maybe if you pass `cateogry__exact` in the POST data instead of just `category`?
}
queryset = Product.objects.all()
for key, val in request.POST.items():
if key in possible_filters:
filter_kwargs = {
f'{key}__{possible_filters[key]}': val,
}
queryset = queryset.filter(**filter_kwargs)
Or you can build up the kwargs and have a single call to filter. Unless you are filtering over reverse FK relationships or M2M relationships, the two are pretty much the same (docs for when they are not the same are here)
filter_kwargs = {}
for key, val in request.POST.items():
if key in possible_filters:
filter_key = f'{key}__{possible_filters[key]}'
filter_kwargs[filter_key] = val
queryset = queryset.filter(**filter_kwargs)

Django CursorPagination for ordering by several fields

I am using Django DRF's CursorPagination for lazy loading of my data, and currently my goal is to sort the data by more than one field.
This is how my code looks like now:
class EndlessPagination(CursorPagination):
ordering_param = ''
def set_ordering_param(self, request):
self.ordering = request.query_params.get(self.ordering_param, None)
if not self.ordering:
raise ValueError('Url must contain a parameter named ' +
self.ordering_param)
if self.ordering.startswith("\"") or self.ordering.endswith("\""):
raise ValueError('Ordering parameter should not include quotation marks'
def paginate_queryset(self, queryset, request, view=None):
# This function is designed to set sorting param right in the URL
self.set_ordering_param(request)
return super(EndlessPagination, self).paginate_queryset(queryset, request, view)
This code works fine for urls like my_url/sms/270380?order_by=-timestamp, but what if I want to sort by several fields ?
Use str.split() to split the url params
class EndlessPagination(CursorPagination):
ordering_param = 'order_by'
def set_ordering_param(self, request):
ordering_param_list = request.query_params.get(self.ordering_param, None)
self.ordering = ordering_param_list.split(',')
# here, "self.ordering" will be a "list", so, you should update the validation logic
"""
if not self.ordering:
raise ValueError('Url must contain a parameter named ' +
self.ordering_param)
if self.ordering.startswith("\"") or self.ordering.endswith("\""):
raise ValueError('Ordering parameter should not include quotation marks'
"""
def paginate_queryset(self, queryset, request, view=None):
# This function is designed to set sorting param right in the URL
self.set_ordering_param(request)
return super(EndlessPagination, self).paginate_queryset(queryset, request, view)
Example URLs
1. my_url/sms/270380?order_by=-timestamp
2. my_url/sms/270380?order_by=-timestamp,name
3. my_url/sms/270380?order_by=-name,foo,-bar
UPDATE-1
First of all thanks to you for giving a chance to dig deep :)
As you said, me too didn't see comma seperated query_params in popular APIs. So, Change the url format to something like,my_url/sms/270380??order_by=-name&order_by=foo&order_by=-bar
At this time, the request.query_params['order_by'] will be a list equal to ['-name','foo','-bar']. So, you don't want to use the split() function, hence your set_ordering_param() method become,
def set_ordering_param(self, request):
self.ordering = request.query_params.get(self.ordering_param, None)
#...... your other validations

Does django's URL Conf support mapping an argument to strings?

The Problem
I'm unsure of the best way to phrase this, but here goes: (note some of this may not be syntactically/semantically correct, as it's not my actual code, but I needed it to help explain what I'm asking)
Say I have a model the model Album:
Class Album(models.Model):
ALBUM_TYPE_SINGLE = 1
ALBUM_TYPE_DEMO = 2
ALBUM_TYPE_GREATEST_HITS = 3
ALBUM_CHOICES = (
(ALBUM_TYPE_SINGLE, 'Single Record'),
(ALBUM_TYPE_DEMO, 'Demo Album'),
(ALBUM_TYPE_GREATEST_HITS, 'Greatest Hits'),
)
album_type = models.IntegerField(choices=ALBUM_CHOICES)
And I want to have separate URLs for the various types of albums. Currently, the URL Conf is something like so:
urlpatterns = [
url(r'^singles/(?P<pk>.+)/$', views.AlbumView, name="singles"),
url(r'^demos/(?P<pk>.+)/$', views.AlbumView, name="demos"),
url(r'^greatest-hits/(?P<pk>.+)/$', views.AlbumView, name="greatest_hits"),
]
And when I want to serve the appropriate URL, I need to check the album_type manually:
if object.album_type == Album.ALBUM_TYPE_SINGLE:
return reverse('singles', object.id)
elif object.album_type == Album.ALBUM_TYPE_DEMO:
return reverse('demos', object.id)
elif object.album_type == Album.ALBUM_TYPE_GREATEST_HITS:
return reverse('greatest_hits', object.id)
However, this is cumbersome to do, and I'm wondering if there is a way to pass in the album_type field to the call to reverse and have it automatically get the URL based on that. i.e. something like this:
urlpatterns = [
url(r'^(?P<type>[singles|demos|greatest-hits])/(?P<pk>.+)/$', views.AlbumView, name="albums"),
]
and called with
reverse("albums", object.album_type, object.id)
Attempted solutions
I considered setting the choice strings to be
ALBUM_CHOICES = (
(ALBUM_TYPE_SINGLE, 'singles'),
(ALBUM_TYPE_DEMO, 'demos'),
(ALBUM_TYPE_GREATEST_HITS, 'greatest-hits'),
)
which would then allow me to send object.get_album_type_display() as a string variable for type, which works, however, I need to be able to use reverse to build the URL while only having access to the integer value of album_type and not the display value.
I know this is an oddly specific question for an oddly specific scenario, but if anyone has any kind of potential solutions, I'd be very grateful! Thank you in advance!
I would change the field to a CharField, and use the URL slug as the actual value rather than the display value:
Class Album(models.Model):
ALBUM_TYPE_SINGLE = 'singles'
ALBUM_TYPE_DEMO = 'demos'
ALBUM_TYPE_GREATEST_HITS = 'greatest-hits'
ALBUM_CHOICES = (
(ALBUM_TYPE_SINGLE, 'Single Record'),
(ALBUM_TYPE_DEMO, 'Demo Album'),
(ALBUM_TYPE_GREATEST_HITS, 'Greatest Hits'),
)
album_type = models.CharField(choices=ALBUM_CHOICES, max_length=50)
In your urls.py:
urlpatterns = [
url(r'^(?P<type>singles|demos|greatest-hits)/(?P<pk>.+)/$', views.AlbumView, name="albums"),
]
Then you can reverse it by passing album.album_type:
reverse('albums', args=(album.album_type, album.pk))

Building a custom filter for my tastypi resource

I have this working code to filter out from a list that I testing using a normal GET request at a test url:
tag_list = request.GET.get('tag_list').split('&')
tags = Tag.objects.all()
all_species = Species.objects.all()
filtered_species = [all_species.filter(tags__description=c) for c in tag_list]
species = reduce(and_, filtered_species, all_species)
request will look like:
/?tag_list=winged fruit&latex present&foo&bar
How or where do I add that as a custom filter to my api resource?
Hi again we have met on #tastypie.
That was interested question and will answer it here again might be useful for others.
First your url should be in form:
/?tag_list=winged%20fruit&tag_list=latex%20present&tag_list=foo&tag_list=bar
Then to access to your tag_list in request you have to use special method getlist:
request.GET.getlist('tag_list')
Edit:
I would implement query this way but probably this solution could be improved:
tag_phrases = request.GET.getlist('tag_list')
# Create OR query based on `tag_phrases`
query = Q(tags__description=tag_phrases[0])
for index, tag_phrase in tag_phrases:
if index == 0:
continue
query |= Q(tags__description=tag_phrase)
species = Species.objects.filter(query)
# Some of species might be duplicated
species = set(species)

Categories