Is it possible to prefetch model with one query in this case? - django

Is it possible to prefetch Models B to Model A with one query or with minimal queries. I'm confused. Thanks.
from django.db import models
class ModelA(models.Model):
pass
class ModelB(models.Model):
pass
class ModelC(models.Model):
model_a = models.ForeignKey(ModelA, related_name="models_a", on_delete=models.CASCADE)
models_b = models.ManyToMany(ModelB, through="ModelD")
class ModelD(models.Model):
model_c = models.ForeignKey(ModelC, on_delete=models.CASCADE)
model_b = models.ForeignKey(ModelB, on_delete=models.CASCADE)
I'm do something like that, and it is work. But seems a bit ugly.
models_a_list = ModelsA.objects.all()
model_d_qs = ModelD.objects.select_related("model_c", "model_b")
model_d_map = defaultdict(list)
for d_item in model_d_qs:
model_d_map[d_item.model_c.model_a.id].append(d_item.model_b)
for a_item in models_a_list:
settatr(a_item, "model_b_set", model_d_map.get(a_item.id))
return models_a_list

Related

DRY fields in Django models

I know about using inheritance and abstract models in reusing common fields in different models.
I'd like to know if the following approach is possible instead of inheritance and mixins.
from django.db import models
common_modified_by = models.CharField()
class Author(models.Model):
name = models.CharField()
modified_by = common_modified_by
class Book(models.Model):
title = models.CharField()
modified_by = common_modified_by
Will the above code work? Why or why not?
The issue with reusing the same field in multiple models is that the model attribute of the field will be set to the last model where the field is defined
from django.db import models
common_modified_by = models.CharField(max_length=20)
class Author(models.Model):
name = models.CharField(max_length=20)
modified_by = common_modified_by
class Book(models.Model):
title = models.CharField(max_length=20)
modified_by = common_modified_by
The field now has Book as it's model even when you get the field from the Author model. This can be problematic
>>> Book._meta.get_field('modified_by').model
<class 'foo.models.Book'>
>>> Author._meta.get_field('modified_by').model
<class 'foo.models.Book'>
One issue could be using the field as the target of a foreign key django.db.models.fields.related seems to use this model attribute quite a lot
It's also used when generating subqueries

Exporting a field from another model, using foreign key

I've just set up the whole import-export thing and I just can't make it export a field from another model, using the foreign key.
models.py
from django.db import models
from django.contrib.auth.models import User
from datetime import date
from .validators import validate_file_size
# Create your models here.
class CORMeserii(models.Model):
CodCOR = models.CharField(max_length=25, primary_key=True, unique=True)
MeserieCor = models.CharField(max_length=50, unique=True)
def __str__(self):
return str(self.CodCOR + " - " + self.MeserieCor)
class Meta:
verbose_name_plural = "CORuri"
class Oferta(models.Model):
solicitant = models.ForeignKey(User, on_delete=models.CASCADE)
cor = models.ForeignKey(CORMeserii, on_delete=models.CASCADE)
dataSolicitare = models.DateField(default=date.today)
locuri = models.IntegerField()
agentEconomic = models.CharField(max_length=50)
adresa = models.CharField(max_length=150)
dataExpirare = models.DateField()
experientaSolicitata = models.CharField(max_length=200)
studiiSolicitate = models.CharField(max_length=200)
judet = models.CharField(max_length=20)
localitate = models.CharField(max_length=25)
telefon = models.CharField(max_length=12)
emailContact = models.EmailField(max_length=40)
rezolvata = models.BooleanField(default=False)
def __str__(self):
return str(self.cor)
admin.py
from django.contrib import admin
from .models import Oferta, CORMeserii
from import_export import resources
from import_export.admin import ImportExportMixin, ImportExportModelAdmin
import tablib
# Register your models here.
class CorImEx(resources.ModelResource):
class Meta:
model = CORMeserii
class CorAdmin(ImportExportMixin, admin.ModelAdmin):
list_display = ('CodCOR', 'MeserieCor')
resource_class = CorImEx
class CorImExAdmin(ImportExportModelAdmin):
resource_class = CorImEx
class OferteImEx(resources.ModelResource):
class Meta:
model = Oferta
fields = ('id', 'solicitant', 'cor', 'oferta.cor.MeserieCor')
class OfertaAdmin(ImportExportMixin, admin.ModelAdmin):
list_display = ('id', 'solicitant', 'dataExpirare', 'dataSolicitare')
resource_class = OferteImEx
class OferteImExAdmin(ImportExportModelAdmin):
resource_class = OferteImEx
admin.site.register(Oferta, OfertaAdmin)
admin.site.register(CORMeserii, CorAdmin)
You can see it in the OferteImEx class - the field 'oferta.cor.MeserieCor'.
I want to export the MeserieCor field from the model CORMeserii - but I can't figure it out.
I tried with: 'oferta.cor.MeserieCor',
cor.MeserieCor', 'MeserieCor' (even though the last 2 ones don't make sense to me at all).
Is there any way to export that field somehow, even though it is from another model? (I'm pretty sure there is but I can't figure it out)
Thanks.
In Django you use double underscore (__) to follow relationship in lookups. It's in the documentation:
Django offers a powerful and intuitive way to “follow” relationships in lookups, taking care of the SQL JOINs for you automatically, behind the scenes. To span a relationship, just use the field name of related fields across models, separated by double underscores, until you get to the field you want.
Link: Lookups that span relationship
There is even an example in the django import export documentation how to follow relationship:
When defining ModelResource fields it is possible to follow model relationships:
class BookResource(resources.ModelResource):
class Meta:
model = Book
fields = ('author__name',)
Source: https://django-import-export.readthedocs.io/en/latest/getting_started.html#customize-resource-options

