Effecient way to fetch related models count in Django ORM - python

I'm working on Django 1.10 and PostgreSQL9.6
I have two models in my project: Order and Customer. Also I'm using Django's auth.User to store login credentials.
Here is a code snipped:
from django.contrib.auth.models import User
from django.db import models
class Order(models.Model):
created_by = models.ForeignKey(User, null=True, on_delete=models.SET_NULL, related_name='created_orders')
# ... other fields ...
class Customer(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
# ... other fields ...
Now, I need to show a table of customers and show a number of orders created by each of them.
The staight forward code is:
for customer in Customer.objects.all():
print(customer.user.created_orders.count())
Now I need to avoid N+1 problem and make Django to fetch all data with constant number of queries.
I've tried to write something like:
query = Customer.objects.select_related('user').annotate(
orders_count=Count('user.created_orders')
)
But this gives me an error like Cannot resolve keyword 'user.created_orders' into field.
Can you help me with this?

You should not use a dot (.) here, but two consecutive underscores (__):
query = Customer.objects.select_related('user').annotate(
orders_count=Count('user__created_orders')
)
Note that you do not need to .select_related('user') in order to annotate a queryset. If you however plan to use the .user later in your logic, it can indeed boost performance.

Related

How To Separate Objects In Django Admin?

I have an app called 'Product' with the following models.py:
class Product(models.Model):
product_id = models.CharField(max_length=50)
pub_date = models.DateTimeField(default=datetime.now)
title = models.CharField(max_length=255)
price = models.DecimalField(max_digits=8, decimal_places=2)
user = models.ForeignKey(User, on_delete=models.CASCADE)
featured = models.BooleanField(default=False)
I want to have two separate sections in Django Admin: Products and Featured Products, depending if featured = True or False.
So by default all products are listed under the Products section. But if featured = True they will be moved to Featured Products section. Can you please help me how to do that? Thanks in advance.
Three steps:
Write a proxy model for model Product.
Change the default manager to only returns featured products.
Register your proxy model in the admin like any other model.
You can read more about it here: Using Proxy Models to Customize the Django Admin
There are a couple of ways to do this. The simplest perhaps is to create a database view, and then encapsulate it using a django model. You can create a view like so in your database console:
CREATE VIEW view_name AS
SELECT columns
FROM tables
[WHERE conditions];
Once you have done that, you can reference the view in django like so:
class FeaturedProduct(modes.Model):
attr1 = models.CharField()
class Meta:
managed = False
db_table = '<name of your view here>'
Make sure that managed is set to False. Here is the relevant documentation for that. You want to do that because django is not creating this model for you, but rather you are creating it yourself.
Another way to do this would be to create a custom Manager. These managers allow you to modify the objects attribute of your model, allowing you to set a queryset that you want. I think you'd want to take a look at the Manager documentation and you can take a look at defining custom querysets for your objects.

How to get relevant data from a parent model in Django?

I have 2 database tables, Prospects and Profile. They're related by a One-to-one foreign key relationship
Model.py
class Prospect(models.Model):
profile = models.OneToOneField(Profile, on_delete=models.CASCADE, null=True, blank=True, related_name="profile_prospects")
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="profile")
In my view.py
prospects = prospects[:50]
I have a QuerySet of prospects (prospects is working correctly, exactly what I want), and I would like to retrieve a QuerySet of profiles based on the database model above. I tried
profiles = Profile.objects.filter(profile_prospects__in = prospects)
It returns an error of
django.db.utils.ProgrammingError: subquery has too many columns
How can I get all the relevant profiles?
You have spaces in
profiles = Profile.objects.filter(profile_prospects__in = prospects)
Sorry, I might be confused here. But isn't the profile automatically inherited by the prospect since it's a one-to-one relationship?
When you have the prospect you should be able to get the profile like this
prospect.profile
Again, I might have gotten the question wrong.

Django query extended USER table

I have a profile table which have a foreign key of the user.
class Profile(models.Model):
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
image = models.CharField(max_length=100)
user = models.ForeignKey(User)
I also have a COMMENT table which have a foreign key of the user.
class Comment(models.Model):
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
content = models.TextField()
user = models.ForeignKey(User)
I want to query the table COMMENT and also I want to get the image of the user which is in the PROFILE table. How can I query this in most effective way in django?.
thanks
If you are okay with changing ForeignKey to OneToOneField on Profile model then you can do it like,
Comment.objects.all().select_related('user__profile')
The above one selecting additional related-object data when it executes its query. This is a performance booster which results in a single more complex query but means later use of foreign-key relationships won’t require database queries.
Otherwise you can get it this way
for comment in Comment.objects.all():
print comment.user.profile_set.all()[0].image
If you're using Django = <1.4 the following is best practice:
comment = Comment.objects.get(pk=1)
comment.user.get_profile().image
Following the deprecation of native Profile model support (Django 1.5+) the following is still possible:
comment = Comment.objects.get(pk=1)
comment.user.profile.image
Django 1.5+ introduces custom auth models so you can do the following:
comment = Comment.objects.get(pk=1)
comment.user.image
At very least you should change your ForeignKey to a OneToOne relation for the user column on Profile, as Django =< 1.4 expects only one User Profile to be associated with a User.
References:
Django 1.4 get_profile: https://docs.djangoproject.com/en/1.4/topics/auth/#django.contrib.auth.models.User.get_profile
Django 1.5+ extending the user model:
https://docs.djangoproject.com/en/1.9/topics/auth/customizing/#extending-the-existing-user-model

