My models are as follows:
class AppUser(models.Model):
id = models.AutoField(primary_key=True)
user = models.OneToOneField(User)
states = models.ManyToManyField(State)
class ABC(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=50)
email = models.EmailField()
app_user = models.ForeignKey(AppUser, null=True, blank=True)
I want to query my database for list of objects present in ABC model and I want to filter it according to the list of States.
I am trying something like this:
ABC.objects.filter(app_user__states__in = state_list).values('id','name')
But this is not working. Can I even access a many to many field like this or do I need to create a custom through table.
Yes, you can.
For queryset:
ABC.objects.filter(app_user__states__in = [1,2]).values('id', 'name')
you'll get sql like this:
>>> print(ABC.objects.filter(app_user__states__in = [1,2]).values('id', 'name').query)
SELECT "test_abc"."id", "test_abc"."name"
FROM "test_abc"
INNER JOIN "test_appuser" ON ("test_abc"."app_user_id" = "test_appuser"."id")
INNER JOIN "test_appuser_states" ON ("test_appuser"."id" = "test_appuser_states"."appuser_id")
WHERE "test_appuser_states"."state_id" IN (1, 2);
Looks fine. Maybe it doesn't work as you expected?
Related
I'm trying to access data in a join table using django. I have a list of users and a list of groups in a ManyToMany relationship.
class Member(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
groups = models.ManyToManyField(Group, related_name="members")
class Team(models.Model):
name = models.CharField(max_length=25)
creator = models.ForeignKey("footballteamapi.Member", on_delete=models.CASCADE)
The join table looks like this:
footballteamapi_member_teams
id+member_id+team_id
1+1+1
2+1+2
3+2+1
How do I go about accessing that table since there's no coinciding model to reference (i.e. Team.objects.all())?
You can use access the Team table in the reverse direction like so:
member = Member.objects.get(id="<member-id>")
teams = member.team_set.all()
But I like to set the related_name option on the ForiegnKey field like so:
class Team(models.Model):
creator = models.ForeignKey(..., related_name='teams')
...
Then the above changes to:
member = Member.objects.get(id="<member-id>")
teams = member.teams.all()
I want to create a queryset that references three related models, and allows me to filter. The SQL might look like this:
SELECT th.id, th.customer, ft.filename, fva.path
FROM TransactionHistory th
LEFT JOIN FileTrack ft
ON th.InboundFileTrackID = ft.id
LEFT JOIN FileViewArchive fva
ON fva.FileTrackId = ft.id
WHERE th.customer = 'ACME, Inc.'
-- AND ft.filename like '%storage%' --currently don't need to do this, but seeing placeholder logic would be nice
I have three models in Django, shown below. It's a bit tricky, because the TransactionHistory model has two foreign keys to the same model (FileTrack). And FileViewArchive has a foreign key to FileTrack.
class FileTrack(models.Model):
id = models.BigIntegerField(db_column="id", primary_key=True)
filename = models.CharField(db_column="filename", max_length=128)
class Meta:
managed = False
db_table = "FileTrack"
class TransactionHistory(models.Model):
id = models.BigIntegerField(db_column="id", primary_key=True)
customer = models.CharField(db_column="Customer", max_length=128)
inbound_file_track = models.ForeignKey(
FileTrack,
db_column="InboundFileTrackId",
related_name="inbound_file_track_id",
on_delete=models.DO_NOTHING,
null=True,
)
outbound_file_track = models.ForeignKey(
FileTrack,
db_column="OutboundFileTrackId",
related_name="outbound_file_track_id",
on_delete=models.DO_NOTHING,
null=True,
)
class Meta:
managed = False
db_table = "TransactionHistory"
class FileViewArchive(models.Model):
id = models.BigIntegerField(db_column="id", primary_key=True)
file_track = models.ForeignKey(
FileTrack,
db_column="FileTrackId",
related_name="file_track_id",
on_delete=models.DO_NOTHING,
null=True,
)
path = models.CharField(db_column="Path", max_length=256)
class Meta:
managed = False
db_table = "FileViewArchive"
One thing I tried:
qs1 = TransactionHistory.objects.select_related('inbound_file_track').filter(customer='ACME, Inc.')
qs2 = FileViewArchive.objects.select_related('file_track').all()
qs = qs1 & qs2 # doesn't work b/c they are different base models
And this idea to use chain doesn't work either because it's sending two separate queries an I'm not altogether sure if/how it's merging them. I'm looking for a single query in order to be more performant. Also it returns an iterable, so I'm not sure I can use this in my view (Django Rest Framework). Lastly x below returns a TransactionHistory object, so I can't even access the fields from the other two models.
from itertools import chain
c = chain(qs1 | qs2) # great that his this lazy and doesn't evaluate until used!
type(c) # this returns <class 'itertools.chain'> and it doesn't consolidate
x = list(c)[0] # runs two separate queries
type(x) # a TransactionHistory object -> so no access to the Filetrack or FileViewArchive fields
Any ideas how I can join three models together? Something like this?:
qs = TransactionHistory.objects.select_related('inbound_file_track').select_related('file_track').filter(customer='ACME, Inc.', file_track__filename__contains='storage')
More info: this is part of a view that will look like below. It returns a querysets that is used as part of a Django Rest Framework view.
class Transaction(generics.ListAPIView):
serializer_class = TransactionSerializer
def filter_queryset(self, queryset):
query_params = self.request.query_params.copy()
company = query_params.pop("company", [])[0]
filename = query_params.pop("filename", [])[0]
# need code here that generate filtered queryset for filename and company
# qs = TransactionHistory.objects.select_related('inbound_file_track').select_related('file_track').filter(customer='ACME, Inc.', file_track__filename__contains='storage')
return qs.order_by("id")
Based from the sql query you shared, you are filtering based on the inbound_file_track file name. So something like this should work:
TransactionHistory.objects.select_related(
'inbound_file_track',
).prefetch_related(
'inbound_file_track__file_track_id',
).filter(
customer='ACME, Inc.', inbound_file_track___filename__contains='storage',
)
For the below sample schema
# schema sameple
class A(models.Model):
n = models.ForeignKey(N, on_delete=models.CASCADE)
d = models.ForeignKey(D, on_delete=models.PROTECT)
class N(models.Model):
id = models.AutoField(primary_key=True, editable=False)
d = models.ForeignKey(D, on_delete=models.PROTECT)
class D(models.Model):
dsid = models.CharField(max_length=255, primary_key=True)
class P(models.Model):
id = models.AutoField(primary_key=True, editable=False)
name = models.CharField(max_length=255)
n = models.ForeignKey(N, on_delete=models.CASCADE)
# raw query for the result I want
# SELECT P.name
# FROM P, N, A
# WHERE (P.n_id = N.id
# AND A.n_id = N.id
# AND A.d_id = \'MY_DSID\'
# AND P.name = \'MY_NAME\')
What am I trying to achieve?
Well, I’m trying to find a way somehow be able to write a single queryset which does the same as what the above raw query does. So far I was able to do it by writing two queryset, and use the result from one queryset and then using that queryset I wrote the second one, to get the final DB records. However that’s 2 hits to the DB, and I want to optimize it by just doing everything in one DB hit.
What will be the queryset for this kinda raw query ? or is there a better way to do it ?
Above code is here https://dpaste.org/DZg2
You can archive it using related_name attribute and functions like select_related and prefetch_related.
Assuming the related name for each model will be the model's name and _items, but it is better to have proper model names and then provided meaningful related names. Related name is how you access the model in backward.
This way, you can use this query to get all models in a single DB hit:
A.objects.all().select_related("n", "d", "n__d").prefetch_related("n__p_items")
I edited the code in the pasted site, however, it will expire soon.
I have 4 models in my simplified design
class modelA(models.Model):
name = models.CharField()
class modelsUser(model.Model):
username = models.CharField()
class bridge(models.Model):
user = models.ForeignKey(modelUser, on_delete=models.CASCADE, related_name='bridges')
modelA = models.ForeignKey(modelA, on_delete=models.CASCADE, related_name='bridges')
class subModelA(models.Model):
modelA = models.ForeignKey(modelA, on_delete=models.CASCADE, related_name='subModelAs')
value = models.IntegerField()
class subModelB(models.Model):
modelA = models.ForeignKey(modelA, on_delete=models.CASCADE, related_name='subModelBs')
text = models.TextField()
What I am trying to to is to get all subModelBs and subModelAs that are for modelAs for which given modelUser have bridge.
I've started with this:
user = modelUser.objects.get(pk=1)
bridges = user.bridges.all()
What I've been thinking is something like this:
subModelBs = subModelB.objects.filter(modelA__in=bridges__modelA)
but unfortunately it doesn't work because of error that __modelA is not defined.
Is there any proper way to do this?
Find first the modelAs and then do two other queries:
modelAs = bridge.objects.filter(user__pk=1).values_list('modelA', flat=True)
subModelAs = subModelA.object.filter(modelA__in=modelAs)
subModelBs = subModelB.object.filter(modelA__in=modelAs)
A good question first of all!
Tried reproducing on my system, the following worked for me:
user = modelUser.objects.get(pk=1)
bridges = user.bridges.all()
subModelAs = subModelA.objects.filter(
modelA_id__in=[x.modelA_id for x in list(bridges)]
)
And similarly for subModelBs. Hope this helps you well.
My Django models look like this:
class User(models.Model):
userid = models.CharField(max_length=26,unique=True)
#some more fields that are currently not relevant
class Followers(models.Model):
user = models.ForeignKey('User',related_name='usr')
coins = models.IntegerField()
followers = models.CharField(max_length=26, null=True, blank=True)
I would now like to make a filter query in my Followers table selecting every entry where users have ID x and followers have ID y (I expect to get one result from the query).
To visualize what I have tried and know won't work is this:
queryfilter = Followers.object.filter(followers=fid, user=uid)
and this:
queryfilter = Followers.object.filter(followers=fid, user__userid=uid)
In the end I would like to access the coins:
c = queryfilter.coins
It may be possible that I cannot do it with one single query and need two, since I am trying to do a filter query with two tables involved.
Firstly, I have modified your 'Followers' model (for naming convention).
models.py
class Follower(models.Model):
user = models.ForeignKey('User', related_name='followers')
coins = models.IntegerField()
key = models.CharField(max_length=26, null=True, blank=True)
Your queryset should be ..
views.py
#coins
coins = Follower.objects.filter(key=fid, user__userid=uid).get().coins