Django: get all parent objects that contain object in manytomany field - django

I have the following User object as a simple example
class AppUser(AbstractBaseUser, PermissionsMixin):
follows = models.ManyToManyField('self', related_name='follows_users', symmetrical=False)
I want to get all the user's that follow a user. for example
u2.follows.add(u1)
u3.follows.add(u1)
I want to return [u2,u3] as a result for users that follow u1

You need to use the related_name you set in the field definition:
users_who_follow_u1 = u1.follows_user.all()
You should also find a better name for it such as followed_by.

Related

Django tabularInline 'categories.Category_children_ids' has more than one ForeignKey to 'categories.Category'. You must specify a 'fk_name' attribute

I'm want to create nested categories, model work fine.
class Category(models.Model):
category_name = models.CharField(max_length=100)
children_ids = models.ManyToManyField(
"Category", blank=True, related_name="categories"
)
...etc
but, when i add inline for admin panel
class ChildrensInline(admin.TabularInline):
model = Category.children_ids.through
compiler shows me error:
'categories.Category_children_ids' has more than one ForeignKey to 'categories.Category'. You must specify a 'fk_name' attribute.
I also try fk_name='categories', and columns name inside Category_children_ids table, but it not work
The value for fk_name has to be the name of the class (lower-case), prefixed with either "from_" or "to_", depending on your needs. So in your case, it has to be either fk_name='from_category' or fk_name='to_category':
class ChildrensInline(admin.TabularInline):
model = Category.children_ids.through
fk_name='from_category' # or: 'to_category'
Generally, a quick way to figure such things out is to (transiently) place a simple print(model.__dict__) pretty much anywhere in your code, here e.g. right after the second line. Then all fields of model will be shown in the console output.

How to get objects which have an object in their ManyToManyField?

There's this model:
class User(AbstractUser):
followers = models.ManyToManyField('self', symmetrical=False, related_name='followings', null=True, blank=True)
Say I have a user object called 'A'. In a view, I want to filter User objects which have 'A' as their follower. How can I do that?
You can query with:
A.followings.all()
The related_name=… [Django-doc] is the name of the relation in reverse, so it is a QuerySet of Users that have A as follower.
If you do not specify a related_name=… it will take the name of the model in lowercase followed by the …_set suffix, so user_set in this case.
If you only have the primary key of A, then you can query with:
User.objects.filter(followers__id=a_id)
Note: Using null=True [Django-doc] for a ManyToManyField [Django-doc] makes no sense: one can not enforce by the database that a ManyToManyField should be non-empty, therefore as the documentation says: "null has no effect since there is no way to require a relationship at the database level."

What does related_name do?

In the Django documentation about related_name it says the following:
The name to use for the relation from the related object back to this one. It’s also the default value for related_query_name (the name to use for the reverse filter name from the target model). See the related objects documentation for a full explanation and example. Note that you must set this value when defining relations on abstract models; and when you do so some special syntax is available.
If you’d prefer Django not to create a backwards relation, set related_name to '+' or end it with '+'.
I didn't understand it clearly. If somebody would please explain it a bit more, it would help me a lot.
When you create a foreign key, you are linking two models together. The model with the ForeignKey() field uses the field name to look up the other model. It also implicitly adds a member to the linked model referring back to this one.
class Post(models.Model):
# ... fields ...
class Comment(models.Model):
# ... fields ...
post = models.ForeignKey(Post, related_name=???)
There are three possible scenarios here:
1. Don't specify related_name
If you don't specify a name, django will create one by default for you.
some_post = Post.objects.get(id=12345)
comments = some_post.comment_set.all()
The default name is the relation's name + _set.
2. Specify a custom value
Usually you want to specify something to make it more natural. For example, related_name="comments".
some_post = Post.objects.get(id=12345)
comments = some_post.comments.all()
3. Prevent the reverse reference from being created
Sometimes you don't want to add the reference to the foreign model, so use related_name="+" to not create it.
some_post = Post.objects.get(id=12345)
comments = some_post.comment_set.all() # <-- error, no way to access directly
related_query_name is basically the same idea, but when using filter() on a queryset:
posts_by_user = Post.objects.filter(comments__user__id=123)
But to be honest I've never used this since the related_name value is used by default.
If in a model you have a ForeignKey field (this means you point through this field to other model):
class Author(models.Model):
name = ......
email = .....
class Article(models.Model):
author = models.ForeignKey(Author)
title= ....
body = ....
if you specify related_name on this field
class Article(modles.Model):
author = models.ForeignKey(Author, related_name='articles')
you give a name to the attribute that you can use for the relation (named reverse realationship) from the related object back to this one (from Author to Article). After defining this you can retrieve the articles of an user like so:
author.articles.all()
If you don't define a related_name attribute, Django will use the lowercase name of the model followed by _set (that is, in our case, article_set) to name the relationship from the related object back to this one, so you would have to retrieve all articles of an user like so:
author.article_set.all()
If you don't want to be possible a reverse relationship (from the model to which points your ForeignKey filed to this model (the model in which the ForeignKey field is defined) you can set
class Author(models.Model):
author = models.ForeignKey(User, related_name='+')

Remove ForeignKey relationship

I want to remove the relationship between BUser and Profile:
Since the ForeignKey doesn't allow null values I have to iterate (performance is awful!) like this to remove all the relations:
for u in user.profile_set.all():
u.delete()
class Profile(models.Model):
user = models.ForeignKey('BUser')
class BUser(AbstractBaseUser, PermissionsMixin):
username = models.CharField(max_length=40, unique=True)
There is another way to delete all the relations [with a better performance]?
I've tried with:
obj.transparentprofile_set = None
obj.transparentprofile_set.clear()
obj.transparentprofile_set.empty()
but, like I said, since there's not null=True in the ForeignKey I can't use them.
You may use my way, instead touching related model via dotted ORM childs etc objects.childs_set, its more clear and human readable code:
# Get user instance
user = User.objects.get(pk=<uid>)
# Remove profiles
Profile.objects.filter(user=user).delete()
Also, you need remember about related_name Model parameter, so instead profile_set you can use:
class Profile(models.Model):
user = models.Foreignkey(
to=User,
related_name='profiles'
)
# Use related name alias in code
profiles = user.profiles.all()
profile = user.profiles.filter(pk=<profile_id>)

Filtering a model by checking for presence in another model via ManyToMany relationship

Given the following two models:
class Card(models.Model):
disabled = models.BooleanField(default=False)
class User(models.Model):
owned_cards = models.ManyToManyField(Card)
Given a certain user, how can I, in one query, get all the Card objects that are not disabled, and are also present in that user's owned_cards field?
It's actually quite simple, you can use the owned_cards field of a user object as a manager.
enabled_cards = theuser.owned_cards.filter(disabled=False)
Answer for the second question in the comments, this should work, using Q objects to negate the lookup.
not_owned_cards = Card.objects.filter(~Q(id__in=theuser.owned_cards.all()), disabled=False)