Django ORM annotate on foreign key query - django

I have 3 models:
class Project(models.Model):
name = models.CharField(max_length=300, unique=True)
description = models.CharField(
max_length=2000,
blank=True,
null=True,
default=None
)
class QuestionSession(models.Model):
name = models.CharField(
max_length=500, default=None, null=True, blank=True
)
project = models.ForeignKey(
Project,
on_delete=models.CASCADE,
related_name='sessions',
blank=True,
null=True,
default=None
)
class Question(models.Model):
description = models.CharField(max_length=500)
question_session = models.ForeignKey(
QuestionSession,
on_delete=models.CASCADE,
related_name='questions',
blank=True,
null=True,
default=None
)
As you can see, Project contains Sessions, Session contains questions.
What I'm trying to achieve is I wanna fetch a single Project with sessions and number of questions in them. I can do it easily with 2 different queries but I cannot do it in 1 query.
My serializers:
class SingleProjectSerializer(serializers.ModelSerializer):
sessions = MinifiedSessionSerializer(many=True)
class Meta:
model = Project
fields = [
'id',
'name',
'description',
'sessions'
]
class MinifiedSessionSerializer(serializers.ModelSerializer):
questions_number = serializers.IntegerField()
class Meta:
model = QuestionSession
fields = [
'id',
'name',
'questions_number'
]
I used to grab sessions in a single query like this:
Project.objects.get(id=project_id).sessions.annotate(questions_number=Count('questions'))
But how to do it now? I need to fetch the project first and then annotate on sessions. I have no idea how to do it. I need a query like this:
Project.objects.filter(pk=project_id).annotate(sessions__questions_number=Count('sessions__questions'))

I don't believe it's possible through Django ORM. The only solution which I can think of is changing the way on how you ask for the data:
sessions = QuestionSession.objects.filter(project_id=project_id).select_related('project').annotate(questions_number=Count('questions'))
project = sessions[0].project
That would end up in a single query, but I assume you want to pass this project instance down to the DRF serializer. In such case the project knows nothing about prefetched sessions, so it would need to be handled separately. Additionally there is an issue when particular project has no associated sessions - sessions[0].project would raise exception. To keep the code clean I would probably stay with yours current approach (but then - the issue remains unresolved to keep everything in one db hit).

Related

How to use prefetch_related in django rest api with foreying key and heritage

I am working on this Django project, it uses heritage and foreign keys for its models.
These are the models:
class SetorFii(models.Model):
name = models.CharField(max_length=255)
class Asset(models.Model):
category = models.ForeignKey(
Category, related_name='categories', on_delete=models.CASCADE)
ticker = models.CharField(max_length=255, unique=True)
price = models.FloatField()
class Fii(Asset):
setor_fii = models.ForeignKey(
SetorFii, null=True, default=None, on_delete=models.CASCADE, related_name="setor_fiis")
Class Crypto(Asset):
circulating_supply = models.FloatField(default=0)
class PortfolioAsset(models.Model):
asset = models.ForeignKey(Asset, on_delete=models.CASCADE)
I would like to get the field setor_fii in the PortfolioAssetSerializer, That is what I tried without success.
I get this error message: Cannot find 'setor_fii' on PortfolioAsset object, 'setor_fii' is an invalid parameter to prefetch_related()
Would like some help to achieve that.
The serializer:
class PortfolioAssetSerializer(serializers.ModelSerializer):
category = serializers.CharField(source='asset.category.name')
setor_fii = serializers.CharField(source='asset.setor_fii.name')
class Meta:
model = models.PortfolioAsset
fields = (
'id',
'category',
'setor_fii'
)
The view
class PortfolioAssetList(generics.ListAPIView):
serializer_class = serializers.PortfolioAssetSerializer
def get_queryset(self):
return models.PortfolioAsset.objects.filter(portfolio_id=self.kwargs['pk']).prefetch_related('setor_fii')
To prefetch setor_fii, you will have to go through asset. Since Fii inherits from Asset, Asset will have an automatically created one-to-one field named fii. You can then use that to access setor_fii:
PortfolioAsset.objects.filter(
portfolio_id=self.kwargs['pk'],
).prefetch_related(
'asset__fii__setor_fii',
)
Also since the whole relationships here are just one to ones, you can use select_related instead of prefetch_related to get them all in one query (compared to three queries using prefetch_related):
PortfolioAsset.objects.filter(
portfolio_id=self.kwargs['pk'],
).select_related(
'asset__fii__setor_fii',
)

is it possible to have two parameters in limit_choices_to in django models?

