When "select_related" is needed? - django

In my project , Each candidate can takepart in some assessments,each assessment has some tests, each test has some questions in it and candidates should answer the questions
at last scores of the questions are saved in question_score and test_score table
I need to get some values of field and use them
I write a method for question_result table, to get them
but i dont know if it is needed to use select_related or not
if it is needed how can i use it ?
Assessment:
class Assessment(BaseModel):
company = models.ForeignKey(
'company.Company',
on_delete=models.CASCADE,
related_name='assessments',
)
title = models.CharField(max_length=255)
job_role = models.ForeignKey(
JobRole,
on_delete=models.PROTECT,
related_name='assessments',
blank=True,
null=True,
)
tests = models.ManyToManyField(
'exam.Test',
related_name='assessments',
blank=True,
through='TestOfAssessment',
)
candidates = models.ManyToManyField(
'user.User',
related_name='taken_assessments',
blank=True,
through='candidate.Candidate'
)
def __str__(self):
return self.title
Test:
class Test(BaseModel):
class DifficultyLevel(models.IntegerChoices):
EASY = 1
MEDIUM = 2
HARD = 3
company = models.ForeignKey(
'company.Company',
on_delete=models.PROTECT,
related_name='tests',
null=True,
blank=True,
)
questions = models.ManyToManyField(
'question.Question',
related_name='tests',
blank=True,
help_text='Standard tests could have multiple questions.',
)
level = models.IntegerField(default=1, choices=DifficultyLevel.choices)
title = models.CharField(max_length=255)
summary = models.TextField()
def __str__(self):
return self.title
Question :
class Question(BaseModel):
company = models.ForeignKey(
'company.Company',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='company_questions',
)
question_text = models.TextField()
def __str__(self):
return truncatewords(self.question_text, 7)
TestResult:
class TestResult(BaseModel):
candidate = models.ForeignKey(
'Candidate',
on_delete=models.CASCADE,
related_name='test_results',
)
test = models.ForeignKey(
'exam.Test',
on_delete=models.CASCADE,
)
test_score = models.DecimalField(default=0.00, max_digits=5, decimal_places=2)
def __str__(self):
return f'{self.candidate.user.email} - {self.test.title}'
Candidate :
class Candidate(BaseModel):
assessment = models.ForeignKey(
'assessment.Assessment',
on_delete=models.CASCADE,
)
user = models.ForeignKey(
'user.User',
on_delete=models.CASCADE,
)
is_rejected = models.BooleanField(default=False)
def __str__(self):
return f'{self.user.email} - {self.assessment.title}'
Company :
class Company(models.Model):
manager = models.ForeignKey('user.User', on_delete=models.CASCADE, related_name='user_companies')
name = models.CharField(max_length=255)
city = models.ForeignKey('company.City', null=True, on_delete=models.SET_NULL)
address = models.CharField(max_length=255, blank=True, null=True)
def __str__(self):
return self.name
QuestionResult :
class QuestionResult(BaseModel):
test = models.ForeignKey(
'TestResult',
on_delete=models.CASCADE,
related_name='question_results',
)
question = models.ForeignKey(
'question.Question',
on_delete=models.CASCADE,
related_name='results',
)
result = models.TextField(
null=True,
blank=True,
)
answer_score = models.DecimalField(default=0.00, max_digits=5, decimal_places=2)
def __str__(self):
return f'{self.test.candidate.user.email} - {self.question}'
def text_variables(self):
email = self.test.candidate.user.email
company_name = self.test.test.company.name
assessment_name = self.test.candidate.assessment.title
candidate_first_name = self.test.candidate.user.first_name
job_name = self.test.candidate.assessment.job_role
user_fullname = User.full_name
data = dict(
job_name=job_name,
company_name=company_name,
email=email,
assessment_name=assessment_name,
candidate_first_name=candidate_first_name,
job_name=job_name,
user_fullname = user_fullname
)
return data
I wrote the def text_variables(self): method to fill the data dictionary and use it somewhere else
it work properly but i dont know if it needed to use selected_related or not
something like this (it does not work)
def text_variables(self):
question_result_object = QuestionResult.objects.filter(id=self.id).select_related(
"test__candidate","test__test__company","test__candidate__assessment")
email = question_result_object.test.candidate.user.email
company_name = question_result_object.test.test.company.name
assessment_name = question_result_object.test.candidate.assessment.title
candidate_first_name = question_result_object.test.candidate.user.first_name
job_name = question_result_object.test.candidate.assessment.job_role
data = dict(
job_name=job_name,
company_name=company_name,
email=email,
assessment_name=assessment_name,
candidate_first_name=candidate_first_name,
job_name=job_name,
user_fullname = user_fullname
)
return data
the error is :
File "E:\work\puzzlelity\talent-backend\candidate\models.py", line 385, in report_to_candidate_email_text_variables
email = question_result_object.test.candidate.user.email
AttributeError: 'QuerySet' object has no attribute 'test'
[03/Jan/2023 17:59:00] "POST /api/v1/candidatures/183f8432-ea81-4099-b211-3b0e6475ffab/submit-answer/ HTTP/1.1" 500 123319
I dont know how should i use the select_related

