How to join one table with grouped values from different query? - django

I have a models like this:
class Subscription(models.Model):
visable_name = models.CharField(max_length=50, unique=True)
recipe_name = models.CharField(max_length=50)
website_url = models.URLField()
class User(models.Model):
username = models.CharField(max_length=50)
class UserSubs(models.Model):
subscription = models.ForeignKey(Subscription, to_field='visable_name')
user = models.ForeignKey(User, to_field='username')
And I want to prepare simple ranking, so I came up with something like this:
rank = UserSubs.objects.values('subscription').
annotate(total=Count('user')).
order_by('-total')`
what gives:
>> rank
[
{'total': 3, 'subscription': u'onet'},
{'total': 2, 'subscription': u'niebezpiecznik'},
{'total': 1, 'subscription': u'gazeta'}
]
what I need, is similar list of full objects:
[
{'total': 3, 'subscription': <Subscription: onet>},
{'total': 2, 'subscription': <Subscription: niebezpiecznik>},
{'total': 1, 'subscription': <Subscription: gazeta>}
]
I am not sure, whether 'select_related' will be helpful here, but I can't figured out how to use it :(

Better to build your query from Subscription because you need it:
Subscription.objects.annotate(total=models.Count('usersubs')).order_by('-total')

Maybe you can use dict and list comprehension, and filter this as simple python objects:
d = {sub.visable_name: sub for sub in Subscriptions.objects.all()}
new_rank = [
{
'total': row['total'],
'subscriptions': d[row['subscriptions']]
}
for row in rank
]
what will give:
>> new_rank
[
{'total': 3, 'subscriptions': <Subscriptions: onet>},
{'total': 2, 'subscriptions': <Subscriptions: niebezpiecznik>},
{'total': 1, 'subscriptions': <Subscriptions: gazeta.pl>}
]

Related

How to get the Primary key of annotate Count

Hi stackoverflow community, my question is about django annotate.
Basically what I am trying to do is to find duplicated value with same values from two different fields in two different tables.
This is my models.py
class Order(models.Model):
id_order = models.AutoField(primary_key=True)
class OrderDelivery(models.Model):
order = models.ForeignKey(Order, on_delete=models.SET_NULL, null=True, blank=True)
delivery_address = models.TextField()
class OrderPickup(models.Model):
order = models.ForeignKey(Order, on_delete=models.SET_NULL, null=True, blank=True)
pickup_date = models.DateField(blank=True, null=True)
This is my current code:
dup_job = Order.objects.filter(
orderpickup__pickup_date__range=(start_date, end_date)
).values(
'orderdelivery__delivery_address',
'orderpickup__pickup_date',
).annotate(
duplicated=Count('orderdelivery__delivery_address')
).filter(
duplicated__gt=1
)
Based on what I have, I am getting result like this (delivery_address is omitted for privacy purpose):
{'orderdelivery__delivery_address': '118A', 'orderpickup__pickup_date': datetime.date(2022, 3, 9), 'duplicated': 2}
{'orderdelivery__delivery_address': '11', 'orderpickup__pickup_date': datetime.date(2022, 3, 2), 'duplicated': 6}
{'orderdelivery__delivery_address': '11 A ', 'orderpickup__pickup_date': datetime.date(2022, 3, 3), 'duplicated': 5}
{'orderdelivery__delivery_address': '21', 'orderpickup__pickup_date': datetime.date(2022, 3, 10), 'duplicated': 3}
{'orderdelivery__delivery_address': '642', 'orderpickup__pickup_date': datetime.date(2022, 3, 7), 'duplicated': 2}
{'orderdelivery__delivery_address': '642', 'orderpickup__pickup_date': datetime.date(2022, 3, 8), 'duplicated': 2}
{'orderdelivery__delivery_address': 'N/A,5', 'orderpickup__pickup_date': datetime.date(2022, 3, 8), 'duplicated': 19}
Is there a way to get the id_order of those 'duplicated'?
I have tried include id_order in .values() but the output will not be accurate as the annotation is grouping by the id_order instead of delivery_address.
Thank you in advance
You can get the smallest (or largest) item with a Min [Django-doc] (or Max) aggregate:
from django.db.models import Min
dup_job = Order.objects.filter(
orderpickup__pickup_date__range=(start_date, end_date)
).values(
'orderdelivery__delivery_address',
'orderpickup__pickup_date',
).annotate(
min_id_order=Min('id_order')
duplicated=Count('orderdelivery__delivery_address')
).filter(
duplicated__gt=1
)
or for postgresql, you can make use of the ArrayAgg [Django-doc] to generate a list:
# PostgreSQL only
from django.contrib.postgres.aggregates import ArrayAgg
dup_job = Order.objects.filter(
orderpickup__pickup_date__range=(start_date, end_date)
).values(
'orderdelivery__delivery_address',
'orderpickup__pickup_date',
).annotate(
min_id_order=ArrayAgg('id_order')
duplicated=Count('orderdelivery__delivery_address')
).filter(
duplicated__gt=1
)

Django Count with filter

iam trying to count with filter and it doesn't show any error but there is no count showing
I have tried all what I know and search every where didn't find any solution :
class UsersAnswersSerializer(serializers.ModelSerializer):
Answers = serializers.SerializerMethodField('get_answers')
def get_answers(self, obj):
queryset = AnswersModel.objects.filter(Question=obj.Answer.Question.id)\
.annotate(answersCount=Count('UsersAnswer', distinct=True))
serializer = Answersserializer(instance=queryset, many=True)
return serializer.data
class Meta:
model = UsersAnswerModel
fields = ['Answer', 'APNSDevice' ,'RegistrationID','Answers']
and this is my models :
class AnswersModel(models.Model):
Question = models.ForeignKey(QuestionsModel, related_name='QuestionAnswer', on_delete=models.CASCADE)
Answer = models.CharField(max_length=200)
class UsersAnswerModel(models.Model):
Answer = models.ForeignKey(AnswersModel, related_name='UsersAnswer', on_delete=models.CASCADE)
RegistrationID = models.CharField(max_length=200)
APNSDevice = models.CharField(max_length=200,default='no name')
class QuestionsModel(models.Model):
created = models.DateTimeField(auto_now_add=True)
Question = models.CharField(max_length=200)
what is get is
{
"Answer": 12,
"APNSDevice": "byname",
"RegistrationID": "asdasdasdasdasdasdasdasdsa",
"Answers": [
{
"id": 10,
"Question": 4,
"Answer": "Answer 1",
"UsersAnswer": [
"gfgdgdfgdf",
"c748dfd8aa7dd73a6c1ef17676aa7667161ff7e0f8e2ef21ef17e964a26150e4"
]
},
{
"id": 11,
"Question": 4,
"Answer": "Answer 2",
"UsersAnswer": [
"sfgdfgdf",
"c748dfd8aa7dd73a6c1ef17676aa7667161ff7e0f8e2ef21ef17e964a26150e4",
"c748dfd8aa7dd73a6c1ef17676aa7667161ff7e0f8e2ef21ef17e964a26150e4",
"c748dfd8aa7dd73a6c1ef17676aa7667161ff7e0f8e2ef21ef17e964a26150e4"
]
}
I need to make "UsersAnswer" show a count of its array
I think what you want to do is aggregate and count.
Replace annotate with aggregate

Django rest framework response items by category

I'm using Django rest framework serializer to return plans for a mobile application. For now my serializer response looks like this:
[
{
"id": 1,
"nome": "Test",
"localizacao": "Example - MG",
"instituicao": "Example",
"modalidade": "Example",
"categoria": "Category 1",
"frequencia_semanal": 1,
"carga_horaria": 2,
"quantidade_alunos": 2,
"valor_unitario": "12.00",
"taxa_matricula": "12.00",
"informacoes_adicionais": ""
}
]
I need to separate plans by category so, response structure must look like this:
[
"Category 1": [
{
"id": 1,
"nome": "Test",
"localizacao": "Example",
"instituicao": "Example",
"modalidade": "Example",
"frequencia_semanal": 1,
"carga_horaria": 2,
"quantidade_alunos": 2,
"valor_unitario": "12.00",
"taxa_matricula": "12.00",
"informacoes_adicionais": ""
}
]
]
The "categoria" value, must be keys and all plans belonging to that category must be contained in the key.
there is my models.py
class Plano(models.Model):
PUBLICO_ALVO_MENORES = 1
PUBLICO_ALVO_ADOLESCENTES = 2
PUBLICO_ALVO_ADULTOS = 3
PUBLICO_ALVO_TODOS = 4
PUBLICO_ALVO_CHOICES = (
(PUBLICO_ALVO_MENORES, 'Menores'),
(PUBLICO_ALVO_ADOLESCENTES, 'Adolecentes'),
(PUBLICO_ALVO_ADULTOS, 'Adultos'),
(PUBLICO_ALVO_TODOS, 'Todas as idades'),
)
nome = models.CharField(max_length=200)
localizacao = models.ForeignKey('Localizacao', on_delete=models.PROTECT)
instituicao = models.ForeignKey('Instituicao', null=True, on_delete=models.PROTECT)
modalidade = models.ForeignKey('PlanoModalidade', verbose_name='Modalidades', on_delete=models.PROTECT)
categoria = models.ForeignKey('PlanoCategoria', verbose_name='Categorias', on_delete=models.PROTECT)
publico_alvo = models.SmallIntegerField(choices=PUBLICO_ALVO_CHOICES, null=True)
frequencia_semanal = models.PositiveSmallIntegerField(verbose_name='Frequência semanal')
carga_horaria = models.PositiveSmallIntegerField(verbose_name='Carga horária diária')
quantidade_alunos = models.PositiveSmallIntegerField(verbose_name='Quantidade de alunos')
valor_unitario = models.DecimalField(decimal_places=2, max_digits=18, verbose_name='Valor unitário')
taxa_matricula = models.DecimalField(decimal_places=2, max_digits=18, verbose_name='Taxa matrícula')
informacoes_adicionais = models.TextField(max_length=200, blank=True)
DRF serializers.py
class PlanosSerializer(serializers.ModelSerializer):
categoria = serializers.StringRelatedField()
modalidade = serializers.StringRelatedField()
localizacao = serializers.StringRelatedField()
instituicao = serializers.StringRelatedField()
class Meta:
model = Plano
fields = [
'id', 'nome', 'localizacao', 'instituicao', 'modalidade', 'categoria',
'frequencia_semanal', 'carga_horaria', 'quantidade_alunos', 'valor_unitario',
'taxa_matricula', 'informacoes_adicionais',
]
Please help me haha.
I recoment change your JSON to anything similar to:
[{
"category_id":1,
"category_name":"Category 1",
"category_items":[
{
"id":1,
"nome":"Test",
"localizacao":"Example",
"instituicao":"Example",
"modalidade":"Example",
"frequencia_semanal":1,
"carga_horaria":2,
"quantidade_alunos":2,
"valor_unitario":"12.00",
"taxa_matricula":"12.00",
"informacoes_adicionais":""
}
]
}]
And is more easy, create using neested serializer.
https://www.django-rest-framework.org/api-guide/relations/#nested-relationships

How to filter foreign key objects in django queries?

I have two models, one related to other by foreign key like this
class CapturedPrescriptionModel(ColModel):
p_id = models.IntegerField()
p_age = models.IntegerField()
p_gender = models.CharField(max_length=10)
p_care_type = models.CharField(max_length=100)
bacteria_id = models.ForeignKey(BacteriaListModel,
on_delete=models.CASCADE, null=True)
class SuggestedAntibioticsModel(ColModel):
prescription_id = models.ForeignKey(CapturedPrescriptionModel,
related_name='antibiotics',
on_delete=models.CASCADE)
cat_ids = models.TextField()
flag = models.IntegerField(default=0)
Now I want all the prescriptions with suggested antibiotics where flag=1
I have tried with CapturedPrescriptionModel.objects.filter(antibiotics__flag=1) but that filter the prescriptions not the list of antibiotics in the queryset.
[
{
"id": 7,
"p_id": 0,
"p_age": 19,
"p_gender": "Male",
"p_care_type": "ICU",
"bacteria_id": null,
"antibiotics": [
{
"id": 188,
"cat_ids": "[]",
"flag": 0,
"antibiotic_id_id": 87,
"prescription_id_id": 7
},
{
"id": 187,
"cat_ids": "[]",
"flag": 1,
"antibiotic_id_id": 112,
"prescription_id_id": 7
},
......
]
}
....
]
My expected result will be like this
[
{
"id": 7,
"p_id": 0,
"p_age": 19,
"p_gender": "Male",
"p_care_type": "ICU",
"bacteria_id": null,
"antibiotics": [
{
"id": 187,
"cat_ids": "[]",
"flag": 1,
"antibiotic_id_id": 112,
"prescription_id_id": 7
}
]
}
....
]
You need a filtered Prefetch if you want to filter the related objects only, not the main objects:
from django.db.models import Prefetch
CapturedPrescriptionModel.objects.prefetch_related(Prefetch(
'antibiotics',
queryset=SuggestedAntibioticsModel.objects.filter(flag=1)
)
You then have to make sure that antibiotics on the individual prescription objects is only accessed with prescription.antibiotics.all(), otherwise the prefetch is not used and you'll get all antibiotics again.
Collect all Prescriptions:
prescriptions = CapturedPrescriptionModel.objects.all()
for prescription in prescriptions:
prescription.antibiotics = prescription.antibiotics.filter(flag=1)
# at this time presciptions should be prepared, just make sure to not save them...
You could also extend your model to have a property for that list.
class CapturedPrescriptionModel(ColModel):
p_id = models.IntegerField()
p_age = models.IntegerField()
p_gender = models.CharField(max_length=10)
p_care_type = models.CharField(max_length=100)
bacteria_id = models.ForeignKey(BacteriaListModel,
on_delete=models.CASCADE, null=True)
#property
def flagged_antibiotics(self):
try:
return self.antibiotics.filter(flag=1)
except Exception:
return []
class SuggestedAntibioticsModel(ColModel):
prescription_id = models.ForeignKey(CapturedPrescriptionModel,
related_name='antibiotics',
on_delete=models.CASCADE)
cat_ids = models.TextField()
flag = models.IntegerField(default=0)
Something like this would be my first take on that

Django Reverse Foreign key with nested serializer gives multiple results

How to generate left outer join query using reverse foreign key relation and map it to the nested serializer? I want to filter result on multiple foreign keys.
models.py
class Question(models.Model):
question_name = models.CharField(max_length=1024)
class Paper(models.Model):
paper_name = models.CharField(max_length=128)
class Answer(models.Model):
score = models.IntegerField()
question_id = models.ForeignKey(Question, related_name='answer_questions')
paper_id = models.ForeignKey(Paper, related_name='answer_papers')
class Meta:
unique_together = ("question_id", "paper_id")
serializers.py
class PaperSerializer(serializers.ModelSerializer):
class Meta:
fields = ('id', 'paper_name')
model = Paper
class AnswerSerializer(serializers.ModelSerializer):
class Meta:
fields = ('id', 'score', 'question_id', 'paper_id')
model = Answer
class QuestionSerializer(serializers.ModelSerializer):
answer_questions = AnswerSerializer(many=True, allow_null=True)
class Meta:
fields = ('id', 'question_name', 'answer_questions')
model = Question
views.py
class QuestionList(generics.ListAPIView):
def get_queryset(self):
paper_id = self.kwargs['pk']
queryset = Question.objects.filter(answer_questions__paper_id=paper_id, answer_questions__question_id=F('id'))
return queryset
serializer_class = QuestionSerializer
My url is
localhost/papers/1/questions/
Expected output:
(1) List of all questions with answer object for corresponding question_id and paper_id only (single answer object) embedded in it.
(2) List should include all questions irrespective of they are answered or not (in case of question not answered, question with empty answer object should be returned) i.e. left outer join
[
{
"id": 1,
"question_id": 1,
"answer_questions": [
{
"id": 24,
"score": 5,
"question_id": 1,
"paper_id": 1
},
{
"id": 27,
"score": 8,
"question_id": 1,
"paper_id": 2
},
{
"id": 28,
"score": 6,
"question_id": 1,
"paper_id": 3
}
]
}
]
Current Output:
(1) I am getting multiple answer objects for particular question_id including all paper_ids. i.e. For question_id = 1 and paper_id = 2, My output shows questions with answer objects for question_id = 1 not filtering on paper_id.
(2) Only answered questions (as query is inner join query)
[
{
"id": 1,
"question_id": 1,
"answer_questions": [
{
"id": 24,
"score": 5,
"question_id": 1,
"paper_id": 1
}
]
}
]
If it's not a good way to implement, kindly suggest better way to optimize it.
I think you'll get desired output if you slightly change the get_quesyset() method.
class QuestionList(generics.ListAPIView):
def get_queryset(self):
return Question.objects.all()
serializer_class = QuestionSerializer
When you access the QuestionList list api you will get the output as follows
[
{
"id": 4,
"question_name": "qus-1",
"answer_questions": [
{
"id": 5,
"score": 50,
"question_id": 4,
"paper_id": 4
},
{
"id": 6,
"score": 10,
"question_id": 4,
"paper_id": 5
}
]
},
{
"id": 5,
"question_name": "qus-2",
"answer_questions": []
},
{
"id": 6,
"question_name": "que-3",
"answer_questions": [
{
"id": 7,
"score": 342,
"question_id": 6,
"paper_id": 4
}
]
}
]
Update-1
Change your serializer as below
class QuestionSerializer(serializers.ModelSerializer):
answer_questions = serializers.SerializerMethodField()
def get_answer_questions(self, question):
paper_id = self.context['view'].kwargs.get('paper_id')
return AnswerSerializer(question.answer_questions.filter(paper_id=paper_id), many=True).data
class Meta:
fields = ('id', 'question_name', 'answer_questions')
model = Question