Django get all records of related models - python

I have 3 models for a to-do list app:
class Topic(models.model)
user = models.ForeignKey(UserProfile)
lists = models.ManyToManyField(List)
class List(models.model)
activities = models.ManyToManyField(Activity)
class Activity(models.model)
activity = models.CharField(max_length=250)
This makes sense when a user selects a Topic, then a List (sub-category), which shows all activities on that list.
But how would I efficiently query things like
All activities of user X (regardless topic or list)
All activities of topic X for user X (regardless of lists)
Would I need to use select_related() in the query and than loop trough the related objects, or is there a more efficient way without looping? (Or should I change my models?)

Use the double-underscore syntax to query across relationships.
All activities for user:
Activity.objects.filter(list__topic__user=my_user)
All activities for user for a topic:
Activity.objects.filter(list__topic=my_topic)
(Note that currently a Topic is for a single user only. Not sure if that's what you mean: you describe a user selecting a topic, which couldn't happen here. Potentially the link from Topic to UserProfile should go the other way, or be a ManyToMany.)

Give them related names (it's easier to manage):
class Topic(models.model)
user = models.ForeignKey(UserProfile, related_name = 'topics')
lists = models.ManyToManyField(List, related_name = 'topics')
class List(models.model)
activities = models.ManyToManyField(Activity, related_name = 'lists')
class Activity(models.model)
activity = models.CharField(max_length=250)
Then you can do awesome things:
user = UserProfile.objects.get(pk=1) # for example
user.topics.all() # returns all topics
topic = Topic.objects.get(pk=1) # for example
topic.users.get(pk=1) # returns user
lists = topic.lists.all() # returns List object instances QuerySet
for list in lists:
list.activites.all()
Handy info: https://docs.djangoproject.com/en/dev/ref/models/relations/

Related

Update a model after deleting a row in another model in Django

I have two models UserProfile and ChatUser.
ChatUser.models.py
class ChatUser(models.Model):
chat = models.ForeignKey(ChatRoom,on_delete=models.CASCADE)
user = models.ForeignKey(User,on_delete=models.CASCADE)
UserProfile.models.py
class UserProfile(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE)
phone_number = models.IntegerField(default=0)
image = models.ImageField(upload_to='profile_image',blank=True,default='prof1.jpeg')
gender = models.CharField(max_length=10)
joined = JSONField(null=True)
ChatRoom.models
class ChatRoom(models.Model):
eid = models.CharField(max_length=64, unique=True)
name = models.CharField(max_length=100)
location = models.CharField(max_length=50)
vehicle = models.CharField(max_length=50)
brand = models.CharField(max_length=50)
max_limit = models.IntegerField()
joined in UserProfile is an array consisting room ids of the chatrooms model. Now when I delete a ChatRoom row, it automatically deletes the Foreign Key referenced ChatUser object since I am using on_delete=models.CASCADE. But how to update the joined in UserProfile model. I want to remove the id of the deleted ChatRoom from UserProfile.joined
I have used the django.db.models.signals to solve the updating part.
#receiver(post_delete,sender=ChatUser)
def update_profile(sender,instance,**kwargs):
id = instance.chat_id
joined = instance.user.userprofile.joined
if id in joined:
joined.remove(id)
model = profiles.models.UserProfile.objects.filter(user_id=instance.user.id).update(joined=joined)
SDRJ and Willem Van OnSem, thank you for your suggestions
#SAI SANTOSH CHIRAG- Please explain this. You have a ChatUser model that adds user_id and chatroom_id. Now, if I need to find out the list of chatrooms a user has joined, I can simply query this model. If I want to find out the total number of users in a specific chatroom then I can still query this table. Why do I need to keep track of joined in UserProfile? And I am basing this on the premise that joined keeps track of chatroom ids that a user has joined.
At any point, if you choose to add a many-to-many field in any of the models then this is my opinion. E.g Let's assume that you add the following in the UserProfile model
chatroom = models.ManytoManyField(Chat)
Imagine as the number of chatrooms the user joins grows, the list becomes larger and larger and I find it inconvenient because I will have this tiny scroll bar with a large list. It's not wrong but I simply stay away from M2M field for this purpose especially if I expect my list to grow as my application scales.
I prefer the ChatUser approach that you used. Yes, I might have repeating rows of user_ids or repeating chatroom_ids but I don't mind. I can live with it. It's still a bit cleaner to me. And this is simply my opinion. Feel free to disagree.
Lastly, I would rename the ChatUser model to ChatRoomUser...Why? Just by the name of it, I can infer it has something to do with two entities Chatroom and User.

How to repeat a many-to-many relationship in Django?

I have these models in my Django app:
from django.db import models
from django.contrib.auth import get_user_model
User = get_user_model()
class Animal(models.Model):
name = models.CharField(max_length=100, unique=True)
class AnimalList(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
list = models.ManyToManyField(Amimal)
I want to add the same pokemon to the same list twice:
>>> animal = Animal.objects.create(name='milla')
>>> user = User.objects.create(username='user')
>>> list = AnimalList.objects.create(user=user)
>>> list.list.add(animal)
>>> list.list.add(animal)
>>> animal.save()
The animal is added only once, though:
>>> list.list.all()
<QuerySet [<Animal: Animal object (3)>]>
I expected that, the documentation is explicit that
Adding a second time is OK, it will not duplicate the relation:
Yet, I do want to repeat the animals.
How could I do that? Is it possible by using ManyToManyField?
I could not repeat these relationships because Django applies a UniqueConstrant to many-to-many relationships. My solution was to add another table that referenced the animals:
class AnimalListItem(models.Model):
animal = models.ForeignKey(Animal, on_delete=models.CASCADE)
class AnimalList(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
list_items1 = models.ManyToManyField(AnimalListItem)
After that, every time I wanted to add an animal to a list, I had to create a list item first, pointing to the animal, then add this list item to the list column.
There are other possible solutions as well. For example, the through argument from ManyToManyField disables any default constraints that would be added to the relationship table. Or you could set db_constraint to False without through.
However, those were not solutions to me because, as the documentation states:
If the custom through table defined by the intermediate model does not enforce uniqueness on the (model1, model2) pair, allowing multiple values, the remove() call will remove all intermediate model instances
I needed to remove only one instance at a time, so removing all of them would not be feasible.

Complex Django queryset filtering, involving Q objects and tricky logic

I have a web-based chat app developed in Django. People can form their own chat groups in it, invite others and chatter away in the said groups.
I want to isolate all users who were recently online AND: (i) have already been sent an invite for a given group, OR (ii) have already participated (i.e. replied) at least once in the same given group. Note that user is a vanilla django.contrib.auth user.
To accomplish this, I'm writing:
online_invited_replied_users = User.objects.filter(id__in=recently_online,(Q()|Q()))
Assume recently_online to be correctly formulated. What should be the two Q objects?
The first Q() ought to refer invitees, the second to users who have replied at least once. I seem to be running into code-writer's block in formulating a well-rounded, efficient db query here. Please advise!
Relevant models are:
class Group(models.Model):
topic = models.TextField(validators=[MaxLengthValidator(200)])
owner = models.ForeignKey(User)
created_at = models.DateTimeField(auto_now_add=True)
class GroupInvite(models.Model):
#this is a group invite object
invitee = models.ForeignKey(User, related_name='invitee')
inviter = models.ForeignKey(User, related_name ='inviter')
sent_at = models.DateTimeField(auto_now_add=True)
which_group = models.ForeignKey(Group)
class Reply(models.Model):
#if a user has replied in a group, count that as participation
text = models.TextField(validators=[MaxLengthValidator(500)])
which_group = models.ForeignKey(Group)
writer = models.ForeignKey(User)
submitted_on = models.DateTimeField(auto_now_add=True)
Note: feel free to ask for more info; I'm cognizant of the fact that I may have left something out.
For users that are already invited to a group group, follow the GroupInvite.invitee foreign key backwards.
already_invited = Q(invitee__which_group=group)
The related_name you chose, invitee, doesn't really make sense, because it's linking to an group invite, not a user. Maybe `group_invites_received' would be be better.
For users that have already replied, follow the Reply.writer foreign key backwards.
already_replied = Q(reply__which_group=group)
In this case it's reply. because you haven't specified a related name.
Note that your current pseudo code
User.objects.filter(id__in=recently_online,(Q()|Q()))
will give an error 'non-keyword arg after keyword arg'.
You can fix this by either moving the non-keyword argument before the keyword argument,
User.objects.filter((Q()|Q()), id__in=recently_online)
or put them in separate filters:
User.objects.filter(id__in=recently_online).filter(Q()|Q())
Finally, note that you might need to use distinct() on your queryset, otherwise users may appear twice.

Query Many to Many in Datastore with Filter

I have to 2 entity models
class User(ndb.Model):
username = ndb.StringProperty()
# other properties
class Item(ndb.Model):
type = ndb.StringProperty()
# other properties
with a many to many relationship between User and Item
class UserItem(ndb.Model):
user = ndb.KeyProperty(kind=User)
item = ndb.KeyProperty(kind=Item)
# other properties
How can I query UserItem with a filter to Item.type. Something like select * from UserItem where UserItem.user = user_key and UserItem.item.type = item_type.
I know I can do it with StructuredProperty but there is an entity limit of 1mb. If it's not possible with the model how should I model the relationship to get get the query with filter work in datastore?
Thanks
You can't do this, and you shouldn't try. The datastore is not a relational database, and shouldn't be used as one.
Rather than having a linking UserItem table, you should consider storing a list of keys in one of the entities. For example, you could add a field to User:
items = ndb.KeyProperty(kind=Item, repeated=True)
and then with your User object, get the items by key and filter out the ones you need:
user_items = ndb.get_multi(my_user.items)
relevant_items = [item for item in user_items if item.type == my_type]
The exact structure is going to depend on your use-case, but the point is not to think in terms of traditional relationships like you would in SQL.

Is there a way to simulate a foreach in Django using queries?

I have a model 'Status' with a ManyToManyField 'groups'. Each group has a ManyToManyField 'users'. I want to get all the users for a certain status. I know I can do a for loop on the groups and add all the users to a list. But the users in the groups can overlap so I have to check to see if the user is already in the group. Is there a more efficient way to do this using queries?
edit: The status has a list of groups. Each group has a list of users. I want to get the list of users from all the groups for one status.
Models
class Status(geomodels.Model):
class Meta:
ordering = ['-date']
def __unicode__(self):
username = self.user.user.username
return "{0} - {1}".format(username, self.text)
user = geomodels.ForeignKey(UserProfile, related_name='statuses')
date = geomodels.DateTimeField(auto_now=True, db_index=True)
groups = geomodels.ManyToManyField(Group, related_name='receivedStatuses', null=True, blank=True)
class Group(models.Model):
def __unicode__(self):
return self.name + " - " + self.user.user.username
name = models.CharField(max_length=64, db_index=True)
members = models.ManyToManyField(UserProfile, related_name='groupsIn')
user = models.ForeignKey(UserProfile, related_name='groups')
I ended up creating a list of the groups I was looking for and then querying all users that were in any of those groups. This should be pretty efficient as I'm only using one query.
statusGroups = []
for group in status.groups.all():
statusGroups.append(group)
users = UserProfile.objects.filter(groupsIn__in=statusGroups)
As you haven't posted your models, its a bit difficult to give you a django queryset answer, but you can solve your overlapping problem by adding your users to a set which doesn't allow duplicates. For example:
from collections import defaultdict
users_by_status = defaultdict(set)
for i in Status.objects.all():
for group in i.group_set.all():
users_by_status[i].add(group.user.pk)
Based on your posted model code, the query for a given status is:
UserProfile.objects.filter(groupsIn__receivedStatuses=some_status).distinct()
I'm not 100% sure that the distinct() call is necessary, but I seem to recall that you'd risk duplicates if a given UserProfile were in multiple groups that share the same status. The main point is that filtering on many-to-many relationships works using the usual underscore notation, if you use the names as defined either by related_name or the default related name.

Categories