It's never required. It optimizes querysets, especially in ListViews.
Consider your Assessment model. It has ForeignKey fields company and job_role. If you simply fetch
assessment = Assessment.objects.get( id=something)
and then refer to assessment.company, that causes a second DB query to fetch the company object. And then a third if you refer to assessment.job_role.
You can reduce these three queries to one by using
assessment = Assessment.objects.select_related(
'company', 'job_role') .get( id=something)
which does a more complex query to retrieve all the data.
Where it matters is in a list view where you iterate over a large number of assessment objects in Python or in a template. For example, if object_list is assessment.objects.all() and there are 300 of them, then
{% for assessment in object_list %}
... stuff ...
{{assessment.company.name }}
...
{% endfor %}
Will hit the DB 300 times, once for each company! If you use select_related, all 300 companies linked to the 300 assessments will be retrieved in a single DB query. which will be very noticeably faster.
I'd strongly recommend installing Django Debug Toolbar in your development project. Then click on the SQL option on any view, and you can see what SQL was required, and in particular how many SQL queries were performed and whether there were batches of repetetive queries which mean there's a trivial optimisation to be made.

Related

Retrieve items from Many-to-Many relationship in Django query

Models.py
class SalesOrderItems(models.Model):
item = models.ForeignKey(MasterItems, on_delete=models.CASCADE)
item_quantity = models.IntegerField(default=0)
class SalesOrder(models.Model):
delivery_method_choice = (('Full-Truck Load', 'Full-Truck Load'), ('Part-Truck Load', 'Part-Truck Load'))
status_choices = (('On Hold', 'On Hold'),('Ready to Dispatch', 'Ready to Dispatch'),('Dispatched', 'Dispatched'))
owner = models.ForeignKey(Teacher, on_delete=models.CASCADE, related_name='so_owner')
client = models.ForeignKey(MasterClient, on_delete=models.CASCADE, related_name='so_client')
reference_no = models.CharField(max_length=500, blank=True, null=True)
date = models.DateField(default=datetime.date.today)
shipment_date = models.DateField(default=datetime.date.today)
delivery_method = models.CharField(max_length=500, default='Full-Truck Load', choices=delivery_method_choice)
items = models.ManyToManyField(SalesOrderItems, related_name='items_so', blank=True, null=True)
status = models.CharField(max_length=500, default='On Hold', choices=status_choices)
origin = models.CharField(max_length=255)
destination = models.CharField(max_length=255)
I want to retrieve the items of a particular sales order by django query
My attempt to get the items:
items = []
for i in sales_orders:
so = SalesOrder.objects.filter(pk=i)[0]
print("items",so.items)
Output:
items classroom.SalesOrderItems.None
How can I get the list of items in a particular SalesOrder ??
sales_orders = SalesOrder.objects.prefetch_related('items').filter(**sales_filter_here)
for sales_order in sales_orders:
for sales_order_item in sales_order.items.all():
print(sales_order_item)
this query will work for you
SalesOrder.objects.filter(id=1).values_list('items__item__item_name')
below is my simple Masteritems model
class MasterItems(models.Model):
item_name = models.CharField(max_length=50)
def __str__(self):
return self.item_name
by this way you will get every item name in queryset. this is my simple output
<QuerySet [('rice',), ('pepsi',), ('Computer',)]>

How to print foreignkey Model relation?

