Django get rid of duplicated queries in nested models - python

I have models as shown below,
class Manufacturer(models.Model):
name = models.CharField(max_length=100)
class Car(models.Model):
manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
name = models.CharField(max_length=300)
#property
def latest_variant(self):
return self.carvariant_set.last()
class CarVariant(models.Model):
car = models.ForeignKey(Car, on_delete=models.CASCADE)
name = models.CharField(max_length=300)
and I am making a query to get the latest variant of all cars, I am getting much duplicated queries.
I couldn't eliminate it with prefetch_related
Car.objects.all().prefetch_related('carvariant_set')
How can I eliminate the duplicated queries?

If you use .prefetch_related it will populate the carvariant_set value, but only for a .all() query, not for a .last(), that will trigger a new query.
What we can do is define a property like:
class Car(models.Model):
manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
name = models.CharField(max_length=300)
#property
def latest_variant(self):
items = getattr(self, '_latest_variants', ())
if items:
return items[-1]
return self.carvariant_set.last()
Then we can prefetch the related object with:
from django.db.models import Prefetch
Car.objects.prefetch_related(
Prefetch(
'carvariant_set',
queryset=CarVariant.objects.order_by('pk'),
to_attr='_latest_variants'
)
)

To get rid of duplicates you use "distinct()".
For example Car.objects.all().prefetch_related('carvariant_set').distinct(). You can read about it here:
https://docs.djangoproject.com/en/3.2/ref/models/querysets/#django.db.models.query.QuerySet.distinct
sometimes you might need to tell the "distinct" function which fields make an object distinct. By default it's the id, but you can do something like "distinct('name')" in order to avoid getting 2 instances with the same name for example.

Related

Django filter for ForeignKey included in ManyToMany

I don't know if I'm tired or if the answer is simple but I don't see it
I have a model like this:
class Company(models.Model):
name = models.CharField(max_length=32, unique=True)
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
company_admin = models.ManyToManyField(Company)
# ...
class GroupData(models.Model):
name = models.CharField(max_length=32)
company = models.ForeignKey(
Company, on_delete=models.CASCADE)
If in a view I do:
print(request.user.userprofile.company_admin.all())
...I get a QS with of all the companies to which the user belongs, and are Ok.
What I need is to get a filter for GroupData where company 'is included' into this QS in order to get only objects of GroupData based on user 'company_admin' right.
Can anyone help me to understand how to get this filter?
Great thanks
If I understand the requirement:
user_company_pks = users_profile.company_admin.all().values_list( 'pk', flat=True)
filtered_groupdata = Groupdata.objects.filter( company__pk__in=user_company_pks )
The first returns a list of pks. The second filters groupdata objects that have a company with a pk in that list
It might be worth investigating whether
filtered_groupdata = Groupdata.objects.filter( company_id__in=user_company_pks )
(a) works and (b) is significantly any more efficient. The value of a foreign key (company_id) is usually the pk of the related object.

Foreign keys and serializers in django rest

class Patient(models.Model):
user = models.OneToOneField(User, related_name='patient', on_delete=models.CASCADE)
id_type = models.CharField(max_length=300)
id_number = models.CharField(max_length=300)
creation_date = models.DateField(default=datetime.date.today)
class Allergie(models.Model):
name = models.CharField(max_length=300, default="X")
class PatientAllergies(models.Model):
patient = models.ForeignKey(Patient, related_name="patient_allergies", on_delete=models.CASCADE)
allergie = models.ForeignKey(Allergie, on_delete=models.CASCADE, null=True)
professional_contract = models.ForeignKey(ProfessionalContract, null=True ,on_delete=models.CASCADE)
Is it possible to retrieve a patient objecto with a property that is a list of all his allergies, including name and id with these models?
you have the PatientAllergies as a chain,
so
patientAllergies = PatientAllergies.objects.get(patient.id_number='0000')
patientAllergies.allergie #you get the single allergie model connect with it, take care it is a foreignKey so it is singolar and not many
patientAlleriges.patient.user #will give you access to all the data of the user
You can achieve this with prefetch_related and Prefetch like so:
Patient.objects.prefetch_related(
Prefetch('patient_allergies__allergie', to_attr='allergies')
)
EDIT: Just learned that to_attr will not work on multiple levels of prefetch. Another approach I can think of is use a model property for Patient that returns its related allergies like this:
class Patient(models.Model):
#property
def allergies(self):
return Allergie.objects.filter(patientallergies_set__patient=self)
Then in your serializer, the allergies field can use the Allergies serializer

