How to use transactions with Django REST framework? - python

I wish to use Django REST framework to create a number of model objects "together" -- i.e. in a single transaction.
The objective is that each of the objects will only be visible at the (successful) end of the transaction.
How can I do that?

Use atomic from django.db.transaction as a decorator around a function performing the database operations you are after:
If obj_list contains a list of populated (but not saved) model objects, this will execute all operations as part of one transaction.
#atomic
def save_multiple_objects(obj_list):
for o in obj_list:
o.save()
If you want to save multiple objects as part of the same API request, then (for example), if they are all of the same type, then you could POST a list of objects to an API endpoint - see Django REST framework post array of objects

You can achieve this by using django db transactions. Refer to the code below
from django.db import transaction
with transaction.atomic():
model_instance = form.save(commit=False)
model_instance.creator = self.request.user
model_instance.img_field.field.upload_to = 'directory/'+model_instance.name+'/logo'
self.object = form.save()
This example is taken from my own answer to this SO post. This way, before calling save() you can save/edit other dependencies

Related

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

Using python builtins to pass django request from middleware to model in order to create default model filters for current user?

I have a fairly complex django application that has been in production for over a year.
The application holds data from different customers. The data is obviously in the same table, separated by customer_id.
Recently the client has started to ask questions about data segregation. Since the app is sold on a per user basis and holds sensitive information, customers have been asking if and how we maintain data segregation per customer, and are there any security measures that we take to prevent data leakages (ie. data from one customer being accessed by another customer).
We do our filters in the view endpoints, but eventually a developer in the team might forget to include a filter in his ORM query, and cause a data leakage
So we came up with the idea to implement default filters on our models. Basically whenever a developer writes:
Some_Model.objects.all()
essentially they will execute:
Some_Model.objects.filter(customer_id = request.user.customer_id)
We plan to achieve this by overriding the objects property on each model to point to a manager with a filtered queryset. Something like this:
class Allowed_Some_Model_Manager(models.Manager):
def get_queryset(self):
return super(Allowed_Some_Model_Manager, self).get_queryset().filter(
customer_id = request.user.customer_id
# the problem is that request.user is not available in models.py
)
class Some_Model(models.Model):
name = models.CharField(max_length=50)
customer = models.ForeignKey(Customer)
objects = Allowed_Some_Model_Manager()
all_objects = models.Manager() # use this if we want all objects
However our problem is that request.user is not available in models.py.
I have found several ways to solve this.
Option 1 includes passing the request.user to the manager each time. However since I am dealing with thousands of lines of old code, I don't want to go and change all of our ORM queries.
Option 2, included using threading.local() to set the request.user in the thread local data.
Something like this: https://djangosnippets.org/snippets/2179/
There is a module that seems to be doing this: https://github.com/Alir3z4/django-crequest
However, a lot of people seem to be against this idea... Namely these two discussions:
django get_current_user() middleware - strange error message which goes away if source code is "changed" , which leads to an automatic server restart
Django custom managers - how do I return only objects created by the logged-in user?
So that brings me to Option 3 which I came up with, and I can not find anybody else using it. Use the python builtins module to pass the user from the middleware to the model.
#middleware.py
import builtins
def process_request(self, request):
if request.user.id:
builtins.django_user = request.user
#models.py
import builtins
class Allowed_Some_Model_Manager(models.Manager):
def get_queryset(self):
if 'django_user' in vars(builtins):
return super(Allowed_Some_Model_Manager, self).get_queryset().filter(
customer_id = django_user.customer_id
)
else:
return super(Allowed_Some_Model_Manager, self).get_queryset()
I have tested the code and it is working on my local django server and on Apache with mod_wsgi. But I really want to hear if there are any pitfalls of this approach. I have never used builtins module before, and I am not sure if I understand how it works, and what is the use-case for it.

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.

Django: Filter request results to only contain data related to the requesting user

I'm a Django beginner (though I do have experience in web development using Sails.js + Angular) so bear with me.
I have an existing application that uses REST API in communicating between Sails.js backend and AngularJS frontend. Now, we've found the backend to be unsuited for our purposes, and we're going to swap to using Django in near-future. Sails.js automatically creates the REST methods for the controllers while Django doesn't, so I suppose I'm going to use something like Django Rest Framework to create the API.
So yeah, I've found corresponding features for most things. The on thing I haven't found yet is a replacement for a Sails.js feature called "policies". They are functions that can be executed on queries to certain controller actions, and can be defined as model-specific, model-controller action-specific, and request type specific. For example, you can have an "authAccess" policy that checks that the user of a request is authenticated, and the policy gets executed before the actual requested controller method gets executed. You can also use these to modify request objects before passing them to the controller. Now to my actual problem:
Let's say I have a User model that has a many-to-one relation with another model, let's call it Book, meaning a user can own many books, but a book can only have one owner. Goody good. Now, we have a logged-in user that is making a query to find all of his books. He makes a GET request to /book. I want to ensure that the returned Book objects are filtered so that ONLY HIS BOOKS are returned.
So basically in Sails I was able to write a policy that altered the request parameters to be like {user: loggedInUser} so the resulting Book results were automatically filtered. This was handy, since I was able to use the same policy to filter other models, too, like DVD or Friend or whatnot. My question is - what would be the best way to implement the same functionality in Django?
Have a look at the documentation:
http://www.django-rest-framework.org/api-guide/filtering/#filtering-against-the-current-user
Most likely you are better off overwriting the get_queryset method in a model viewset. And you can make this a generic approach by creating a base class for your views, something like:
from rest_framework import generics, viewsets, mixins, generics
class OwnerModelViewSet(viewsets.ModelViewSet):
def get_queryset(self):
"""
This view should return a list of all the records
for the currently authenticated user.
"""
return self.model.objects.filter(user=self.request.user)
All your model viewset classes can inherit from that class. It would require the foreign key field to be always named "user" though. If that is not the case here is a slightly hacky way how you could find a foreign key field to the User table. Use with care.
from django.db.models.fields.related import ForeignKey
from accounts.models import User
def _get_related_user(self, obj):
'''
Search for FK to user model and return field name or False if no FK.
This can lead to wrong results when the model has more than one user FK.
'''
for f in self.model._meta.fields:
if isinstance(f, ForeignKey) and f.rel.to == User:
return f.name
return False

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