If in My model, Moneybook have a many moneylogs.
so, I design a model
Moneybook/models.py
name = models.CharField(max_length=30, default="행복한 여행!")
owner = models.ForeignKey(
user_models.User, on_delete=models.CASCADE, related_name="owner")
companion = models.ManyToManyField(
user_models.User, related_name="companion", blank=True)
country = CountryField()
location = models.CharField(max_length=50, blank=True)
start_date = models.DateTimeField(default=NOW)
end_date = models.DateTimeField(default=NOW)
Moneylog/models.py
moneybook = models.ForeignKey(
moneybook_models.Moneybook, on_delete=models.CASCADE, related_name="moneybooks")
payer = models.ForeignKey(
user_models.User, on_delete=models.CASCADE, related_name="payer")
dutch_payer = models.ManyToManyField(
user_models.User, related_name="dutch_payer")
price = models.IntegerField()
category = models.CharField(max_length=10)
memo = models.TextField()
If i want to load all the moneylogs in the each belonging moneybook. how can i load it?
I guess...
def moneybook_detail(request, pk):
moneylogs=moneylog.filter(moneylog.moneybook.id=request.moneybook.id)
return render(request, "moneybooks/detail.html")
but error occured.
moneylogs = moneylog.filter(request.moneybook.id=request.moneybook.id)
SyntaxError: keyword can't be an expression
You can either query the Moneylog table with the following query by using the double underscore __ to filter based on referenced object fields.
moneylogs = MoneyLog.filter(moneybook__id=<<<MoneyBookID_GOES_HERE>>>)
Or by using the internal ReverseManyToOneManager in Django
just by using
moneybook = MoneyBook.objects.get(pk=<<<<MoneyBookID_GOES_HERE>>>>)
moneylogs = moneybook.moneylog_set.all() # all() to get all money logs
# You can do filter(...) on it too to filter the moneylogs too.
this will return all money logs related to the money book.
In general, you have to use double underscore __ to reference foreign key columns in filters:
def moneybook_detail(request, pk):
moneylogs=moneylog.filter(moneybook__id=request.moneybook.id)
return render(request, "moneybooks/detail.html")

How to specify GROUP BY field in Dajngo ORM?

I have the following working SQL statement:
SELECT id FROM ops_kpitarget WHERE (site_id = 1 AND validFrom <= "2019-08-28") GROUP BY kpi_id HAVING validFrom = (MAX(validFrom))
But I cannot get this to work inside Django ORM.
The best I got was the code below, but then the database is complaining that it is missing a GROUP BY clause to make HAVING work.
How can I get the same query with specifying "kpi_id" as the GROUP BY clause using Djangos ORM? Any ideas?
KpiTarget.objects
.filter(validFrom__lte=fromDate)
.values("id", "kpi")
.filter(validFrom=Max("validFrom"))
... which translates to:
SELECT "ops_kpitarget"."id", "ops_kpitarget"."kpi_id" FROM "ops_kpitarget" WHERE "ops_kpitarget"."validFrom" <= 2019-08-14 HAVING "ops_kpitarget"."validFrom" = (MAX("ops_kpitarget"."validFrom"))
I played around with annotate but this is not really giving me what I want...
Update:
Some background: I have 3 tables: Kpi, KpiTarget, and KpiTargetObservation.
Kpi holds all general information regarding the KPI like name, typeetc.
KpiTarget stores target values defined for several different sites. These target values can change over time. Hence, I have included the combination of MAX() and validFrom <= (some date) to determine the latest valid target for any given KPI.
KpiTargetObservation stores the individual observations per defined KPI target. It just holds the link to KpiTarget, the date of the observation, and the observation value.
The final queries I need to build will have to give me something like the following:
give me all known KPIs per given site
tell me the most recent target value for the KPIs you found
get me any known observation that is related to the identified kpi targets
I am struggling with the 2nd query, and specifically how to get this working using Djangos ORM. I could just escape to RAW SQL, but I would prefer to not to, if possible.
The models:
class KpiCategory(models.Model):
name = models.CharField(max_length=255)
def __str__(self):
return self.name
class Kpi(models.Model):
KPI_KIND_CHOICES = [("BOOL", "Boolean"), ("FLOAT", "Float"), ("STRING", "String")]
# firstCreated = models.DateField(auto_now_add=True)
# firstCreatedBy = models.OneToOneField(User, on_delete=models.CASCADE)
# lastEdited = models.DateField(auto_now=True)
# lastEditedBy = models.OneToOneField(User, on_delete=models.CASCADE)
name = models.CharField(max_length=255)
category = models.ForeignKey(KpiCategory, on_delete=models.CASCADE)
kind = models.CharField(max_length=150, choices=KPI_KIND_CHOICES)
def __str__(self):
return self.name
class KpiTarget(models.Model):
# firstCreated = models.DateField(auto_now_add=True)
# firstCreatedBy = models.OneToOneField(User, on_delete=models.CASCADE)
# lastEdited = models.DateField(auto_now=True)
# lastEditedBy = models.OneToOneField(User, on_delete=models.CASCADE)
kpi = models.ForeignKey(Kpi, on_delete=models.CASCADE, related_name="kpiTargetSet")
targetDouble = models.DecimalField(
max_digits=20, decimal_places=15, blank=True, null=True
)
targetBool = models.BooleanField(blank=True, null=True)
targetStr = models.CharField(max_length=255, blank=True)
site = models.ForeignKey(Site, on_delete=models.CASCADE)
validFrom = models.DateField()
def __str__(self):
return str(self.kpi)
class KpiObservation(models.Model):
# firstCreated = models.DateField(auto_now_add=True)
# firstCreatedBy = models.OneToOneField(User, on_delete=models.CASCADE)
# lastEdited = models.DateField(auto_now=True)
# lastEditedBy = models.OneToOneField(User, on_delete=models.CASCADE)
kpiTarget = models.ForeignKey(
KpiTarget, on_delete=models.CASCADE, related_name="kpiObservationSet"
)
observed = models.DateField()
observationDouble = models.DecimalField(
max_digits=20, decimal_places=15, blank=True, null=True
)
observationBool = models.BooleanField(blank=True, null=True)
observationStr = models.CharField(max_length=255, blank=True)
def __str__(self):
return str(self.observed)
KpiTarget.objects.filter(validFrom__lte=fromDate).annotate(validFrom=Max("validFrom")).order_by('kpi__id').values("id", "kpi")

