Django CursorPagination for ordering by several fields - python

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

Related

django - combine the output of json views

I write a simple json api, I use one base class, and I mostly write one api view per one model class. What I want is to combine the output of few views into one url endpoint, with as least as possible additional code.
code:
# base class
class JsonView(View):
def get(self, request):
return JsonResponse(self.get_json())
def get_json(self):
return {}
class DerivedView(JsonView):
param = None
def get_json(self):
# .. use param..
return {'data': []}
urls.py:
url('/endpoint1', DerivedView.as_view(param=1))
url('/endpoint2', DerivedView2.as_view())
# What I want:
url('/combined', combine_json_views({
'output1': DerivedView.as_view(param=1),
'output2': DerivedView2.as_view()
}))
So /combined would give me the following json response:
{'output1': {'data': []}, 'output2': output of DerivedView2}
This is how combine_json_views could be implemented:
def combine_json_views(views_dict):
d = {}
for key, view in views_dict.items():
d[key] = view() # The problem is here
return json.dumps(d)
The problem is that calling view() give me the encoded json, so calling json.dumps again gives invalid json. I could call json.loads(view()), but that looks bad to decode the json that I just encoded.
How can I modify the code (maybe a better base class) here, while keeping it elegant and short? without adding too much code. Is there any way to access the data (dict) which is used to construct JsonResponse?
You can create a combined view that calls the get_json() methods and combines them:
class CombinedView(JsonView):
def get_json(self):
view1 = DerivedView(param=1)
view2 = DerivedView2()
d = view1.get_json()
d.update(view2.get_json())
return d
then:
url('/combined', CombinedView.as_view()),

Why is this Django QuerySet returning no results?

I have this Class in a project, and I'm trying to get previous and next elements of current one.
def get_context(self, request, *args, **kwargs):
context = super(GuidePage, self).get_context(request, *args, **kwargs)
context = get_article_context(context)
all_guides = GuidePage.objects.all().order_by("-date")
context['all_guides'] = all_guides
context['next_guide'] = all_guides.filter(date__lt=self.date)
context['prev_guide'] = all_guides.filter(date__gt=self.date)
print context['next_guide']
print context['prev_guide']
return context
These two lines:
context['prev_guide'] = all_guides.filter(date__lt=self.date)
context['next_guide'] = all_guides.filter(date__gt=self.date)
are returning empty results as printed in the console:
(QuerySet[])
(QuerySet[])
What am I missing?
EDIT:
I changed lt and gt to lte and gte. As I understand that will include results that are also equal in date.
In this case I got ALL elements. All elements were created the same day, but, of course, at different times, so they should be different by minutes. Is this difference not taking into account when filtering for greater/lesser ?
If you want to filter not only by date, but time also, you must change the relevant field in your model to be of DateTimeField instead of DateField.
Like this:
from django.db import models
class MyModel(models.Model):
date_time = models.DateTimeField()
Now, you can do stuff like all_guides.filter(date_time__lte=self.date_time) or all_guides.filter(date_time__gte=self.date_time).
Carefull of the two underscores __.

django-rest-framework : list parameters in URL

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

How to obtain a single resource through its ID from a URL?

I have an URL such as: http://example.com/page/page_id
I want to know how to get the page_id part from url in the route. I am hoping I could devise some method such as:
#route('/page/page_id')
def page(page_id):
pageid = page_id
It's pretty straightforward - pass the path parameter in between angle brackets, but be sure to pass that name to your method.
#app.route('/page/<page_id>')
def page(page_id):
pageid = page_id
# You might want to return some sort of response...
You should use the following syntax:
#app.route('/page/<int:page_id>')
def page(page_id):
# Do something with page_id
pass
You can specify the ID as integer :
#app.route('/page/<int:page_id>')
def page(page_id):
# Replace with your custom code or render_template method
return f"<h1>{page_id}</h1>"
or if you are using alpha_num ID:
#app.route('/page/<username>')
def page(username):
# Replace with your custom code or render_template method
return f"<h1>Welcome back {username}!</h1>"
It's also possible to not specify any argument in the function and still access to URL parameters :
# for given URL such as domain.com/page?id=123
#app.route('/page')
def page():
page_id = request.args.get("id") # 123
# Replace with your custom code or render_template method
return f"<h1>{page_id}</h1>"
However this specific case is mostly used when you have FORM with one or multiple parameters (example: you have a query :
domain.com/page?cars_category=audi&year=2015&color=red
#app.route('/page')
def page():
category = request.args.get("cars_category") # audi
year = request.args.get("year") # 2015
color = request.args.get("color") # red
# Replace with your custom code or render_template method
pass
Good luck! :)

Django optional view parameter with HttpResponseRedirect

So I have a view that grabs a person's info from a query and returns the info to the page:
def film_chart_view(request, if_random = False):
I also have a view that randomly grabs a person's info and redirects it to the above view:
def random_person(request):
.
.
return HttpResponseRedirect(reverse('home.views.film_chart_view')+"?q="+get_person.short)
However, I want the first view to recognize if it came from the second view, so that if it is, it sets the if_random parameter to True, but I'm not exactly sure how to do that.
my urls:
url(r'^film_chart_view/$', 'home.views.film_chart_view'),
url(r'^random/$', 'home.views.random_person'),
You don't have to pass if_random as a url parameter.
def random_person(request):
return HttpResponseRedirect(
reverse('home.views.film_chart_view') + \
"?q=" + get_person.short + \
"&is_random=1"
)
def film_chart_view(request):
is_random = 'is_random 'in request.GET
But if you prefer url parameters, the solution is a little more complex.
The parameters passed to the view function comes from the url patterns, you need to set them at first.
Because the is_random para is optional, I suggest you to write 2 separated patterns for the film_chart_view.( actually you can combine these 2 patterns to one with a more complex regex expr, but readability counts.)
urlconf:
url(r'^film_chart_view/$', 'home.views.film_chart_view', name ='film_chart_view'),
url(r'^film_chart_view/(?P<is_random>.*)/$', 'home.views.film_chart_view', name ='film_chart_view_random'),
url(r'^random/$', 'home.views.random_person'),
def random_person(request):
return HttpResponseRedirect(
reverse('home.views.film_chart_view', kwargs={'is_random': '1'}) + \
"?q=" + get_person.short
)
The view parameters(except the request) are always strings, you need to convert it to int/bool/... in you code.
def film_chart_view(request, is_random=None):
if is_random:
...

Categories