Query unique values inside django forloop - python

I have a query where I should avoid double entry of the same question. In fact, I would like to get only unique values but, I am using the distinct() django function which isn't working.
I have these models:
class QuestionTopic(models.Model):
name = models.CharField(max_length=255)
question_subject = models.ForeignKey(
QuestionSubject, on_delete=models.CASCADE)
exam_questions_num = models.IntegerField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
class Meta:
ordering = ('created_at',)
class Question(models.Model):
id = models.CharField(max_length=7,
unique=True,
primary_key=True,
editable=False)
question_subject = models.ForeignKey(
QuestionSubject, on_delete=models.CASCADE)
text = tinymce_models.HTMLField()
mark = models.IntegerField(default=1)
is_published = models.BooleanField(default=True)
question_bank_id = models.CharField(max_length=255, blank=True, null=True)
question_topic = models.ForeignKey(
QuestionTopic, on_delete=models.CASCADE, null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
and the query is:
subject = QuestionSubject.objects.get(id=request.POST.get('subject'))
question_topics = QuestionTopic.objects.filter(
question_subject=subject)
questions_list = []
for topic in question_topics:
for q in range(topic.exam_questions_num):
question = Question.objects.filter(
question_subject=subject, question_topic=topic).values_list(
'id', flat=True).order_by('?').distinct().first()
questions_list.append(question)
What I would like to achieve is to have all different questions for each different topic inside questions_list. Which I am not achieving at the moment being the distinct used inside a loop.

You can work with a single query with:
subject = QuestionSubject.objects.get(id=request.POST.get('subject'))
question_topics = QuestionTopic.objects.filter(question_subject=subject)
questions_list = [
question
for topic in question_topics
for question in Question.objects.filter(
question_subject=subject, question_topic=topic
).order_by('?')[: topic.exam_questions_num]
]
This will make O(n) queries with n the number of topics.
If the number of questions is not that large, you can do this with two queries with:
from random import sample
subject = QuestionSubject.objects.get(id=request.POST.get('subject'))
question_topics = QuestionTopic.objects.filter(
question_subject=subject
).prefetch_related('question_set')
questions_list = [
question
for topic in question_topics
for question in sample(topic.question_set.all(), topic.exam_question_num)
]

Just make questions_list a set:
questions_list = set()
Sets don't allow a value more than once.

Related

Query Django ORM three tables with totals

I have three tables CATEGORIES, TIPODESPESA and EXPENSE. I would like to perform a simpler and faster query to return the total expenses grouped by TYPODESPESA and by CATEGORY.
The expected result is the one below:
enter image description here
What I would like is a result in which the totals are grouped first by CATEGORY, then by TYPE EXPENSE and then show the EXPENSES.
However, for me to be able to do this, I perform the following queries below and I'm trying to simplify and make it faster, but I'm still learning a few things. If you can help with the query.
registros = CategoriaDespesa.objects.filter(
tipodespesas__tipodespesa_movimentodespesa__data__lte=data_final,
tipodespesas__tipodespesa_movimentodespesa__data__gte=data_inicio, ) \
.order_by('description')
categorias_valores = CategoriaDespesa.objects.filter(
tipodespesas__tipodespesa_movimentodespesa__data__lte=data_final,
tipodespesas__tipodespesa_movimentodespesa__data__gte=data_inicio,
tipodespesas__tipodespesa_movimentodespesa__company=company.company) \
.values('id').annotate(total=Coalesce(Sum(
F("tipodespesas__tipodespesa_movimentodespesa__valor"), output_field=FloatField()), 0.00))
tipos_valores = TipoDespesa.objects.filter(
tipodespesa_movimentodespesa__data__lte=data_final,
tipodespesa_movimentodespesa__data__gte=data_inicio,
tipodespesa_movimentodespesa__company=company.company).values('id') \
.annotate(total=Coalesce(Sum(F("tipodespesa_movimentodespesa__valor"), output_field=FloatField()), 0.00))
The classes I'm using are:
class CategoriaDespesa(models.Model):
description = models.CharField(max_length=200, help_text='Insira a descrição')
active = models.BooleanField(default=True, help_text='Esta ativo?', choices=CHOICES)
user_created = models.ForeignKey(User, on_delete=models.PROTECT)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class TipoDespesa(models.Model):
category = models.ForeignKey(
CategoriaDespesa, on_delete=models.CASCADE, related_name="tipodespesas")
description = models.CharField(max_length=200, help_text='Insira a descrição')
active = models.BooleanField(default=True, help_text='Esta ativo?', choices=CHOICES)
user_created = models.ForeignKey(User, on_delete=models.PROTECT)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Despesa(models.Model):
company = models.ForeignKey(
Company, on_delete=models.CASCADE, related_name="companies_movimentodespesa")
tipodespesa = models.ForeignKey(
TipoDespesa, on_delete=models.CASCADE, related_name="tipodespesa_movimentodespesa")
description = models.CharField(
max_length=200, help_text='Insira a descrição')
data = models.DateField(help_text="Data do Movimento",
default=datetime.now, blank=False)
valor = models.DecimalField(
max_digits=8, decimal_places=2, default=0.00, help_text='Valor')
user_created = models.ForeignKey(User, on_delete=models.PROTECT)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
My goal is to create a report that shows the levels and summed values ​​by level.

How to create a django queryset object for inner join?

I have two tables.
class DibbsSpiderDibbsMatchedProductFieldsDuplicate(models.Model):
nsn = models.TextField()
nsn2 = models.TextField()
cage = models.TextField()
part_number = models.TextField()
company_name = models.TextField(blank=True, null=True)
supplier = models.TextField(db_column='Supplier', blank=True, null=True) # Field name made lowercase.
cost = models.CharField(db_column='Cost', max_length=15, blank=True, null=True) # Field name made lowercase.
list_price = models.CharField(db_column='List_Price', max_length=15, blank=True, null=True) # Field name made lowercase.
gsa_price = models.CharField(db_column='GSA_Price', max_length=15, blank=True, null=True) # Field name made lowercase.
hash = models.TextField()
nomenclature = models.TextField()
technical_documents = models.TextField()
solicitation = models.CharField(max_length=32)
status = models.CharField(max_length=16)
purchase_request = models.TextField()
issued = models.DateField()
return_by = models.DateField()
file = models.TextField()
vendor_part_number = models.TextField()
manufacturer_name = models.TextField(blank=True, null=True)
product_name = models.TextField(blank=True, null=True)
unit = models.CharField(max_length=15, blank=True, null=True)
class Meta:
managed = False
db_table = 'dibbs_spider_dibbs_matched_product_fields_duplicate'
class DibbsSpiderSolicitation(models.Model):
line_items = models.IntegerField()
nsn = models.TextField()
nomenclature = models.TextField()
technical_documents = models.TextField()
purchase_request = models.TextField()
class Meta:
managed = False
db_table = 'dibbs_spider_solicitation'
What will be the equivalent django query for the inner join of two tables on the column nsn?
My views function will be like
def inner(request,nsn):
u_m = DibbsSpiderDibbsMatchedProductFieldsDuplicate.objects.filter(nsn2__icontains=id)
c_m = DibbsSpiderSolicitation.objects.filter(nsn__icontains=id)
obj = .......................
context = {'obj':obj}
return render(request,,"a.html",context)
the queryset should return the combination of two tables according to the common nsn.
the obj should return the combination of u_m and c_m. If u_m contains only one rows and c_m contains many rows then the obj must replicate the values of u_m.
You can try some of the options:
Adding foreign key constraint and use select_related as per this post
Raw query as mentioned in this stackoverflow post and another post with custom joins
3. Using IN query as per the following logic:
DibbsSpiderDibbsMatchedProductFieldsDuplicate.objects.filter(
nsn2__in=DibbsSpiderSolicitation.objects.filter(nsn__icontains='text_to_search').values('origin'))
First, great model names. Let's alias them:
DibbsSpiderDibbsMatchedProductFieldsDuplicate is Apples; DibbsSpiderSolicitation is Oranges
inner_qs = Apples.objects.all().extra(
tables=("yourapp_oranges",),
where=("yourapp_apples.nsn=yourapp_oranges.nsn",),
)
The documentation mentions that this api will be deprecated:
https://docs.djangoproject.com/en/4.0/ref/models/querysets/#extra
Option #1 - Introduce a ForeignKey (Recommended):
Under the class DibbsSpiderDibbsMatchedProductFieldsDuplicate add:
fkey = models.ForeignKey('DibbsSpiderSolicitation')
then you can easily access their join:
obj = DibbsSpiderDibbsMatchedProductFieldsDuplicate.Objects.filter(fkey__nsn).select_related()
Now it is your choice what you wish to do with nsn2
Option #2 - without a ForeigKey:
Raw SQL:
obj = DibbsSpiderDibbsMatchedProductFieldsDuplicate.objects.extra(where = ['''SELECT *
FROM DibbsSpiderSolicitation
INNER JOIN DibbsSpiderDibbsMatchedProductFieldsDuplicate
ON DibbsSpiderSolicitation.nsn = DibbsSpiderDibbsMatchedProductFieldsDuplicate.nsn2;'''])
# or
obj = DibbsSpiderDibbsMatchedProductFieldsDuplicate.objects.raw('''SELECT *
FROM DibbsSpiderSolicitation
INNER JOIN DibbsSpiderDibbsMatchedProductFieldsDuplicate
ON DibbsSpiderSolicitation.nsn = DibbsSpiderDibbsMatchedProductFieldsDuplicate.nsn2;''')
Using filter:
obj = DibbsSpiderSolicitation.objects.filter(nsn__in=DibbsSpiderDibbsMatchedProductFieldsDuplicate.objects.nsn2)
Sorry, I was not able to test any.

Django multiple foreign key to a same table

I need to log the transaction of the item movement in a warehouse. I've 3 tables as shown in the below image. However Django response error:
ERRORS:
chemstore.ItemTransaction: (models.E007) Field 'outbin' has column name 'bin_code_id' that is used by another field.
which is complaining of multiple uses of the same foreign key. Is my table design problem? or is it not allowed under Django? How can I achieve this under Django? thankyou
DB design
[Models]
class BinLocation(models.Model):
bin_code = models.CharField(max_length=10, unique=True)
desc = models.CharField(max_length=50)
def __str__(self):
return f"{self.bin_code}"
class Meta:
indexes = [models.Index(fields=['bin_code'])]
class ItemMaster(models.Model):
item_code = models.CharField(max_length=20, unique=True)
desc = models.CharField(max_length=50)
long_desc = models.CharField(max_length=150, blank=True)
helper_qty = models.DecimalField(max_digits=10, decimal_places=4)
unit = models.CharField(max_length=10, blank=False)
def __str__(self):
return f"{self.item_code}"
class Meta:
verbose_name = "Item"
verbose_name_plural = "Items"
indexes = [models.Index(fields=['item_code'])]
class ItemTransaction(models.Model):
trace_code = models.CharField(max_length=20, unique=False)
item_code = models.ForeignKey(
ItemMaster, related_name='trans', on_delete=models.CASCADE, null=False)
datetime = models.DateTimeField(auto_now=False, auto_now_add=False)
qty = models.DecimalField(max_digits=10, decimal_places=4)
unit = models.CharField(max_length=10, blank=False)
action = models.CharField(
max_length=1, choices=ACTION, blank=False, null=False)
in_bin = models.ForeignKey(
BinLocation, related_name='in_logs', db_column='bin_code_id', on_delete=models.CASCADE, null=False)
out_bin = models.ForeignKey(
BinLocation, related_name='out_logs', db_column='bin_code_id', on_delete=models.CASCADE, null=False)
remarks = models.TextField(blank=True)
def __str__(self):
return f"{self.trace_code} {self.datetime} {self.item_code} {dict(ACTION)[self.action]} {self.qty} {self.unit} {self.in_bin} {self.out_bin}"
you have same db_column in two fields so change it
in_bin = models.ForeignKey(
BinLocation, related_name='in_logs', db_column='bin_code_id', on_delete=models.CASCADE, null=False)
out_bin = models.ForeignKey(
BinLocation, related_name='out_logs', db_column='other_bin_code', on_delete=models.CASCADE, null=False) /*change db_column whatever you want but it should be unique*/
If are linked to the same model name, You should use different related_name for each foreign_key filed . here is the exemple :
address1 = models.ForeignKey(Address, verbose_name=_("Address1"),related_name="Address1", null=True, blank=True,on_delete=models.SET_NULL)
address2 = models.ForeignKey(Address, verbose_name=_("Address2"),related_name="Address2", null=True, blank=True,on_delete=models.SET_NULL)
thank you for everyone helped. According to Aleksei and Tabaane, it is my DB design issue (broken the RDBMS rule) rather than Django issue. I searched online and find something similar: ONE-TO-MANY DB design pattern
In my case, I should store in bin and out bin as separated transaction instead of both in and out in a single transaction. This is my solution. thankyou.
p.s. alternative solution: I keep in bin and out bin as single transaction, but I don't use foreign key for bins, query both in bin and out bin for the bin selection by client application.

Django: Filtering via SQL, not Python

I created the following context variables context["genders"] and context["ages"].
Currently, there is a lot of work done by Python under #Filtering, while I think it would be better done in #Query.
However, that's where I currently struggle. Do you have an idea on how to achieve the pre-filtering in the #Query section via SQL?
Please not the int(answer_obj.answer) as answer is a TextField.
# Query
responses = Response.objects.filter(
survey__event=12, survey__template=settings.SURVEY_POST_EVENT
).order_by("-created")
# Filtering
filtered_responses = []
for response in responses:
for answer_obj in response.answers.all():
if (
answer_obj.question.focus == QuestionFocus.RECOMMENDATION_TO_FRIENDS
and int(answer_obj.answer) >= 8
):
filtered_responses.append(response)
# Context
gender_list = []
age_list = []
for response in filtered_responses:
for answer_obj in response.answers.all():
# Here a list of all the genders that gave that answer:
if answer_obj.question.focus == QuestionFocus.GENDER:
gender_list.append(answer_obj.answer)
# Here a list of all the ages that gave that answer:
if answer_obj.question.focus == QuestionFocus.AGE:
age_list.append(answer_obj.answer)
context["genders"] = gender_list
context["ages"] = age_list
models.py
class Answer(TimeStampedModel):
question = models.ForeignKey(
"surveys.Question", on_delete=models.CASCADE, related_name="answers"
)
response = models.ForeignKey(
"Response", on_delete=models.CASCADE, related_name="answers"
)
answer = models.TextField(verbose_name=_("Answer"))
choices = models.ManyToManyField(
"surveys.AnswerOption", related_name="answers", blank=True
)
class Response(TimeStampedModel):
class Language(Choices):
CHOICES = settings.LANGUAGES
survey = models.ForeignKey(
"surveys.Survey", on_delete=models.CASCADE, related_name="responses"
)
order = models.ForeignKey(
"orders.Order",
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="response",
)
attendee = models.ForeignKey(
"attendees.Attendee",
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="response",
)
total_time = models.PositiveIntegerField(
null=True, blank=True, verbose_name=_("Total time")
)
ip_address = models.GenericIPAddressField(null=True, verbose_name=_("IP Address"))
language = models.CharField(
max_length=Language.get_max_length(),
choices=Language.CHOICES,
verbose_name=_("Language"),
)
class Question(TimeStampedModel):
survey = models.ForeignKey(
"surveys.Survey", on_delete=models.CASCADE, related_name="questions"
)
question_set = models.ForeignKey(
"QuestionSet", on_delete=models.CASCADE, related_name="questions"
)
title = models.CharField(max_length=100, verbose_name=_("Title"))
help_text = models.TextField(null=True, blank=True, verbose_name=_("Help text"))
type = models.CharField(
max_length=QuestionType.get_max_length(),
choices=QuestionType.CHOICES,
verbose_name=_("Question type"),
)
focus = models.CharField(
max_length=QuestionFocus.get_max_length(),
choices=QuestionFocus.CHOICES,
verbose_name=_("Question focus"),
)
required = models.BooleanField(default=False, verbose_name=_("Is required?"))
position = models.PositiveSmallIntegerField(
null=True, blank=True, verbose_name=_("Position")
)
# Translatable fields
i18n = TranslationField(fields=("title", "help_text"))
class Meta:
ordering = ("position", "pk")
Filtering
It looks to me, that you want all responses, where answer is higher than eight for question focused on friend recommendation. Is it expected that you might have the same response appended to the filtered responses more than once, or will there be only one question of this type? I think you could rewrite it as follows:
filtered_response = responses.filter(
answers__question__focus=QuestionFocus.RECOMMENDATION_TO_FRIENDS
).annotate(
answer_num=Cast("answers__answer", IntegerField()),
).filter(
answer_num__gt=8,
)
And populating the context:
context["genders"] = Answer.objects.filter(
response_id__in=filtered_response.values_list("id", flat=True),
question__focus=QuestionFocus.GENDER,
).values_list("answer", flat=True)
context["ages"] = Answer.objects.filter(
response_id__in=filtered_response.values_list("id", flat=True),
question__focus=QuestionFocus.AGE,
).values_list("answer", flat=True)
This should allow you to avoid firing the queries to iterate over response.answers.all(), and hopefully achieve the same result.

how to perform count for a secod foreign key in django?

i have three models Category,Post,Comment
class Category(models.Model):
class Meta:
verbose_name_plural = "Categories"
COLOR_CHOICES = (
('primary', 'Blue'),
('success', 'Green'),
('info', 'Sky Blue'),
('warning', 'Yellow'),
('danger', 'Red')
)
title = models.CharField(max_length=250, unique=True)
description = models.CharField(max_length=250)
slug = models.SlugField(max_length=200,)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
visited = models.IntegerField(default=0)
color = models.CharField(
max_length=20, default='primary', choices=COLOR_CHOICES)
class Post(models.Model):
STATUS_CHOICES = (
(True, 'Visible'),
(False, 'Hidden')
)
title = models.CharField(max_length=250)
author = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
body = models.TextField()
category = models.ForeignKey(
Category, on_delete=models.SET_NULL, null=True)
slug = models.SlugField(max_length=200,)
status = models.BooleanField(default=False, choices=STATUS_CHOICES)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
visited = models.IntegerField(default=0)
class Comment(models.Model):
STATUS_CHOICES = (
(True, 'Visible'),
(False, 'Hidden')
)
post = models.ForeignKey(Post, on_delete=models.CASCADE, null=True)
author = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
body = models.TextField()
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
status = models.BooleanField(default=True, choices=STATUS_CHOICES)
I want to perform a query set to get categories and number of comments of each category, I can't find a good way.
I already know how to count posts for each category using annotate.
I tried :
categories = Category.objects.annotate(nb_comments=Count('post__comment'))
if you want to count number of comment that related to specific categoriy:
number_of_commemt_for_cat1=Comment.objects.filter(post__category_id=1).count()
this query say: count of comment that belong to posts that post are belong to specific category.
and here you can find count of post that related to specific category:
number_of_post_for_cat1=Post.objects.filter(category_id=1).count()
I'm with mobile so I can't explain enough or put references here for you. hope help.

Categories

Resources