Django one to many Relationship Query - django

Well, I'm starting learning Django and now I have problems to get a QuerySet that allows me fill a table.
The table shows information about the projects of a programer. Each project has n iterations.
|id|Name|Start|End|Cost|Objetive|Description|Client|Progress|Status| Iterations|
In the View, I can get the first 9 columns (From the Project model) using a QuerySet like this:
projects = Project.objects.filter(responsible = request.user.id)
In the 10th column I have to show the number of "Iterations" of every Project and I'm trying this:
Proyect.objects.filter(responsible = request.user.id).annotate(Niterations= Iteration.objectsfilter(project_id= request.user.id).count())
And well,it doesn't work, I understand that with an annotation the arguments will be added to each object in the QuerySet ...
The other way Iwas trying is:
projects = Project.objects.filter(responsible = request.user.id)
for proy in projects:
proy.annotate(Iteration.objects.filter(project_id=proy.pk).count())
I don't know exactly whay it doesn't works... Is there an easier way?
It's been a little confusing ...
I'm working with two models: "Project" and "Iteration"
Model: Project
from django.db import models
from usuarios.models import User
class Proyect(models.Model):
name = models.CharField(max_length=255)
start = models.DateField()
end = models.DateField(blank=True, null=True)
cost = models.FloatField(blank=True, null=True)
objetive = models.TextField(blank=True, null=True)
description = models.CharField(max_length=255, blank=True, null=True)
client = models.CharField(max_length=255, blank=True,null=True)
progress = models.FloatField(verbose_name=u'percentage of progress', default=0.00, editable=False)
responsible = models.ForeignKey(User, editable=False)
STATUS_CHOICES = (
('A','Atrasado'),
('N','Normal'),
('AP','Alta prioridad'),
)
status = models.CharField(max_length=2, choices=STATUS_CHOICES)
def __unicode__(self):
return self.name
Model: Iteration
from django.db import models
from proyectos.models import Project
class Iteration(models.Model):
identifier = models.CharField(max_length=255)
start = models.DateField()
end = models.DateField()
progress = models.FloatField(default=0.0, editable=False)
project = models.ForeignKey(Project)
Thanks in advance!!!
(I translated this, sorry if there are mistakes in translations)

