Find parent data using Django ORM - python

I have 3 tables
AllTests
ReportFormat
Parameter
AllTests has reportFormat as ForeignKey with one to one relation.
class AllTests(models.Model):
id=models.AutoField(primary_key=True)
name=models.CharField(max_length=200)
reportFormat=models.ForeignKey(ReportFormat)
.....
.....
class Meta:
db_table = 'AllTests'
Parameter table has reportFormat as ForeignKey with one too many relations. Means one report format has many parameters.
class Parameter(models.Model):
id=models.AutoField(primary_key=True)
name=models.CharField(max_length=200)
reportFormat=models.ForeignKey(ReportFormat)
.....
.....
class Meta:
db_table = 'AllTests
ReportFormat Table:-
class ReportFormat(models.Model):
id=models.AutoField(primary_key=True)
.....
.....
class Meta:
db_table = 'ReportFormat'
I want to make a query on the parameter model that return parameter data with the related test data. Please suggest me a better way for the same.
my current query is like this.
from django.db.models import (
Sum, Value, Count, OuterRef, Subquery
)
data = Parameter.objects.filter(isDisable=0).values('id', 'name', 'reportFormat_id').annotate(
test=Subquery(Alltsets.objects.filter(reportFormat_id=OuterRef('reportFormat_id').values('id', 'name')))
)

Since, report format column of both the tables refer to the same column of reportformat table, maybe you can directly relate them with something like below.
select * from parameter inner join alltests on parameter.reportformat=alltests.reportformat
Maybe in ORM something like this?
Parameter.objects.filter().annotate(alltests_id=F('reportformat__alltests__id'),alltests_name=F('reportformat__alltests__name'),....other fields)

Fetch all Parameter and AllTests objects. Find the parameter's alltests object using reportFormat field. Add the corresponding data to the parameter dict.
Solution:
parameter_qs = Parameter.objects.all().select_related('reportFormat')
alltests_qs = AllTests.objects.all().select_related('reportFormat')
data = []
for parameter in parameter_qs:
item = {
'id': parameter.id,
'name': parameter.name,
'alltests': {}
}
# fetch related AllTests object using `reportFormat` field
alltests_obj = None
for alltests in alltests_qs:
if parameter.reportFormat == alltests.reportFormat:
alltests_obj = alltests
break
if alltests_obj is not None:
item['alltests'] = {
'id': alltests_obj.id,
'name': alltests_obj.name
}
data.append(item)

Related

How to make Django values() exclude some fields?

I have multiple models which has fields like "created_at" "updated_at" which I don't want to get with objects.values().
Does Django has any way to exclude fields in values()?
I know people refer to defer(), but it doesn't return QuerySet<Dict> like values() instead returns QuerySet<Model>.
I tried objects.defer("created_at", "updated_at").values(), but it includes those 2 deferred fields in the resulting Dict.
I see defer().query only selecting the non-exluded fields in the SQL, but using defer(..).values() resets the deferred fields and selects all fields.
I cannot specify which field I want, since different model has different fields, I can only specity which fields I don't want. So I cannot use values('name', 'age', ...)
I'm planning to use a CustomeManager, which I can use in all model.
Example:
class CustomManager(models.Manager):
def values_excluded(self):
return self.values() # somehow exlude the fields and return QuerySet<Dict>
class ExampleModel(models.Model):
name = models.CharField(max_length=20)
age = models.IntegerField()
created_at = models.DateTimeField()
updated_at = models.DateTimeField()
objects = CustomManager()
ExampleModel.objects.values_excluded()
Is there any way in Django or do I have to manually delete those keys from the resulting Dict from values()?
esclude_fields = ['created_at', 'updated_at']
keys = [f.name for f in Model._meta.local_fields if f.name not in esclude_fields]
queryset.values(*keys)
This should work:
class CustomManager(models.Manager):
def values_excluded(self, *excluded_fields):
included_fields = [f.name for f in self.model._meta.fields if f.name not in excluded_fields]
return self.values(*included_fields)

Django rest framework - Optimizng serialization of nested related field