Django: Adding Many to Many field

I am currently struggling to assign a many-to-many field to my newly created objects. Anyone knows what I am doing wrong?
Note: I have one solution in mind which is first creating the ticket, and then afterwards trying to assign it. Might that be the way to do it?
#Create ticket_tax
assign_event = Event.objects.all()
for event in assign_event:
TicketTax.objects.create(
event=event,
name=lorem,
percentage=0.19,
)
# Create tickets
price_gross = ['40.60', '30.30', '100.40', ]
name = ['Early Bird', 'Regular Ticket', 'Last Minute Ticket', ]
assign_event = Event.objects.all().first()
assign_ticket_tax = TicketTax.objects.all().first()
for i in range(len(price_gross)):
Ticket.objects.create(
event=assign_event,
ticket_tax=assign_ticket_tax.add(),
price_gross=price_gross[i],
name=name[i],
description='ABC',
start_at='2018-05-26 18:12:58.556925+02',
end_at='2018-05-29 18:12:58.556925+02',
quantity=100,
status='On sale',
)
models.py
class TicketTax(models.Model):
event = models.ForeignKey(
Event,
on_delete=models.PROTECT,
related_name='ticket_taxes'
)
name = models.CharField(max_length=100)
percentage = models.DecimalField(
max_digits=5,
decimal_places=4
)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
class Ticket(models.Model):
event = models.ForeignKey(
Event,
on_delete=models.PROTECT,
related_name='tickets'
)
ticket_tax = models.ManyToManyField(TicketTax, blank=True)
price_gross = models.DecimalField(
max_digits=25,
decimal_places=2
)
name = models.CharField(max_length=100)
description = models.TextField(blank=True, null=True)
start_at = models.DateTimeField()
end_at = models.DateTimeField()
quantity = models.PositiveIntegerField()
status = models.CharField(
max_length=8,
choices=TicketStatus.CHOICES
)
is_archived = models.BooleanField(default=False)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
Note: I have one solution in mind which is first creating the ticket, and then afterwards trying to assign it. Might that be the way to do it?
That is exactly correct. You can't instantiate an object and assign a M2M in one create statement--since both objects need to exist in the database. This inside the for loop should (roughly) do what your code above is trying to do:
ticket = Ticket.objects.create(
event=assign_event,
price_gross=price_gross[i],
name=name[i],
description='ABC',
start_at='2018-05-26 18:12:58.556925+02',
end_at='2018-05-29 18:12:58.556925+02',
quantity=100,
status='On sale',
)
ticket.ticket_tax.add(TicketTax.objects.all().first())