The right way is:
Project.objects.filter(responsible=request.user.id).annotate(niterations=Count('iteration')
You can see the query to understand better:
result = Project.objects.filter(responsible=request.user.id).annotate(niterations=Count('iteration')
print str(result.query)
I hope I've helped.

Related

Django viewflow running queries in a custom node

I am trying to adapt the Viewflow Dynamicsplit example
The objective is to split approvals of an Order based on OrderLines and to assign them to the appropriate approver. It seems that this should be possible as there seems to be a possibility described in this answer - Assign user to tasks automatically (Viewflow, Django)
The issue is that whenever I try to grab the current order pk in the DynamicSplitActivation - the queryset comes back empty. Not sure where I am going wrong. The queryset works fine if I set the pk manually, but as soon as I try to use a dynamic variable it stops working.
I have listed the flow and models etc. at the end of the post, however, I am guessing the issue is quite basic. Any help would be appreciated!
nodes.py
...
class DynamicSplitActivation(AbstractGateActivation):
def calculate_next(self):
self._order = self.flow_task._order_callback(self.process)
order_id = self._order.pk
# order_id = 28
order_lines = models.OrderLine.objects.filter(order_id=order_id)
project_lines = project_models.ProjectLine.objects.filter(orderline__in=order_lines)
users = User.objects.filter(projectline__in=project_lines).distinct()
self._approvers = users
...
class DynamicSplit(...):
...
def __init__(self, callback):
super(DynamicSplit, self).__init__()
self._order_callback = callback
self._ifnone_next_node = None
...
flows.py
...
approval_split = (
nodes.DynamicSplit(lambda act: act.order)
.IfNone(this.end)
.Next(this.split_approval_budget)
)
models.py
...
class ProjectLine(models.Model):
project = models.ForeignKey(Project, on_delete=models.CASCADE)
description = models.CharField(max_length=50)
budget_holder = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True)
class Order(models.Model):
description = models.CharField(max_length=30)
class OrderLine(models.Model):
order = models.ForeignKey(Order, on_delete=models.CASCADE)
project_line = models.ForeignKey(project_models.ProjectLine, on_delete=models.SET_NULL, null=True, blank=False)
product = models.ForeignKey(catalogue_models.Product, on_delete=models.CASCADE)
quantity = models.PositiveIntegerField()
budgetholder_approved = models.BooleanField(null=True)
class OrderProccess(Process):
order = models.ForeignKey(Order, blank=True, null=True, on_delete=models.CASCADE)
...

How to relationship such a connection in django?

I need to implement a "end-to-end" connection between products.
like this:
I automatically put down two-way links between two products. But when I link product A to product B and product B to product C, there is no connection between A and C. It is necessary that they communicate themselves when putting down the two previous links.
Models.py
from django.db import models
class Part(models.Model):
brand = models.CharField('Производитель', max_length=100, blank=True)
number = models.CharField('Артикул', max_length=100, unique=True)
name = models.CharField('Название', max_length=100, blank=True)
description = models.TextField('Комментарий', blank=True, max_length=5000)
analog = models.ManyToManyField('self',blank=True, related_name='AnalogParts')
images = models.FileField('Главное изображение', upload_to = 'parts/', blank=True)
images0 = models.FileField('Дополнительное фото', upload_to = 'parts/', blank=True)
images1 = models.FileField('Дополнительное фото', upload_to = 'parts/', blank=True)
images2 = models.FileField('Дополнительное фото', upload_to = 'parts/', blank=True)
def __str__(self):
return str(self.number)
return self.name
class Meta:
verbose_name = 'Запчасть'
verbose_name_plural = 'Запчасти'
Your diagram looks like Parts are connected linear. The easiest way to accomplish that is to add OneToOneFields to the model it self like so:
next = models.OneToOneField('self', related_name='previous', null=True)
Then you can do something like
part = Part.object.get(name='wheel')
while part.next:
part = part.next
# At this point Part is the last part in the row
print(part)
This is the most easy way. Depending whether you have linear connections between parts only you have to adjust your fields. There is no solution to all of the graph problems unless you realize some graph structure/database which will solve common problems (I think GraphQL can do such stuff, but iam not sure). Also keep in mind that this will most probably execute one SQL query per loop iteration.

Optimizing Django with prefetch and filters in large table

I had a database in php/html using MySQL and am transferring this to a Django project.
I have all the functionalities working, but loading a table of the data I want is immensely slow because of the relations with other tables.
After searching for days I know that I probably have to use a model.Manager to use prefetch_all. However, I am not stuck on how to call this into my template.
I have the following models(simplified):
class OrganisationManager(models.Manager):
def get_queryset_director(self):
person_query = Position.objects.select_related('person').filter(position_current=True,
position_type="director"
)
return super().get_queryset().prefetch_related(Prefetch('position_set', queryset=person_query, to_attr="position_list"))
def get_queryset_president(self):
person_query = Position.objects.select_related('person').filter(position_current=True,
position_type="president"
)
return super().get_queryset().prefetch_related(Prefetch('position_set', queryset=person_query, to_attr="position_list"))
class Person(models.Model):
full_name = models.CharField(max_length=255, blank=True, null=True)
country = models.ForeignKey(Country, models.CASCADE, blank=True, null=True)
birth_date = models.DateField(blank=True, null=True)
class Organisation(models.Model):
organisation_name = models.CharField(max_length=255, blank=True, null=True)
positions = models.ManyToManyField(Person, through='Position')
# positions are dynamic, even though there should only be only one director and president at each given time, a onetoone model wouldn't work in this scenario
objects = OrganisationManager()
# The following defs are currently used to show the names and start dates of the director and president in the detailview and listview
def director(self):
return self.position_set.filter(position_current=True, position_type="director").last()
def president(self):
return self.position_set.filter(position_current=True, position_type="P").last()
class Position(models.Model):
POSITION_TYPES = (
('president','President'),
('director','Director'),
)
person = models.ForeignKey(Person, on_delete=models.CASCADE)
organisation = models.ForeignKey(Organisation, on_delete=models.CASCADE)
position_type = models.CharField(max_length=255, choices=POSITION_TYPES, blank=True, null=True)
position_current = models.BooleanField(default=False)
position_start = models.CharField(max_length=10, blank=True, null=True)
I want my table to look like this:
Organisation Name
President
President Start Date
Director
Director Start Date
Organisation 1
President of org 1
2013
Director of org 1
2015
Organisation 2
President of org 2
2018
Director of org 2
2017
With the code I currently have, it all works great. But because it has to call the database each time, this even causes Heroku to timeout.
I don't understand how to use the prefetch query in the models.Manager in the table (ListView) template. Thanks!
One approach to achieve the results you want is to use subqueries. So something like:
president_subquery = Position.objects.filter(
organisation=OuterRef('pk'), position_type='president', position_current=True
).last()
director_subquery = Position.objects.filter(
organisation=OuterRef('pk'), position_type='director', position_current=True
).last()
Organisation.objects.annotate(
president=Subquery(president_subquery.values('person__fullname')),
president_start_date=Subquery(president_subquery.values('position_start')),
director=Subquery(director_subquery.values('person__fullname')),
director_start_date=Subquery(director_subquery.values('position_start')),
)

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 Many to Many Data Duplication?

Background
I'm storing data about researchers. eg, researcher profiles, metrics for each researcher, journals they published in, papers they have, etc.
The Problem
My current database design is this:
Each Researcher has many journals (they published in). The journals have information about it.
Likewise for Subject Areas
But currently, this leads to massive data duplication. Eg, the same journal can appear many times in the Journal table, just linked to a different researcher, etc.
Is there any better way to tackle this problem? Like right now, I have over 5000 rows in the journal column but only about 1000 journals.
Thank you!
EDIT: This is likely due to the way im saving the models for new data (mentioned below). Could anyone provide the proper way to loop and save hashes to models?
Model - Researcher
class Researcher(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
scopus_id = models.BigIntegerField(db_index=True) # Index to make searches quicker
academic_rank = models.CharField(max_length=100)
title = models.CharField(max_length=200,default=None, blank=True, null=True)
salutation = models.CharField(max_length=200,default=None, blank=True, null=True)
scopus_first_name = models.CharField(max_length=100)
scopus_last_name = models.CharField(max_length=100)
affiliation = models.CharField(default=None, blank=True, null=True,max_length = 255)
department = models.CharField(default=None, blank=True, null=True,max_length = 255)
email = models.EmailField(default=None, blank=True, null=True)
properties = JSONField(default=dict)
def __str__(self):
return "{} {}, Scopus ID {}".format(self.scopus_first_name,self.scopus_last_name,self.scopus_id)
Model - Journal
class Journal(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
researchers = models.ManyToManyField(Researcher)
title = models.TextField()
journal_type = models.CharField(max_length=40,default=None,blank=True, null=True)
abbreviation = models.TextField(default=None, blank=True, null=True)
issn = models.CharField(max_length=50, default=None, blank=True, null=True)
journal_rank = models.IntegerField(default=None, blank=True, null=True)
properties = JSONField(default=dict)
def __str__(self):
return self.title
How I'm currently saving them:
db_model_fields = {'abbreviation': 'Front. Artif. Intell. Appl.',
'issn': '09226389',
'journal_type': 'k',
'researchers': <Researcher: x, Scopus ID f>,
'title': 'Frontiers in Artificial Intelligence and Applications'}
# remove researchers or else create will fail (some id need to exist error)
researcher = db_model_fields["researchers"]
del db_model_fields["researchers"]
model_obj = Journal(**db_model_fields)
model_obj.save()
model_obj.researchers.add(researcher)
model_obj.save()
Here is how it works :
class Journal(models.Model):
# some fields
class Researcher(models.Model):
# some fields
journal = models.ManyToManyField(Journal)
Django gonna create a relation table :
Behind the scenes, Django creates an intermediary join table to represent the many-to-many relationship
So you'll have many rows in this table, which is how it works, but journal instance and researcher instance in THEIR table will be unique.
Your error is maybe coming from how you save. Instead of :
model_obj = Journal(**db_model_fields)
model_obj.save()
Try to just do this:
model_obj = Journal.objects.get_or_create(journal_id)
This way you'll get it if it already exists. As none of your fields are unique, you're creating new journal but there's no problem cause django is generating unique ID each time you add a new journal.