How to optimize django Admin using queryset cache - django

im trying to optimize dbase queries from the admin, by caching the results and using in coming queries. But - i still see dbase request for each entry.
i have a model like this:
class actions(models.Model):
user = models.ForeignKey(MyUser)
action = ...
class Myuser(Models.Model):
name = models.CharField()
company = models.ForeignKey(Companies)
in the admin change_list, i'd like to see a table with:
user action company_name
so i defined my admin as follows:
class ActionsAdmin(admin.ModelAdmin):
list_display = ('user','action','company_name')
...
def company_name(self,instance):
return instance.user.company
When i run this, i see that for each user, there is a query to the company model to extract the company's name. However, since there are not many companies and in many cases, users perform many actions once after the other, i want to query for all companies once and then use the cached result instead of accessing the dbase for each entry.
How can i do that?

Use list_select_related to specify the relation to select with the query, so in your case:
class ActionsAdmin(admin.ModelAdmin):
list_display = ('user','action','company_name')
list_select_related = ('user__company', )
...
def company_name(self,instance):
return instance.user.company
Edit:
You can also specify the field directly on list_display, without the need for custom method.
It should handle list_select_related for you (at least according to the docs linked above). If assuming your Companies model has a name field:
class ActionsAdmin(admin.ModelAdmin):
list_display = ('user','action','user__company__name')

Related

Django (DRF) ManyToMany field choices / limit

Working with Django REST Framework I am wondering if it's possible to limit the choices / options of a ManyToMany field on a model to a specific QuerySet?
Using the models below (scroll down to see models), I am curious if it's possible to define this limit in the definition of the model, to achieve the following:
# Having the following Employee instance
emp = Employee(...)
# Should return only the instances with value 'case' in EmployeeSubstitute.type field
emp.substitute_case.all()
# Should return only the instances with value 'phone' in EmployeeSubstitute.type field
emp.substitute_phone.all()
Models:
class Employee(models.Model):
substitute_case = models.ManyToMany(through=EmployeeSubstitute, ...)
substitute_phone = models.ManyToMany(through=EmployeeSubstitute, ...)
class EmployeeSubstitute(models.Model):
from = models.ForeignKey(Employee, ...)
to = models.ForeignKey(Employee, ...)
type = models.CharField(choices=..., ...) # choose between type 'case' and 'phone'
I see that there's the limit_choices_to parameter, but that's not what I am looking for, since that only effects the options shown when using a ModelForm or the admin.
Well, ManyToManyField returns related objects and as docs state
By default, Django uses an instance of the Model._base_manager manager
class when accessing related objects (i.e. choice.question), not the
_default_manager on the related object. This is because Django needs to be able to retrieve the related object, even if it would otherwise
be filtered out (and hence be inaccessible) by the default manager.
If the normal base manager class (django.db.models.Manager) isn’t
appropriate for your circumstances, you can tell Django which class to
use by setting Meta.base_manager_name.
Base managers aren’t used when querying on related models, or when
accessing a one-to-many or many-to-many relationship. For example, if
the Question model from the tutorial had a deleted field and a base
manager that filters out instances with deleted=True, a queryset like
Choice.objects.filter(question__name__startswith='What') would include
choices related to deleted questions.
So if I read it correctly, no, it's not possible.
When you do queries and have through in your ManyToManyField, Django complains you should run these queries on your through model, rather than the "parent". I can't find it in the docs but I remember seeing it a few times.
substitute_case and substitute_phone is something that belongs to substitute and it is it's type. So just do that instead of creating those columns in Employee.
from django.db import models
class SubstituteTypes(models.TextChoices):
case = "case", "case"
phone = "phone", "phone"
class EmployeeSubstituteQueryset(models.QuerySet):
def from_employee(self, e):
return self.filter(_from=e)
def case(self):
return self.filter(type=SubstituteTypes.case)
def phone(self):
return self.filter(type=SubstituteTypes.phone)
class Employee(models.Model):
substitute = models.ManyToManyField(through='EmployeeSubstitute', to='self')
class EmployeeSubstitute(models.Model):
_from = models.ForeignKey(Employee, on_delete=models.CASCADE, related_name='a')
to = models.ForeignKey(Employee, on_delete=models.PROTECT, related_name='b')
type = models.CharField(choices=SubstituteTypes.choices, max_length=5, db_index=True)
objects = EmployeeSubstituteQueryset.as_manager()
Then, once you get your emp object (or only its id), you can do
EmployeeSubstitute.objects.from_employee(emp).case().all()
which is designed in Django philosophy.

