How to disable Django REST Framework caching? - django

I'm just start working with django and DRF, and occure a problem, that is looks like DRF cache responses. I mean - I can change object, create new, or delete it - and DRF keep response, thats nothing is changed. For example, I create an object, but modelViewSet still return data where this object does not presented. But if I directly request it object - it show that it's created. And so with any another actions. I can't find topic about caching in DRF, and look like I have not any django chaching middlewares, so I have no idea what is going on.
Only one thing that helps - restart server ( I'm using default dev-server).
One more thing - all data is ok when it's rendered by django views, not DRF views.
Here is one of the serializers/modelViewSets that I'm using. It simple as it possible. And also - I'm not using django cache backends. At least - I have not any in my settings.
class WorkOperationSerializer(serializers.ModelSerializer):
class Meta:
model = WorkOperation
class WorkOperationAPIView(viewsets.ModelViewSet):
serializer_class = WorkOperationSerializer
queryset = WorkOperation.objects.all()
def get_queryset(self):
return self.queryset

You can read here about django queryset caching. The best advice seems to be: re-run the .all() method to get fresh results. Using object.property may give you cached results.

Related

Django rest framework hyperlinkrelatedfield for one table using primary key

I have a table called 'users' and 'location'. Users table has a foreign key that relates to location table. I have a users serializer to get the JSON. What would I do to get the hyperlinks for the users table using its primary key?
In django rest framework documentation, I couldn't find a solution. I tried using hyperlinkrelatedfield. But still I couldn't achieve this. Can someone help me in finding the solution?
Using rest-framework HyperlinkedRelatedField does not work because it was never built to expose the URL of the object being requested. Mainly because since the client already has the url of the user, why send it back again? Nevertheless you can achieve this by doing something like this.
class UserSerializer(serializers.ModelSerializer):
user_url = serializers.SerializerMethodField()
class Meta:
model = User
def get_label_location(self, obj):
return HyperlinkedRelatedField(view_name='user-detail',
read_only=True) \
.get_url(obj, view_name='label-detail',
request=self.context['request'], format=None)
Take note on a few things,
view-name param to the HyperlinkedRelatedField should be based on your url configuration
read-only has to be true since otherwise you'll have to specify the queryset. But since we have the object needed to generate the url we can ignore that.
I've set format param to None but you might want to set it based on your settings.
You can read up about SerializerMethodField here.

How to modify Django Rest Framework incoming request?

I am building a web app using Django that is pretty much only serving as the API server. I have a single-page application that connects to it as well as an Android client. I have a need to modify some of the incoming POST requests that are coming through.
My two use cases:
If during the registration process the user does not select an avatar image to upload (which is a simple TextField that is the URL to the image), I should be able to insert the default avatar URL. So something like if request.data["avatar"] is None: <use default>
The incoming "timestamp" requests from the Android client are all unix timestamps. I would like to convert this to Django's datetime on the fly - so, current request comes in with date_time = 1473387225, I'd like to convert that to a DateTime object.
Now, I'm already doing something similar for certain POST parameters. The way I do it right now is in the post() function of my generic ListCreateApiView I would directly modify the request object and then call the self.create() with that new request object. Is this the right way, or is there a much better way to do it?
Thanks!
If you are using django-rest-framework these things can be done by serializers.
For avatar use an URLField with default value.
For the timestamp you should probably create a custom field.
Check out this site: http://www.cdrf.co It is an easily navigable display of all the methods available on a given class. You can simply use this to overwrite the View you are using. If a model ViewSet, you likely want perform_create and perform_update.
I often do something like this:
class SomeViewSet(viewsets.ModelViewSet):
queryset = SomeModel.objects.all()
serializer_class = SomeModelSerializer
def perform_create(self, serializer):
data = self.request.data
# make some changes to self.request here
serializer.save(
#change some things here
field='some new value'
)
You can do this in a number of ways. As a part of your validation or in the to_internal_value of the request serializer or in a custom field serializer.
Heres an example of doing this as a part of a custom field serializer.
class AccountCreationSerializer(serializers.Serializer):
avatar = AvatarField(
required=False
allow_files=True
)
# Custom Field Serializer
class AvatarField(serializers.FilePathField):
def to_internal_value(self, value):
user_defined_path = super(AvatarField, self).to_internal_value(value)
if user_defined_path:
return user_defined_path
return default_path

Django multiple dbs - admin search results using a read-only db

I'm using the admin search_fields functionality.
The problem: some of my tables are very big. So search is taking forever, and adding extra load on my production database.
As I'm having a follower of my production db, I though a good idea would be to use the follower as a read-only db, especially for those kind of requests.
So I decided to add a 'read-only' db in settings.DATABASES and surcharge ModelAdmin.get_search_results in my admin classes:
def get_search_results(self, request, queryset, search_term):
queryset, use_distinct = super(ReadOnlyDatabaseAdmin, self)\
.get_search_results(request, queryset, search_term)
queryset = queryset.using('read-only')
return queryset, use_distinct
After this update, I started to get some router errors when trying to set some object as foreign key related object of another object:
Cannot assign "...": the current database router prevents this relation
NB: the read-only database was the same as the default one when I tested and got the aforementioned error, I didn't use the follower yet. I just have set a 'read-only' key in settings.DATABASES, pointing to the same dict as DATABASES['default'].
So the problem is not coming from using a different database, but strictly from the database router.
To give more detail: this error is notably coming from admin actions that are performed when in a admin-search-results page (/admin/app/obj/?q=...).
I figured it's maybe because I replace the queryset object in the method. Maybe this object is actually re-used somewhere else notably in admin actions...? I am currently looking into this.
So I'm interested in:
finding the reason of the error
and/or finding another way of performing admin search requests on a follower database to offload the main database
I guess the answer to the error is to do instead:
if request.method == 'GET':
queryset = queryset.using('read-only')
Indeed, the search results are dont with a GET, while the admin actions are done with a POST.
I will have to check this
This is not exactly you are looking for How to improved query performance in Django admin search on related fields (MySQL), but it can help to optimize the queries.

Getting random object of a model with django-rest-framework

In my Django project I need to provide a view to get random object from a model using django-rest-framework. I had this ListAPIView:
class RandomObject(generics.ListAPIView):
queryset = MyModel.objects.all().order_by('?')[:1]
serializer_class = MyModelSerializer
...
It worked fine but order_by('?') takes a lot of time when launched on big database. So I decided to use usual Python random.
import random
def pick_random_object():
return random.randrange(1, MyModel.objects.all().count() + 1)
class RandomObject(generics.ListAPIView):
queryset = MyModel.objects.all().filter(id = pick_random_object())
...
I found out a strange thing when tried to use this. I launched Django development server and sent some GET requests, but I got absolutely the same object for all of the requests. When dev server restarted and another set of requests sent I'm getting another object, but still absolutely the same one for all of requests - even if random.seed() was used first of all. Meanwhile, when I tried to get a random object not via REST but via python manage.py shell I got random objects for every time I called pick_random_object().
So everything looks good when using shell and the behavior is strange when using REST, and I have no clue of what's wrong.
Everything was executed on Django development server (python manage.py runserver).
As #CarltonGibson noticed, queryset is an attribute of RandomObject class. Hence it cached and cannot be changed any later. So if you want to make some changeable queryset (like getting random objects at every request) in some APIView, you must override a get_queryset() method. So instead of
class RandomObject(generics.ListAPIView):
queryset = MyModel.objects.all().filter(id = pick_random_object())
...
you should write something like this:
class RandomObject(generics.ListAPIView):
#queryset = MyModel.objects.all().filter(id = pick_random_object())
def get_queryset(self):
return MyModel.objects.all().filter(id = pick_random_object())
Here pick_random_object() is a method to get random id from the model.
Since it's an attribute of the class, your queryset is getting evaluated and cached when the class is loaded, i.e. when you start the dev server.
I'd try pulling a list of primary keys, using values_list() — the flat=True example does exactly what you need. Ideally cache that. Pick a primary key at random and then use that to get() the actual object when you need it.
So, how would that go?
I'd define a method on the view. If you forget the caching, the implementation might go like this:
# Lets use this...
from random import choice
def random_MyModel(self):
"""Method of RandomObject to pick random MyModel"""
pks = MyModel.objects.values_list('pk', flat=True).order_by('id')
random_pk = choice(pks)
return MyModel.objects.get(pk=random_pk)
You might then want to cache the first look up here. The caching docs linked above explain how to do that. If you do cache the result look into the db.models signals to know when to invalidate — I guess you'd post_save, checking the created flag, and post_delete.
I hope that helps.

Django admin changelist - restrict where field is NULL

I'm setting up a new Django app and need to customize the Admin for a given table by restricting the records where a field is NULL. Basically a built-in, permanent filter.
Seems like changelist_view needs to be overridden, but I'm uncertain what that change would look like.
There's no code to be included as I'm not overriding changelist_view right now.
You can override default manager, but it has a drawback that you'll have to explicitly specify original manager in all your queries:
class MyManager(models.Manager):
def get_query_set(self):
return super(MyManager, self).get_query_set().filter(my_field__isnull=False)
class MyModel(models.Model):
objects = MyManager()
all_objects = models.Manager()
MyModel.all_objects.all() # all objects including those having my_field=None
There's not really a good way to do this at present - there is in fact an open ticket on Django requesting the ability to customise what QuerySet gets used for the admin views - see ticket #10761. Antony's solution will work in the short term, but you may have to wait until that ticket is resolved for a proper solution.
I've decided to use limited queryset manager as objects. For ModelAdmin I've copied queryset() from django/contrib/admin/options.py and changed _default_manager by mine unlimited queryset manager. Simple!