Given the following models:
class Model_A:
...
class Model_B:
...
class Model_C:
model_a = ForeignKey(Model_A, related_name='c_items')
model_b = ForeignKey(Model_B)
...
And the following model serializers setup:
class Model_A_Serializer:
class Model_C_Serializer:
class Meta:
model = Model_C
fields = ( 'model_b', ... )
c_items = Model_C_Serializer(many=True)
class Meta:
model = Model_A
fields = ( 'c_items', ... )
And a basic vieweset:
class Model_A_Viewset:
model = Model_A
queryset = model.objects.all()
serializer_class = Model_A_Serializer
...
When the user POST's the following JSON payload to create an instance of Model_A along with instances of Model_C:
{
'c_items': [
{
'model_b': 1
},
{
'model_b': 2
},
{
'model_b': 3
}
]
}
Note: Model_B instances with IDs 1, 2, and 3 already exist, but no Model_A and no Model_C instances exist in the above example.
Then I noticed that django seems to execute the following queries when serializing the incoming data:
SELECT ... FROM Model_B WHERE id = 1;
SELECT ... FROM Model_B WHERE id = 2;
SELECT ... FROM Model_B WHERE id = 3;
This seems unnecessary to me as a single SELECT ... FROM Model_B WHERE id IN (1,2,3) would do the job.
How do I go about optimizng this?
I have tried to modify the queryset in the viewset above like so:
queryset = model.objects.prefetch_related('c_items__model_b').all()
But this did not make a difference in the number of queries being executed.
been a while since I django'd but I'm pretty sure you need to prefetch the relations and the relations relations, and I'm pretty sure prefetch is for many to many or reverse foreign keys, while select is for direct foreign keys, which means you can save one query by using select_related instead:
model.objects.prefetch_related('c_items').select_related('c_items__model_b').all()

Context processor for filter empty categories

In my blog the posts are divided based on the categories. I've excluded the category named Uncategorized from the list of categories and now I want exclude the categories that are empty.
Into a context_processor I've the query below that working fine:
MyCategory.objects.exclude(category_name="Uncategorized")
I've tried with a query like this:
def myblog_menu(request):
myblog_menu_link = MyCategory.objects.exclude(category_name="Uncategorized").filter(category_name__category_set__isnull=True)
return {
'myblog_menu_link': myblog_menu_link,
}
but I see this error:
Unsupported lookup 'category_set' for CharField or join on the field
not permitted.
How I can fix it?
models.py
class MyCategory(models.Mode):
category_name = models.CharField(...)
.
.
:
class BlogPost(models.Mode):
title = models.CharField(...)
category = models.ForeignKey(MyCategory, related_name="category_set", ....)
.
.
:
You can exclude categories that are empty with:
MyCategory.objects.exclude(
category_name='Uncategorized'
).filter(
category_set__isnull=False
).distinct()
This will thus generate a query that looks like:
SELECT DISTINCT mycategory.*
FROM mycategory
INNER JOIN blogpost ON mycategory.id = blogpost.category_id
WHERE NOT (mycategory.category_name = 'Uncategorized')
AND blogpost.id IS NOT NULL
We here thus specify that if we make a LEFT OUTER JOIN with BlogPost, that should be empty. The query here is an INNER JOIN, which is an optimization, since if we filter out nullables, then it is clear that we do not have to generate those in the first place.
That being said, I strongly advice you to alter the related_name of your category foreign key to blogpost_set, or just leave it as it is. The related_name is the name of the object in reverse, a Category has no category_set, it has a set of blogposts:
class BlogPost(models.Mode):
title = models.CharField(...)
category = models.ForeignKey(MyCategory, related_name='blogpost_set', ....)
In that case the query is:
MyCategory.objects.exclude(
category_name='Uncategorized'
).filter(
blogpost_set__isnull=False
).distinct()

Django adds extra columns to group by

I have the following queryset:
Pago.objects.filter(created__range=(self.inicio, self.fin)).values('tipo__nombre').annotate(monto=Sum('monto'))
And it is producing the following SQL:
SELECT "invoice_tipopago"."nombre", SUM("invoice_pago"."monto") AS "monto" FROM "invoice_pago" INNER JOIN "invoice_tipopago" ON ( "invoice_pago"."tipo_id" = "invoice_tipopago"."id" ) WHERE "invoice_pago"."created" BETWEEN 2015-01-01 00:00:00-06:00 AND 2015-06-04 14:18:00-06:00 GROUP BY "invoice_tipopago"."nombre", "invoice_pago"."modified", "invoice_pago"."created" ORDER BY "invoice_pago"."modified" DESC, "invoice_pago"."created" DESC
It is adding the extra modified and created columns that I am not specifying, and i would like to know how to avoid it. It should be noticed that Pago is a derived from the django-extensions TimeStampedModel class.
Thanks in advance.
I took a look at TimeStampedModeland it set a default ordering in the class meta:
class TimeStampedModel(models.Model):
""" TimeStampedModel
An abstract base class model that provides self-managed "created" and
"modified" fields.
"""
created = CreationDateTimeField(_('created'))
modified = ModificationDateTimeField(_('modified'))
class Meta:
get_latest_by = 'modified'
ordering = ('-modified', '-created',)
# ^^^^^^^^^^^^^^^^^^^^^^^^^^
abstract = True
see in github
You could overwrite that providing another filter to order by (like tipo__nombre or monto), ex:
Pago.objects.filter(
created__range=(self.inicio, self.fin)
).values(
'tipo__nombre'
).annotate(
monto=Sum('monto')
).order_by(
'tipo__nombre'
)