Django change multiple model entries

I have a model containing various entries tied to one user and I want to give the user a view where he can review these entries, select some of them and perform an action on the selection. something like the admin intereface has. I have tried UpdateView but that is for one entry only. ListView doesn't like that the model returns multiple entries for one identificator. Is there something else I could use?
EDIT:
Below is the model, I am talking about. A user will have multiple model entries and I just want a view that lists these multiple entries and allows the user to perform a bulk action on them, like delete ...
class UserData(models.Model):
class Meta:
app_label = "app"
user_id = models.IntegerField()
name = models.CharField(_("Name"),max_length=100)
latdeg = models.IntegerField(_('Latitude'))
latmin= models.IntegerField(_('Latitude'), validators=[validate_60])
londeg = models.IntegerField(_('Longitude'))
lonmin= models.IntegerField(_('Longitude'), validators=[validate_60])
main = models.BooleanField()
def __unicode__(self):
return user_id + "-" + self.name
I think what you are looking for is inlineformset_factory
Since you have not given any example, I suggest you look at the example of One author, multiple books as given in this SO post.

Django ORM access User table through multiple models

views.py
I'm creating a queryset that I want to serialize and return as JSON. The queryset looks like this:
all_objects = Program.objects.all()
test_data = serializers.serialize("json", all_objects, use_natural_keys=True)
This pulls back everything except for the 'User' model (which is linked across two models).
models.py
from django.db import models
from django.contrib.auth.models import User
class Time(models.Model):
user = models.ForeignKey(User)
...
class CostCode(models.Model):
program_name = models.TextField()
...
class Program(models.Model):
time = models.ForeignKey(Time)
program_select = models.ForeignKey(CostCode)
...
Question
My returned data has Time, Program, and CostCode information, but I'm unable to query back the 'User' table. How can I get back say the 'username' (from User Table) in the same queryset?
Note: I've changed my queryset to all_objects = Time.objects.all() and this gets User info, but then it doesn't pull in 'CostCode'. My models also have ModelManagers that return the get_by_natural_key so the relevant fields appear in my JSON.
Ultimately, I want data from all four models to appear in my serialized JSON fields, I'm just missing 'username'.
Here's a picture of how the JSON object currently appears in Firebug:
Thanks for any help!
It seems a bit heavyweight at first glance but you could look at using Django REST Framework:
http://www.django-rest-framework.org/api-guide/serializers#modelserializer
You can define and use the serializer classes without having to do anything else with the framework. The serializer returns a python dict which can then be easily dumped to JSON.
To get all fields from each related model as nested dicts you could do:
class ProgramSerializer(serializers.ModelSerializer):
class Meta:
model = Program
depth = 2
all_objects = Program.objects.all()
serializer = ProgramSerializer(all_objects, many=True)
json_str = json.dumps(serializer.data)
To customise which fields are included for each model you will need to define a ModelSerializer class for each of your models, for example to output only the username for the time.user:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', )
class TimeSerializer(serializers.ModelSerializer):
"""
specifying the field here rather than relying on `depth` to automatically
render nested relations allows us to specify a custom serializer class
"""
user = UserSerializer()
class Meta:
model = Time
class ProgramSerializer(serializers.ModelSerializer):
time = TimeSerializer()
class Meta:
model = Program
depth = 1 # render nested CostCode with default output
all_objects = Program.objects.all()
serializer = ProgramSerializer(all_objects, many=True)
json_str = json.dumps(serializer.data)
What you really want is a "deep" serialization of objects which Django does not natively support. This is a common problem, and it is discussed in detail here: Serializing Foreign Key objects in Django. See that question for some alternatives.
Normally Django expects you to serialize the Time, CostCode, Program, and User objects separately (i.e. a separate JSON array for each) and to refer to them by IDs. The IDs can either be the numeric primary keys (PKs) or a "natural" key defined with natural_key.
You could use natural_key to return any fields you want, including user.username. Alternatively, you could define a custom serializer output whatever you want there. Either of these approaches will probably make it impossible to load the data back into a Django database, which may not be a problem for you.

djangorestframework: Filtering in a related field