I'll shorten the code as simple as possible. Supposedly, we do have two models.
models.py > Products table
CATEGORY = (
('Hard Disk Drive', 'Hard Disk Drive'),
('Solid State Drive', 'Solid State Drive'),
('Graphics Card', 'Graphics Card'),
('Laptop', 'Laptop'),
('RAM', 'RAM'),
('Charger', 'Charger'),
('UPS', 'UPS'),
('Mouse', 'Mouse'),
('Keyboard', 'Keyboard'),
('Motherboard', 'Motherboard'),
('Monitor', 'Monitor'),
('Power Supply', 'Power Supply'),
('Router', 'Router'),
('AVR', 'AVR'),
('Tablet', 'Tablet'),
('System Unit', 'System Unit'),
('Audio Devices', 'Audio Devices'),
('CPU', 'CPU'),
('Others', 'Others'),
)
class Product(models.Model):
model_name = models.CharField(max_length=100, null=True, blank=True)
asset_type = models.CharField(max_length=20, choices=CATEGORY, blank=True)
date = models.DateField(null=True, blank=True)
And the other table > Order table
class Order(models.Model):
product_order = models.ForeignKey(Product, on_delete=models.CASCADE, null=False)
employee = models.ForeignKey(User, models.CASCADE, null=False)
date = models.DateTimeField(auto_now_add=True)
remarks = models.TextField()
And we all know that adding this code will limit the foreignkey choices under the Order form.
limit_choices_to={"asset_type": "Hard Disk Drive"}
limit_choices_to={"asset_type": "Solid State Drive"}
My goal here is to show items from Products table whose asset_type is either "Hard Disk Drive" OR "Solid State Drive". I've read the documentation of Django for "limit_choices_to" and can't see any pertaining to some kind of solution in here. Thank you in advance who knows a way to make this possible.
You can work with the __in lookup [Django-doc] to specify a list of allowed values:
class Order(models.Model):
product_order = models.ForeignKey(
Product,
on_delete=models.CASCADE,
limit_choices_to=dict(asset_type__in=['Hard Disk Drive', 'Solid State Drive'])
)
# …
Note: Specifying null=False [Django-doc] is not necessary: fields are by default not NULLable.
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.

Django access manytomany field from related_name in a view

I have what i think is a simple question but I am struggling to find out how it works. I get how related name works for foreign keys but with many to many fields it seems to break my brain.
I have two 3 models at play here. A User, TeamMember and Team Model as seen below.
User model is the built in django model.
#TeamMember Model
class TeamMember(models.Model):
member = models.ForeignKey(User, on_delete=models.SET(get_default_team_member), verbose_name='Member Name', related_name="team_members")
...
#Team Model
class Team(models.Model):
name = models.CharField(max_length=50)
manager = models.ForeignKey(TeamMember, on_delete=models.SET_NULL, related_name="managers", null=True, blank=True)
team_lead = models.ForeignKey(TeamMember, on_delete=models.SET_NULL, related_name="tls", null=True, blank=True)
tps = models.ForeignKey(TeamMember, on_delete=models.SET_NULL, related_name="tps", null=True, blank=True)
members = models.ManyToManyField(TeamMember, blank=True, related_name="members")
...
Now in a view i want to access a specific users team. I thought i could do this by doing something like this:
member = TeamMember.objects.get(pk=1)
member_team = member.members.name
However if I print member_name than it prints nothing. If I try to access any of the other fields on that model like member.members.team_lead.first_name it fails to find the team_lead field. I understand that this has a .all() attribute but i thought it was tied to the team object through the members field. So if that member matches the team it would give me the team. So I thought it might be an issue if the same member was linked to more than one team (which is possible) so i tired something like this member.members.all().first().name and i get an error that states it cannot get name from NoneType.
Is there an easy way to get the team name from a relationship like this or am i better off just doing a team query with the user?
Thanks,
jAC
First of all, I would like to point out that you are not using the related_name (and related_query_name parameters in a proper way). I think this SO post will help you to understand the concept in a better way.
So, I would change the related_name (and related_query_name) values in the Team model as below,
class Team(models.Model):
name = models.CharField(max_length=50)
manager = models.ForeignKey(
TeamMember,
on_delete=models.SET_NULL,
related_name="teams",
related_query_name="team",
null=True,
blank=True,
)
team_lead = models.ForeignKey(
TeamMember,
on_delete=models.SET_NULL,
related_name="teams",
related_query_name="team",
null=True,
blank=True,
)
tps = models.ForeignKey(
TeamMember,
on_delete=models.SET_NULL,
related_name="teams",
related_query_name="team",
null=True,
blank=True,
)
members = models.ManyToManyField(
TeamMember, blank=True, related_name="teams", related_query_name="team"
)
...
Now in a view i want to access a specific user's team.
Since the Team and TeamMember models are connected via ManyToManyField, you may have "zero or more" Teams associated with a single TeamMember
So, the following query will get you all the teams associated with a particular TeamMemeber
team_member = TeamMember.objects.get(pk=1)
all_teams = team_member.teams.all()
You can also iterate over the QuerySet as,
team_member = TeamMember.objects.get(pk=1)
for team in team_member.teams.all():
print(team.name)
For anyone wondering what I did based on JPG's advice was the for loop option
team_member = TeamMember.objects.get(pk=1)
teams = [t.name for t in team_member.members.all()]
I personally do not care which team i get as my need in this case is just to pass a team through even if it is none. So i just use team = team[0] if teams.count() > 0 else "No team"