django admin inline many to many custom fields

Hi I am trying to customize my inlines in django admin.
Here are my models:
class Row(models.Model):
name = models.CharField(max_length=255)
class Table(models.Model):
rows = models.ManyToManyField(Row, blank=True)
name = models.CharField(max_length=255)
def __unicode__(self):
return self.name
and my admin:
class RowInline(admin.TabularInline):
model = Table.rows.through
fields = ['name']
class TableAdmin(admin.ModelAdmin):
inlines = [
RowInline,
]
exclude = ('rows',)
However I get this error
ImproperlyConfigured at /admin/table_app/table/1/
'RowInline.fields' refers to field 'name' that is missing from the
form.
How is that possible ?
class RowInline(admin.TabularInline):
model = Table.rows.through
fields = ['name']
This presents a problem because Table.rows.through represents an intermediate model. If you would like to understand this better have a look at your database. You'll see an intermediate table which references this model. It is probably named something like apname_table_rows. This intermeditate model does not contain the field, name. It just has two foreign key fields: table and row. (And it has an id field.)
If you need the name it can be referenced as a readonly field through the rows relation.
class RowInline(admin.TabularInline):
model = Table.rows.through
fields = ['row_name']
readonly_fields = ['row_name']
def row_name(self, instance):
return instance.row.name
row_name.short_description = 'row name'
class TableAdmin(admin.ModelAdmin):
inlines = [
RowInline,
]
exclude = ('rows',)
Django can not display it as you expected. Because there is an intermediary join table which joins your tables. In your example:
admin.py:
class RowInline(admin.TabularInline):
model = Table.rows.through # You are not addressing directly Row table but intermediary table
fields = ['name']
As the above note, model in RowInline addressing following table in your database, not Your Row table and model
table: your-app-name_table_row
--------------------------------
id | int not null
table_id | int
row_id | int
You can think it like there is an imaginary table in your model that joins the two tables.
class Table_Row(Model):
table = ForeignKey(Table)
row = ForeignKey(Row)
So if you edit your Inline as following
class RowInline(admin.TabularInline):
model = Table.rows.through # You are not addressing directly Row table but intermediary table
fields = ['row', 'table']
you will not see any error or exception. Because your model in RowInline addresses an intermediary table and that table do have those fields. Django can virtualize the imaginary table Table_Row up to here and can handle this.
But we can use relations in admin, with using __. If your code do have a ForeignKey relation instead of ManyToManyField relation, then following be valid in your admin
class Row(models.Model):
name = models.CharField(max_length=255)
class Table(models.Model):
rows = models.ForeignKey(Row, blank=True)
name = models.CharField(max_length=255)
def __unicode__(self):
return self.name
and your admin:
class RowInline(admin.TabularInline):
model = Table
fields = ['rows__name']
Because you will have real Models and djnago can evaluate __ relation on them
But if you try that in your structure:
class Row(models.Model):
name = models.CharField(max_length=255)
class Table(models.Model):
rows = models.ManyToManyField(Row, blank=True)
name = models.CharField(max_length=255)
def __unicode__(self):
return self.name
and your admin:
class RowInline(admin.TabularInline):
model = Table.rows.through
fields = ['row__name']
it will raise Exception! Because you do not have real table in your model, and django can not evaluate __ relations on virtual models it designs on top of its head.
Conclusion:
In your Inlines addressing ManyToMany relations, you are dealing with an imaginary intermediary model and you can not use fields orexclude attributes on that because your imaginary model do not have those fields and django can not handle relations ober that imaginary table. Following will be acceptable
class RowInline(admin.TabularInline):
model = Table.rows.through
# No fields or exclude declarations in here
and django will display combo boxes for your virtual intermediary table options and add a fancy green + sign to add new records, but you will can not have inline fields to add new records directly to your database within the same single page. Djnago can not handle this on a single page.
You can try creating real intermediary table and show it using through, but thats totally a longer job and I do not test it to see its results.
Update: There is also the reason why django do not let something like that. Consider following:
Table | Table_Row | Row
-----------+-----------+---------
Start 5 | |
Step 1 5 | | 1
Step 2 5 | 5-1 | 1
At the beginning, You have a table with no related Rows, you want to add a row to the table... For joining a row with a table, you must first create a row so you execute step 1. After You do create your row, you can create Table_Row record to join these two. So in contains more than a single database insertion. Django crew may avoid such usage since it contains multiple inserts and operation is related more tables.
But this is just an assumption on the reason of the behavior.
In your admin.py try this
class RowInline(admin.TabularInline):
model = Table.rows.through
list_display = ('name',)
class TableAdmin(admin.ModelAdmin):
inlines = [
RowInline,
]
readonly_fields = ('rows',)

Categories