Django Combine Two Tables Through a Third

If I have these 3 models
from django.db import models
class ModelA(models.Model):
name = models.CharField(max_length=50)
class ModelB(models.Model):
tag = models.CharField(max_length=50)
class ModelC(models.Model):
a = models.ForeignKey(ModelA)
b = models.ForeignKey(ModelB)
What is the best way to get all of my ModelA's and their associated ModelB.property's? I can't figure out a good way to do it without a raw query.
Ideally I'd be able to transform it into:
[
{
"name": "foo",
"tags": ["tag1", "tag2"]
}
....
]
Use related_name on the ForeignKeys
from django.db import models
class ModelA(models.Model):
name = models.CharField(max_length=50)
class ModelB(models.Model):
property = models.CharField(max_length=50)
class ModelC(models.Model):
a = models.ForeignKey(ModelA, related_name="theas")
b = models.ForeignKey(ModelB, related_name="thebs")
To get all ModelB instances that relate to an instance of ModelA
a_inst = ModelA.objects.get(foo=bar)
ModelB.objects.filter(thebs__a=a_inst)

prefetch_related with nested m2m

Given the following models I would like to improve the performance of the filter to avoid unnecessary calls to DB.
class A(models.Model):
name = models.CharField()
b = ManyToManyField(B)
class B(models.Model):
c = ManyToManyField(C)
class C(models.Model):
d = ManyToManyField(D)
class D(models.Model):
foo = models.TextField()
How could I achieve this with prefetch_related?
''.join(A.objects.filter(b__c__d=self.d).prefetch_related('??').values_list('name', flat=True))
It works in a similar fashion to your filter (b__c__d). Just prefetch:
''.join(A.objects.filter(b__c__d=self.d).prefetch_related('b__c__d').values_list('name', flat=True))

django rest framework relation field

I have models as below:
class Bus(models.Model):
bid = models.CharField(_("Bus unique id"), max_length=45)
plate_number = models.CharField(_("Plate number"), max_length=45)
class BusData(models.Model):
bus = models.ForeignKey(Bus, on_delete=models.CASCADE)
timestamp = models.DateTimeField()
speed = models.IntegerField(_("Speed"), null=True, blank=True)
I want to retrieve the bus object with the busdata together, thus I use below serializer classes.
class BusSerializer(serializers.ModelSerializer):
class Meta:
model = Bus
fields = ('bid', 'plate_number', 'busdata_set')
class BusDataSerializer(serializers.ModelSerializer):
class Meta:
model = BusData
So far, the BusSerializer output looks like this:
In [8]: bus1 = Bus.objects.create(bid='1',plate_number='X101')
In [10]: from django.utils import timezone
In [15]: bus_data1 = BusData.objects.create(bus=bus1,timestamp=timezone.now(),speed=100)
In [18]: bus_data2 = BusData.objects.create(bus=bus1,timestamp=timezone.now(),speed=200)
In [20]: bus_s = BusSerializer(bus1)
In [21]: bus_s.data
Out[21]:
OrderedDict([('bid', u'1'),
('plate_number', u'X101'),
('busdata_set', [10, 11])])
As you can see, the busdatas associated with this bus are already taken in busdata_set fields.
However, what I want here is just get the latest busdata record to return (sort by the timestamp field desc, and return the first record), not all the busdata record.
Something looks like this,
OrderedDict([('bid', u'1'),
('plate_number', u'X101'),
('busdata', 11)])
I've no idea how to achieve this.
Any thoughts and code snippets would be appreciate.
Thanks so much!
There are many ways to do this. The simplest would be to have a property on the Bus class that points to the latest BusData object, and then retrieve it in the serializer.
class Bus(models.Model):
bid = models.CharField(_("Bus unique id"), max_length=45)
plate_number = models.CharField(_("Plate number"), max_length=45)
#property
def bus_data(self):
return self.busdata_set.latest('timestamp')
Then make the change in the serializer:
class BusSerializer(serializers.ModelSerializer):
bus_data = serializers.Field()
class Meta:
model = BusData
As Django-Rest-Framework Serialize Relations docs says You can define the queryset in your PrimaryKeyRelatedField. So you could try like this:
class BusSerializer(serializers.ModelSerializer):
busdata_set = serializers.PrimaryKeyRelatedField(queryset=BusData.objects.create(bus=bus1,timestamp=timezone.now(),speed=200), many=True)
class Meta:
model = Bus
fields = ('bid', 'plate_number', 'busdata_set')