get field name from one app to another app in django

We have defined two apps: Manin_matrix and SW_matrix.
We want the field name (project_name) present in class GENERAL_t of models.py file inside Main_matrix app to be inside the models.py file of SW_matrix app.
Basically, project_name_work field in class EDVT of SW_matrix models.py should be the same as project_name of Main_matrix app.
We want this so that in a database for EDVT table we will get the same project id along with the project name.
Main_matrix/models.py
class GENERAL_t(models.Model):
project_name = models.CharField(
blank=True,
null=True,
max_length=40,
verbose_name='Project_Name'
)
platform = models.CharField(
blank=True,
null=True,
max_length=40,
verbose_name='Platform SW'
)
SW_matrix/models.py
class EDVT(models.Model):
project_rel=models.ForeignKey(
GENERAL_t,
null=True,
on_delete=models.SET_NULL,
verbose_name="Choose Project"
)
project_name_work = models.ForeignKey(
GENERAL_t.project_name,
null=True,
verbose_name='Project_Name'
)
You don't need to do that, and a FK won't allow that. The FK field is just the ID of a row in another table, nothing more complex than that really.
When working with ForeignKey links like this it's a good idea to use strings so that you don't have to import the related model. The format of the string is '<appname.ModelName>
For example, I link an object to a (django) content type like this;
source_content_type = models.ForeignKey(
verbose_name=_('source content type'),
to='contenttypes.ContentType',
on_delete=models.CASCADE
)
So to link your EDVT model to a GENERAL_t you'd do;
class EDVT(models.Model):
general_t = models.ForeignKey(
to='Manin_matrix.GENERAL_t',
null=True,
verbose_name='general t'
)
Then if EDVT needs to be able to return a project_name you could do it as a property.
class EDVT(models.Model):
project_rel = models.ForeignKey(
to='Manin_matrix.GENERAL_t',
null=True,
verbose_name='Project_Name'
)
#property
def project_name(self):
return self.project_rel.project_name
Then you can access it either by EDVT.objects.first().project_name or if you didn't implement the property you can do EDVT.objects.first().general_t.project_name (or any other field on that model)

How to link two models, each with its own template using foreignKey in Django

I want to link two models using foreignKey, The problem is when i try to do that, one model does not get foreignKey value for the next model in the database table.
The aim is for user to fill information on the first page (have its own model and template) then click next (fill more info in the next page having its own model and template) then click next for the same logic. then when other users view this post it must show all content from different models in one page. here is my code.
1st model
class Map(models.Model):
user = models.ForeignKey(User, default=None, blank=True, null=True, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
position = GeopositionField()
HAVING ITS OWN TEMPLATE
2nd Model
class Post(models.Model):
parent = models.ForeignKey("self", default=None, blank=True, null=True, on_delete=models.CASCADE)
user = models.ForeignKey(settings.AUTH_USER_MODEL, default=None, on_delete=models.CASCADE)
title = models.CharField(max_length=50)
content = models.TextField()
map = models.ForeignKey(Map, related_name='mapkey', default=None, blank=True, null=True, on_delete=models.CASCADE)
HAVING ITS OWN TEMPLATE BUT also has serializer method(API) below:
class PostModelSerializer(serializers.ModelSerializer):
user = UserDisplaySerializer(read_only=True)
parent = ParentPostModelSerializer()
map = serializers.SerializerMethodField()
class Meta:
start_date = forms.DateField(widget = forms.SelectDateWidget())
end_date = forms.DateField(widget = forms.SelectDateWidget())
model = Post
fields = [
'id',
'user',
'title',
'content'
'image',
'map',
]
Please focus only on the map field as its isolated in the above codes
everything works perfectly, but the foreignKey. also i didnt see the need to include all the code here but snippets.
i have been struggling with this for days now. do i need to write an api for 1st model also? for views i used class based views.
the database table for model 2, on the map field it shows null all the time.
I have i have provided enough information.Thanks