Django combine foreign keys in a single queryset

I have a model in a Django application that is being referenced by multiple other models as a ForeignKey.
What I am looking for is for a way to create a single queryset for all objects of this class that are being referenced as ForeignKey by the rest of the classes based on some criteria.
I am not even sure if this is possible, but I thought about asking anyway.
class Person(models.Model):
pass
class Book(models.Model):
year_published = models.PositiveIntegerField()
author = models.ForeignKey(Person)
class MusicAlbum(models.Model):
year_published = models.PositiveIntegerField()
producer = models.ForeignKey(Person)
recent_books = Book.objects.filter(year_published__gte=2018)
recent_music_albums = MusicAlbum.objects.filter(year_published__gte=2018)
# How can I create a **single** queryset of the Person objects that are being referenced as `author` in `recent_books` and as `producer` in `recent_music_albums`?
Thanks for your time.
I don't have Django in front of me at the moment, but what about something like:
class Person(models.Model):
pass
class Book(models.Model):
year_published = models.PositiveIntegerField()
author = models.ForeignKey(Person, related_name='books')
class MusicAlbum(models.Model):
year_published = models.PositiveIntegerField()
producer = models.ForeignKey(Person, related_name='albums')
Person.objects.filter(books__year_published__gte=2018, albums__year_published__gte=2018)
Or, if you have to do those first two queries anyway,
Person.objects.filter(books__in=recent_books, albums__in=recent_music_albums)
You will have on Person model instances a RelatedManager for Books and MusicAlbums. Probably they will still have the default names book_set and musicalbum_set since you didn't override them.
You can use these to find the books/music albums associated with one person instance:
persons_books = person.book_set.all()
persons_musicalbums = person.musicalbum_set.all()
And similarly you can generate the relevant queryset from the model manager:
qs = Person.objects.exclude(book=None).exclude(musicalbum=None)
Same can be achieved by this :
person = Person.objects.latest('book__year_published', 'musicalbum__year_published')
or
personList = Person.objects.all().order_by('-book__year_published', '-musicalbum__year_published')

How to add queryset to ManyToMany relationship?

I have following models:
class EnMovielist(models.Model):
content_ID = models.CharField(max_length=30)
release_date = models.CharField(max_length=30)
running_time = models.CharField(max_length=10)
actress = models.CharField(max_length=300)
series = models.CharField(max_length=30)
studio = models.CharField(max_length=30, null=True)
director = models.CharField(max_length=30)
def __str__(self):
return self.content_ID
class EnActress(models.Model):
name = models.CharField(max_length=100, null=True)
movielist = models.ManyToManyField(EnMovielist, related_name='movies')
def __str__(self):
return self.name
I got error when I try to this in Django shell,
b = EnActress.objects.values_list('name', flat=True)
a = EnMovielist.objects.filter(actress__contains=b).values_list('content_ID')
b.movielist.add(a)
AttributeError: 'QuerySet' object has no attribute 'movielist'
How can I django queryset add into many-to-many field?
I have no idea why this is happening.. Any help appreciated! :)
You should not be using values_list if you intend to add a new relation afterwards. From the docs:
values() and values_list() are both intended as optimizations for
a specific use case: retrieving a subset of data without the
overhead of creating a model instance
[Emphasis mine]
It's hard to tell what you're up to without having a good description of what you want to achieve.
You should call m2m add from instance and adding entity should be also model instance. Otherwise your expression doesn't make sense.
b = EnActress.objects.get(pk=some_pk) # get an instance, not queryset
a = EnMovielist.objects.get(pk=some_pk) # also instance
b.movielist.add(a)

one-to-many field in Django

I've got this class:
class PurchaseOrder(models.Model):
product = models.CharField(max_length=256)
dollar_amount = models.FloatField()
item_number = models.AutoField(primary_key=True)
I'm trying to make it so that 'product' has a one to many field. In other words, whenever I am adding a new item in django's default admin page. I want to be able to have the option of putting multiple 'product' for the same dollar amount and item number.
In response to Hedde van der Heide's comment. Would this be how you implement this?
class PurchaseOrder(models.Model):
product = models.ManyToManyField(Order)
dollar_amount = models.FloatField()
item_number = models.AutoField(primary_key=True)
class Order(models.Model):
order_product = models.CharField(max_length =256)
def __unicode__(self):
return self.order_product
No, your edit is incorrect. That would imply a purchase order could belong to many orders and vice versa, which makes no sense. You want a simple ForeignKey from PurchaseOrder to Order.

Categories

Resources