Django: allow user to add fields to model

I am just starting with Django and want to create a model for an application.
I find Djangos feature to
- automatically define validations and html widget types for forms according to the field type defined in the model and
- define a choice set for the field right in the model
very usefull and I want to make best use of it. Also, I want to make best use of the admin interface.
However, what if I want to allow the user of the application to add fields to the model? For example, consider a simple adress book. I want the user to be able to define additional atributes for all of his contacts in the admin settings, i.e. add a fax number field, so that a fax number can be added to all contacts.
from a relational DB perspective, I would have a table with atributes (PK: atr_ID, atr_name, atr_type) and an N:N relation between atributes and contacts with foreign keys from atributes and contacts - i.e. it would result in 3 tables in the DB. right?
but that way I cannot define the field types directly in the Django model. Now what is best practice here? How can I make use of Djangos functionality AND allow the user to add aditional/custom fields via the admin interface?
Thank you! :)
Best
Teconomix
i would suggest storing json as a string in the database, that way it can be as extendable as you want and the field list can go very long.
Edit:
If you are using other damn backends you can use Django-jsonfield. If you are using Postgres then it has a native jsonfield support for enhanced querying, etc.
Edit 2:
Using django mongodb connector can also help.
I've used this approach, first seen in django-payslip, to allow for extendable fields. This provides a structure for adding fields to models, from which you can allow users to add/edit through standard view procedures (no admin hacking necessary). This should be enough to get you started, and taking a look at django-payslip's source code (see the views) also provides view Mixins and forms as an example of how to render to users.
class YourModel(models.Model):
extra_fields = models.ManyToManyField(
'your_app.ExtraField',
verbose_name=_('Extra fields'),
blank=True, null=True,
)
class ExtraFieldType(models.Model):
"""
Model to create custom information holders.
:name: Name of the attribute.
:description: Description of the attribute.
:model: Can be set in order to allow the use of only one model.
:fixed_values: Can transform related exta fields into choices.
"""
name = models.CharField(
max_length=100,
verbose_name=_('Name'),
)
description = models.CharField(
max_length=100,
blank=True, null=True,
verbose_name=_('Description'),
)
model = models.CharField(
max_length=10,
choices=(
('YourModel', 'YourModel'),
('AnotherModel', 'AnotherModel'), # which models do you want to add extra fields to?
),
verbose_name=_('Model'),
blank=True, null=True,
)
fixed_values = models.BooleanField(
default=False,
verbose_name=_('Fixed values'),
)
class Meta:
ordering = ['name', ]
def __unicode__(self):
return '{0}'.format(self.name)
class ExtraField(models.Model):
"""
Model to create custom fields.
:field_type: Connection to the field type.
:value: Current value of this extra field.
"""
field_type = models.ForeignKey(
'your_app.ExtraFieldType',
verbose_name=_('Field type'),
related_name='extra_fields',
help_text=_('Only field types with fixed values can be chosen to add'
' global values.'),
)
value = models.CharField(
max_length=200,
verbose_name=_('Value'),
)
class Meta:
ordering = ['field_type__name', ]
def __unicode__(self):
return '{0} ({1}) - {2}'.format(
self.field_type, self.field_type.get_model_display() or 'general',
self.value)
You can use InlineModelAdmin objects. It should be something like:
#models.py
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=100)
class ContactType(models.Model):
name = models.CharField(max_length=100)
class Contact(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
contact_type = models.ForeignKey(ContactType, on_delete=models.CASCADE)
value = models.CharField(max_length=100)
#admin.py
from django.contrib import admin
class ContactInline(admin.TabularInline):
model = Contact
class PersonAdmin(admin.ModelAdmin):
inlines = [
ContactInline,
]
By the way... stackoverflow questions should contain some code. You should try to do something before asking a question.

Django Rest Framework not saving foreign key for a small number of requests

I am using Django Rest Framework to provide API to a mobile app. I have two models, Order and User. Order has a foreign key relation to User.
For about 1% or so of all my order objects, the User field is null. I've been testing this behavior using cURL.
If I do a cURL without a user object, it tells me "This field is required".
If done with a wrong user object, it tells me that the object does not exist. Both of these are the intended and expected behaviors.
I'm trying to figure out how it is possible for some of the Order objects to be saved without a user field. Is there something I'm not taking into account?
My views:
class OrderList (generics.ListCreateAPIView):
model = Order
serializer_class = OrderSerializer
And serializer:
class OrderSerializer (serializers.ModelSerializer):
user = serializers.SlugRelatedField(slug_field = 'user')
partial = True
class Meta:
model = Order
Models:
class User (models.Model):
uid = models.CharField(max_length =200, unique=True)
class Order (models.Model):
uid = models.ForeignKey (User, related_name = "orders", verbose_name = "User",blank=True, null=True)
You could use two different ModelSerializer classes, one for creation, that makes sure, that an Order object can't be created without a related User and one for updating orders, that passes required=False to the related field's constructor, so that you still can save existing orders that haven't a related User.
Try adding default=None to your models.ForeignKey declaration. You could also just create an anonymous user in the users table and when the user isn't specified it could set the anonymous user instead.

Categories