Basically, I want to filter out inactive users from a related field of a ModelSerializer. I tried Dynamically limiting queryset of related field as well as the following:
class MySerializer(serializers.ModelSerializer):
users = serializers.PrimaryKeyRelatedField(queryset=User.objects.filter(active=True), many=True)
class Meta:
model = MyModel
fields = ('users',)
Neither of these approaches worked for just filtering the queryset. I want to do this for a nested related Serializer class as a field (but couldn't even get it to work with a RelatedField).
How do I filter queryset for nested relation?
I'll be curious to see a better solution as well. I've used a custom method in my serializer to do that. It's a bit more verbose but at least it's explicit.
Some pseudo code where a GarageSerializer would filter the nested relation of cars:
class MyGarageSerializer(...):
users = serializers.SerializerMethodField('get_cars')
def get_cars(self, garage):
cars_queryset = Car.objects.all().filter(Q(garage=garage) | ...).select_related()
serializer = CarSerializer(instance=cars_queryset, many=True, context=self.context)
return serializer.data
Obviously replace the queryset with whatever you want. You don't always need the to give the context (I used it to retrieve some query parameters in the nested serializer) and you probably don't need the .select_related (that was an optimisation).
One way to do this is to create a method on the Model itself and reference it in the serializer:
#Models.py
class MyModel(models.Model):
#...
def my_filtered_field (self):
return self.othermodel_set.filter(field_a = 'value_a').order_by('field_b')[:10]
#Serialziers.py
class MyModelSerialzer(serializers.ModelSerializer):
my_filtered_field = OtherModelSerializer (many=True, read_only=True)
class Meta:
model = MyModel
fields = [
'my_filtered_field' ,
#Other fields ...
]
Another way to avoid the SerializerMethodField solution and therefore still allow writing to the serializer as well would be to subclass the RelatedField and do the filtering there.
To only allow active users as values for the field, the example would look like:
class ActiveUsersPrimaryKeyField(serializers.PrimaryKeyRelatedField):
def get_queryset(self):
return super().get_queryset().filter(active=True)
class MySerializer(serializers.ModelSerializer):
users = ActiveUsersPrimaryKeyField(many=True)
class Meta:
model = MyModel
fields = ('users',)
Also see this response.
Note that this only restricts the set of input values to active users, though, i.e. only when creating or updating model instances, inactive users will be disallowed.
If you also use your serializer for reading and MyModel already has a relation to a user that has become inactive in the meantime, it will still be serialized. To prevent this, one way is to filter the relation using django's Prefetch objects. Basically, you'll filter out inactive users before they even get into the serializer:
from django.db.models import Prefetch
# Fetch a model instance, eagerly prefetching only those users that are active
model_with_active_users = MyModel.objects.prefetch_related(
Prefetch("users", queryset=User.objects.filter(active=True))
).first()
# serialize the data with the serializer defined above and see that only active users are returned
data = MyModelSerializer(model_with_active_users).data

django - how do I join the results of multiple models having foreign key the same user?

I have multiple models that point to the same user like this:
class Model1(moldels.Model):
user = models.ForeignKey(User)
title = models.CharField()
...
class Model2(moldels.Model):
user = models.ForeignKey(User)
...
class Model3(moldels.Model):
user = models.ForeignKey(User)
...
What to be able to do a search/filter on title field on Model1 and join the results from the other models that have the same user. Only Moldel1 will return multiple results for the same user. Model2 and Model3 will always return one result for every user (like one avatar and one profile). - Thank you!
It depends on what you mean by "join". You can access one from any other though user and the related_name of the model (by default: <lowercase class name>_set), e.g.:
model1_instance.user.model2_set.all()
Or you can use the user instance to access each directly:
user.model1_set.all()
user.model2_set.all()
user.model3_set.all()
You can query those models through the user as well, with query traversals:
User.objects.filter(model1__title='some title')
Finally, for the models that have only one instance, such as one profile per user, you should really be using OneToOneField instead of ForeignKey. If you do that, then you can use select_related on any reverse relation that is a OneToOneField (true SQL JOIN), e.g.:
User.objects.filter(model1__title='some title').select_related('model2', 'model3')
However, select_related will not work on reverse foreign key relationship or many-to-many relationships. For that, you'll have to wait until Django 1.4 drops with prefetch_related.
If you want get a queryset, this is impossible. You cannot get a queryset of various models.
But I think that you want something like this:
objs1 = Model.objects.filter(title__icontains='YOUR FILTER')
users = objs1.values('user').distinct()
objs2 = Model2.objects.filter(user__in=users)
objs3 = Model3.objects.filter(user__in=users)
objs = list(objs1)
objs.extend(list(objs2))
objs.extend(list(objs3))
return objs