Django: Are multiple Generic Relations in one Model bad design?

I have a model with multiple Generic Relations that has become very complicated to use in my templates. The model is a 'Gig' or musical event that takes place at a 'Venue' and/or a 'Festival' and has a 'Musician' and/or an 'Ensemble'.
Where it gets complicated is that each 'Gig' has a presenter, promoter and an agent. These are setup as generic relations to other models such as 'PresenterCompany'. A Presenter company could be a promoter, presenter, or agent, or all of them for the same gig. Here are the models (simplified for ref):
class Gig(models.Model):
description = models.CharField(max_length=100, blank=True)
date = models.DateTimeField()
venue = models.ForeignKey(Venue)
festival = models.ForeignKey(Festival, blank = True, null=True)
musician = models.ManyToManyField(Musician, blank=True)
ensembles = models.ManyToManyField(Ensemble, blank = True)
presenter_content_type = models.ForeignKey(ContentType,
limit_choices_to={"model__in": ("Individual", "ArtsOrganization",'Presenter', "BookingAgent","Festival", "OtherOrganization","PresenterCompany", "Venue")}, related_name = "Presenter Type", verbose_name = "Presenter",blank=True, null=True)
presenter_id = models.IntegerField(db_index=True, blank=True, null=True, verbose_name='Presenter ID')
presenter = generic.GenericForeignKey('presenter_content_type','presenter_id')
promoter_content_type = models.ForeignKey(ContentType,
limit_choices_to={"model__in": ("Individual", "ArtsOrganization","BookingAgent","Presenter", "Festival", "OtherOrganization","PresenterCompany", "Venue")}, related_name = "promotor", verbose_name='Promoter Type', blank=True, null=True)
promoter_id = models.IntegerField(db_index=True, blank=True, null=True, verbose_name='Promoter ID')
promoter = generic.GenericForeignKey('promoter_content_type','promoter_id')
agent_content_type = models.ForeignKey(ContentType,
limit_choices_to={"model__in": ("Individual", "BookingAgent")}, related_name="agent", verbose_name='Agent Type', blank=True, null=True)
agent_id = models.IntegerField(db_index=True, blank=True, null=True, verbose_name='Agent ID')
agent = generic.GenericForeignKey('agent_content_type','agent_id')
class PresenterCompany(models.Model):
name = models.CharField(max_length=70)
address =GenericRelation(Address)
presented_gig = GenericRelation('Gig',
content_type_field='presenter_content_type',
object_id_field='presenter_id',
related_name='presenter_presented_gig'
)
promoted_gig = GenericRelation('Gig',
content_type_field='promoter_content_type',
object_id_field='promoter_id',
related_name='presenter_promoted_gig'
)
booked_gig = GenericRelation('Gig',
content_type_field='promoter_content_type',
object_id_field='promoter_id',
related_name='presenter_booked_gig'
)
The main issue is that when I try to get all of the gigs for a presenter company, I have to write three different for loops for each role i.e. {% for gig in presentercompany.presented_gig.all %}, and so on... This seems like redundant code.
Is there a better way to structure this such as using intermediary models for presenter, promoter, and agent? Thanks for your advice!
Generic relationships can definitely be hard to deal with. I would only use them when there is no other option.
In your case, I see a couple other options. You could have a ManyToMany relationship between PresenterCompany and Gig using a through table to specify the type of relationship (https://docs.djangoproject.com/en/2.0/topics/db/models/#extra-fields-on-many-to-many-relationships):
class Gig(models.Model):
description = models.CharField(max_length=100, blank=True)
date = models.DateTimeField()
venue = models.ForeignKey(Venue)
festival = models.ForeignKey(Festival, blank=True, null=True)
musician = models.ManyToManyField(Musician, blank=True)
ensembles = models.ManyToManyField(Ensemble, blank=True)
class PresenterCompanyGigRelationship(models.Model):
gig = models.ForeignKey(Gig, on_delete=models.CASCADE)
presenter_company = models.ForeignKey(
'PresenterCompany', on_delete=models.CASCADE)
relationship = models.CharField(
max_length=10,
choices=(
('presenter', 'Presenter'),
('promoter', 'Promoter'),
('agent', 'Agent'),
))
class PresenterCompany(models.Model):
name = models.CharField(max_length=70)
git = models.ManyToManyField(Gig, through=PresenterCompanyGigRelationship)