Django Rest Framework - how to use different query params format? - python

I use [https://www.npmjs.com/package/vue-bootstrap4-table#8-filtering][1] with django-rest-framework.
The problem is that this component uses totally different query params for sorting, filtering, etc.
vue-bootstrap4-table
http://127.0.0.1:8000/api/products/?queryParams=%7B%22sort%22:[],%22filters%22:[%7B%22type%22:%22simple%22,%22name%22:%22code%22,%22text%22:%22xxx%22%7D],%22global_search%22:%22%22,%22per_page%22:10,%22page%22:1%7D&page=1
"filters":[{"type":"simple","name":"code","text":"xxx"}],
whereas Django-rest-framework needs this format:
../?code__icontains=...
I want to figure out how to make DRF accept this format instead of the built-in?
I use just ViewSet.
class ProductViewSet(viewsets.ModelViewSet):
serializer_class = ProductSerializer
filter_class = ProductFilter
filter_backends = [filters.OrderingFilter]
ordering_fields = '__all__'
Is it possible?

It translates to:
http://127.0.0.1:8000/api/products/?queryParams={"sort":[],"filters":[{"type":"simple","name":"code","text":"xxx"}],"global_search":"","per_page":10,"page":1}&page=1
It almost looks as though you're still supposed to serialize these arguments into the correct format manually, or send them in the body of the request instead of as a query param.
I don't know of a painless way to get DRF to deal with this by automatically. However, since the value of 'queryParams' is valid JSON, you can override the methods you want on the ModelViewSet. This page describes the methods you can override. To get the json into a dict you can do json.loads(request.query_params['queryParams']). From then on you can filter and order manually with the ORM.
Or, of course, you could turn the query params into 'regular' query params client side. This is a great lib that can help you out with that: https://medialize.github.io/URI.js/.
Also, it's generally ill-advised to allow users to order against any field. With products this is probably relatively low risk, but don't make it a common practice.

Related

Passing user-entered data to Queryset.filter() - is it safe?

I have a page which takes GET parameters from its url, and passes these directly to a REST API. So the page URL looks like:
foo.com/pizzas/?toppings=mushrooms&toppings=sausage
As the page loads, it will take the GET params and pass them to a REST API like:
foo.com/totally/unrelated/url/?toppings=mushrooms&toppings=sausage
On the backend, I want to extract these out and filter based on them. This is basically what I have right now:
# inside a django rest framework ModelViewSet
# it's not really relevant other than that self.request exists
def get_queryset(self):
queryset = self.model.objects.all()
for param in self.request.query_params:
# param = "toppings"
if not self.is_real_model_field(param): # assume this works
continue
param_value_list = self.request.query_params.getlist(param)
# param_value_list = ['mushrooms', 'sausage']
queryset = queryset.filter(
f"{param}__in": param_value_list
)
return queryset
I've said that the fact it's Django Rest Framework is irrelevant, but I'm not 100% sure on that. In the above example, request.query_params is added by Django Rest Framework, but based on DRF's documentation here I believe it is simply an alias for django's built-in request.GET.
So, is this safe to do in Django? A malicious actor could directly manipulate the URL. I assume that django's QuerySet.filter(field__in: values) will automatically do some cleaning for you, and/or the limited character set of a URL will help stop anything nasty from coming through, but I haven't been able to find any resources discussing the issue.
Have a look at django_filters package. It does what you want and you won't need to reinvent a wheel.
Using this package you could list all filterable fields. This package also adds query param value validation.

Django REST Framework that doesn't alter the model data

I am trying to create a middleware web app that will allow users to control some services on our servers. To that end, I have several models created in Django that are used to track things like the current state of the server, or a list of which inputs are valid for any given service.
The API needs to be able to:
List all instances of a model
Show detailed information from one instance of a model
Accept JSON to be converted into instructions for the software (i.e. "This list of outputs should source from this input")
I don't need to have any further access to the data - Any changes to the details of the models will be done by a superuser through the Django admin interface, as it will only change if the software configuration changes.
So far all the DRF documentation I've found assumes that the API will be used to create and update model data - How can I use DRF for just GET calls and custom actions? Or should I forego DRF and just use plain Django, returning JSON instead of HTML?
Edit: I've realised where my confusion was coming from; I was misunderstanding the purpose/function of serializers vs viewsets. Serializers will always have create + update methods because they turn incoming data into a model object. Viewsets determine what can be done with that object, so that's where you enable different access methods.
If you are using ModelViewSet, you can use the http_method_names class variable.
class MyModelViewSet(viewsets.ModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
http_method_names = ['get']
you can try to use readonlymodelviewset, example from docs
class AccountViewSet(viewsets.ReadOnlyModelViewSet):
"""
A simple ViewSet for viewing accounts.
"""
queryset = Account.objects.all()
serializer_class = AccountSerializer

Django custom manager filter query set (by parameter)

I would like my models to automatically filter by current user.
I did this by defining:
class UserFilterManager(models.Manager):
def get_queryset(self):
return super(UserFilterManager, self).get_queryset().filter( owner=get_current_user() )
where get_current_user() is a middleware which extracts the current user from the request passed to Django.
However, I need to use the models from Celery which does not go through the middleware. In these cases
MyModel.objects.all()
needs to become
MyModel.objects.filter(user=<some user>)
To avoid wrong queries caused by forgetting to filter by user, I would like the model/manager/queryset to assert when a query (any query) is performed without a filter on user.
Is there a way to achieve this?
From what I see get_queryset() cannot receive parameters and models.QuerySet won't provide aid here.

How to add search parameters to GET request in Django REST Framework?

After having read through and completed the Django REST Framework tutorial, it is not completely obvious how one would implement a filter on a GET request. For example, ListAPIView is great for viewing all the instances of a Model in your database. However, if I wanted to limit the results (e.g. for a Book model, I might want to limit the results by publication date or author, etc.). It seems the best way to do this would be to create a custom Serializer, View, etc. and basically write everything by hand.
Is there a better way?
Search parameters are called filter parameters in terms of django-rest-framework. There are many ways to apply the filtering, check the documentation.
In most cases, you need to override just view, not serializer or any other module.
One obvious approach to do it, is to override view's queryset. Example:
# access to /api/foo/?category=animal
class FooListView(generics.ListAPIView):
model = Foo
serializer_class = FooSerializer
def get_queryset(self):
qs = super(FooListView, self).get_queryset()
category = self.request.query_params.get("category", None)
if category:
qs = qs.filter(category=category)
return qs
But, django-rest-framework allows to do such things automatically, using django-filter.
Install it first:
pip install django-filter
Then specify in your view, by what fields you want to filter:
class FooListView(generics.ListAPIView):
model = Foo
serializer_class = FooSerializer
filter_fields = ('category', )
This will do the same, as in previous example, but less code used.
There are many ways to customize this filtering, look here and here for details.
There is also a way to apply filtering globally:
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',)
}

Tastypie: queryset = Model.objects.all()

I am newbie to Tastypie. I see that tastypie call Django Models using queryset and displays data.
My question is: if Tastypie builds the statement queryset = < DJANGO-MODEL >.objects.all(),
will it put a tremendous load on the database/backend if there are 100 million objects?
class RestaurentsResource(ModelResource):
class Meta:
queryset = Restaurents.objects.all()
print queryset
resource_name = 'restaurents'
Django querysets are lazy: https://docs.djangoproject.com/en/dev/topics/db/queries/#querysets-are-lazy, so no database activity will be carried out until the queryset is evaluated.
If you return all 1000 objects from your REST interface, then a 'tremendous' load will be placed on your server, usually pagination: http://django-tastypie.readthedocs.org/en/latest/paginator.html or similar is used to prevent this.
Calling print on the queryset as in the example class above, will force evaluation. Doing this in production code is a bad idea, although it can be handy when debugging or as a learning tool.
The two other answers are correct in terms of QuerySets being lazy. But on top of that, the queryset you specify in the Meta class is the base for the query. In Django, a QuerySet is essentially the representation of a database query, but is not executed. QuerySets can be additionally filtered before a query is executed.
So you could have code that looks like:
Restaurant.objects.all().filter(attribute1=something).filter(attribute2=somethindelse
Tastypie just uses the QuerySet you provide as the base. On each API access, it adds additional filters to the base before executing the new query. Tastypie also handles some pagination, so you can get paginated results so not every row is returned.
While using all() is very normal, this feature is most useful if you want to limit your Tastypie results. Ie, if your Restaurant resource has a 'hidden' field, you might set:
class Meta:
queryset = Restaurant.objects.filter(hidden=False)
All queries generated by the API will use the given queryset as the base, and won't show any rows where 'hidden=True'.
Django QuerySet objects are evaluated lazily, that is - the result is fetched from the db when it is really needed. In this case, queryset = Restaurents.objects.all() create a QuerySet that has not yet been evaluated.
The default implementation of ModelResource usually forces the queryset to be evaluated at dehydration time or paging. The first one requires model objects to be passed, the other one slices the queryset.
Custom views, authorization, or filtering methods can force the evaluation earlier.
That said, after doing all the filtering and paging, the results' list fetched is considerably smaller that the overall amount of data in the database.

Categories

Resources