Iterate over ManyToMany field Django - python

I am having some problems with iterating over a ManyToMany field.
I want to have a Post and Tag model, and have the Post model extend the Tag model in the form of a ManyToMany relation.
Below are both my Tag and Post models.
class Tag(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return f"{self.name}"
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=150)
content = RichTextField()
timestamp = models.DateTimeField(auto_now_add=True)
slug = models.SlugField(unique=True,default="",max_length=1000)
tag = models.ManyToManyField(Tag,related_name='tags',blank=True)
# .... other non related functions
However, when I am in the Django shell, I can't seem to loop over these tags, despite the object having tags associated to it.
For example, I would do post1 = Post.objects.all()[0], and then post1.tag.name.all(), however it would give me an error saying "AttributeError: 'NoneType' object has no attribute 'all'
Everything else I have tried failed. What can I fix to solve the issue?
Thank you in advance

You are suppose to iterate through tags object like this
for tag in post1.tag.all():
print(tag.name)

It is very simple to iterate over ManyToManyField
for tag in post1.tag.all():
print(tag.name)
Other than iteration, I have some other suggestions. Like
tag will have many tags so naming it tags is more suitable. Secondly related_name is The name to use for the relation from the related object back to this one So in your case it should be posts instead of tags
So it will look like
tags = models.ManyToManyField(Tag, related_name='posts', blank=True)
Now you can access posts from any specific tag using this related_name. For example, If you have tag
tag1 = Tag.objects.all()[0]
Now you can get all posts with this tag using following line
tag1_posts = tag1.posts.all()

Related

Django get related object's QuerySet inside annotate

My Models
class Post(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
#Unnecessary
class Comment(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="user_comments")
post = models.ForeignKey(Post, on_delete=models.CASCADE,related_name="post_comments")
liked_users = models.ManyToManyField(User, blank=True, related_name="liked_comments")
disliked_users = models.ManyToManyField(User, blank=True related_name="disliked_comments")
#Unnecessary
What I Have
Normally, i use this code to make my comment objects have the attribute "voting" and order them by that attribute.
comments = models.Comment.objects.filter(post=post).annotate(voting=Count("liked_users")-Count("disliked_users")).order_by("-voting")
But when I want to query posts, I can't reach their comments:
What I Want
I want my QuerySet "posts" to have an attribute for every single post, called "best_3_comments". Which is a QuerySet of comments of them ordered by their voting's.
Can I achieve this without querying all of the comments everytime I query posts?
posts = models.Post.objects.annotate(
best_3_comments = get_query_of_the_posts_comments("post_comments").annotate(
voting=Count("liked_users")-Count("disliked_users")
).order_by("-voting")[:3]
)
What function(s) can i use to get query of the post's comments, and how should i approach this ? I'm completely open to new ideas of ways to do what I want to achieve, I couldn't find which function and how to use for it.
Thank you in advance.
I found a solution. I would like to share it for those in future looking for a way to solve their problems similar to mine.
posts = models.Post.objects.all() # The QuerySet You Want To Use
for post in posts:
post.best_3_comments = models.Comment.objects.filter(post=post).annotate(voting=Count("liked_users")-Count("disliked_users")).order_by("-voting")[:3]
The solution is iterating over our QuerySet, assigning an attribute for all of them which contains a QuerySet.

Is there a way to get all ManyToMany related objects in DRF

I have Tags in my application and it's possible to Tag different things like News, Events...
News and Events have a ManyToMany relation to Tags. Is it possible to get every object where the Tag is used?
My Models (shortened) look like this:
Tag Model
class Tag(models.Model):
title = models.CharField(max_length=35)
News Model
class News(models.Model):
title = models.CharField(max_length=75)
tag = models.ManyToManyField(Tag, related_name="news")
Event Model
class Event(models.Model):
title = models.CharField(max_length=75)
tag = models.ManyToManyField(Tag, related_name="event")
I know that I can get all News that have the Tags assigned by
tag = self.get_object()
tag.news.all()
But is it possible to get all News, Events... without 10 requests? I'm looking for something like tag.all.all()
try this
tag.news.all() | tag.event.all()
"news" and "event" are accessible due to reverse relationship here which are defined in their respective model fields with the keyword "related_name".
note that this may give duplicate tags as it is a union between the two.
for distinct tags,
(tag.news.all() | tag.event.all()).distinct()

Create a Blog which support multiple type of post

I am a new user of Django, and I am trying to figure out how to created a model which can support many kind (type) of elements.
This is the plot : I want to create a Blog module on my application.
To do this, I created a model Page, which describe a Blog Page. And a model PageElement, which describe a Post on the blog. Each Page can contain many PageElement.
A PageElement can have many types, because I want my users could post like just a short text, or just a video, or just a picture. I also would like (for example) the user could just post a reference to another model (like a reference to an user). Depending of the kind of content the user posted, the HTML page will display each PageElement in a different way.
But I don't know what is the right way to declare the PageElement class in order to support all these cases :(
Here is my Page model :
class Page(models.Model):
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
# Basical informations
title = models.CharField(max_length=150)
description = models.TextField(blank=True)
# Foreign links
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
related_name='pages_as_user'
)
created_at = models.DateTimeField(default=timezone.now)
# Other fields ....
class Meta:
indexes = [
models.Index(fields=['uuid']),
models.Index(fields=['user', 'artist'])
]
For now, I have two solutions, the first one use inheritance : When you create a new post on the blog, you create an Element which inherit from PageElement model. Here are my different Models for each cases :
class PageElement(models.Model):
page = models.ForeignKey(
Page,
on_delete=models.CASCADE,
related_name='%(class)s_elements'
)
updated_at = models.DateTimeField(default=timezone.now)
created_at = models.DateTimeField(default=timezone.now)
class PageImageElement(PageElement):
image = models.ImageField(null=True)
image_url = models.URLField(null=True)
class PageVideoElement(PageElement):
video = models.FileField(null=True)
video_url = models.URLField(null=True)
class PageTextElement(PageElement):
text = models.TextField(null=True)
class PageUserElement(PageElement):
user = models.ForeignKey(
'auth.User',
on_delete=models.CASCADE,
related_name='elements'
)
This solution would be the one I have choosen if I had to work with "pure" Python. Because I could stored each PageElement in a dictionnary and filter them by class. And this solution could be easily extended in the futur with new type of content.
But with Django models. It seems that is not the best solution. Because it will be really difficult to get all PageElement children from the database (I can't just write "page.elements" to get all elements of all types, I need to get all %(class)s_elements elements manually and concatenate them :/). I have thinked about a solution like below (I don't have tried it yet), but it seems overkilled for this problem (and for the database which will have to deal with a large number of request):
class Page(models.Model):
# ...
def get_elements(self):
# Retrieve all PageElements children linked to the current Page
R = []
fields = self._meta.get_fields(include_hidden=True)
for f in fields:
try:
if '_elements' in f.name:
R += getattr(self, f.name)
except TypeError as e:
continue
return R
My second "solution" use an unique class which contains all fields I need. Depending of the kind of PageElement I want to create, I would put type field to the correct value, put the values in the corresponding fields, and put to NULL all other unused fields :
class PageElement(models.Model):
page = models.OneToOneField(
Page,
on_delete=models.CASCADE,
related_name='elements'
)
updated_at = models.DateTimeField(default=timezone.now)
created_at = models.DateTimeField(default=timezone.now)
TYPES_CHOICE = (
('img', 'Image'),
('vid', 'Video'),
('txt', 'Text'),
('usr', 'User'),
)
type = models.CharField(max_length=60, choices=TYPES_CHOICE)
# For type Image
image = models.ImageField(null=True)
image_url = models.URLField(null=True)
# For type Video
video = models.FileField(null=True)
video_url = models.URLField(null=True)
# For type Text
text = models.TextField(null=True)
# For type User
user = models.ForeignKey(
'auth.User',
on_delete=models.CASCADE,
related_name='elements',
null=True
)
With this solution, I can retrieve all elements in a single request with "page.elements". But it is less extendable than the previous one (I need to modify my entire table structure to add a new field or a new kind of Element).
To be honnest, I have absolutly no idea of which solution is the best. And I am sure other (better) solutions exist, but my poor Oriented-Object skills don't give me the ability to think about them ( :( )...
I want a solution which can be easily modified in the future (if for example, I want to add a new Type "calendar" on the Blog, which reference a DateTime). And which would be easy to use in my application if I want to retrieve all Elements related to a Page...
Thanks for your attention :)
I'm not sure it fits your problem but using GenericForeignKeys/ContentType framework may be appropriate in this case. It's quite powerful when one grasps the concept.
Example construct:
class Page(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
page_element = GenericForeignKey('content_type', 'object_id')
...
You can now connect any model object by the GenericFK to the Page model. So adding a new type (as a new model), at a later stage, is not intrusive.
Update:
As a comment pointed out this construct doesn't support many PageElements in a good way for a Page.
To elaborate, one way to solve that problem, still taking advantage of the GenericFK...
class PageElement(models.Model):
class Meta:
unique_together=('page', 'content_type', 'object_id') # Solve the unique per page
page = models.ForeignKey(Page, related_name='page_elements')
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
A Page can have many "abstract" PageElements and content_object is the "concrete PageElement model/implementation".
Easy to retrieve all elements for a specific page and allows inspection of the ContentType to check the type of element etc.
Just one way of many to solve this particular problem.
To establish the relationship between Page and PageElement in Django you would rather use Foreign Key relationship, than inheritance.
class PageImageElement(PageElement):
page = models.ForeignKey(Page,
on_delete=models.CASCADE,
related_name='images')
image = models.ImageField(null=True)
image_url = models.URLField(null=True)
Every user's post would create an instance of Page. Every addition of image to the Page would create an instance of PageImageElement and you could query for them using the related name. This way would be really easy to access all video, image, text modules of a single Page.
On a related note, I would say that PageElement class could be abstract see the docs and if you declare fields as possibly containing null values as in video = models.FileField(null=True) then it might be worth declaring blank=True as well, otherwise there will be errors when creating the object with these fields undefined. Discussed, for example, here: differentiate null=True, blank=True in django
I can't just write "page.elements" to get all elements of all types
Well actually, you can if you use multi-table inheritance. The problem is that all records returned are instances of PageElement, meaning you lose all information of the subclass type and the additional data these child objects may hold.
There are quite a lot of packages that tackle this polymorphism problem:
django packages: Model inheritance

django-taggit obtain tags string from form

I trying to use django-taggit as a tag model.
model.py
class Product(models.Model):
product_no = models.IntegerField(primary_key=True)
...
tags = TaggableManager(blank=True)
views.py
def action(request):
product = Product()
user = User.objects.get(id=request.user.id)
product.seller_username = user
...
product.save()
tag_list = taggit.utils._parse_tags(request.POST['tags'])
product.tags.add(*tag_list)
When I call method product.tags.add(), I'm getting an error say
Product objects need to have a primary key value before you can access
their tags
Many solutions I find inform me to put product.save() before product.tags.add() to make pk available before access many-to-many field.
I've try it and still the error.
Note: the save() method work properly. It create new object in Product list and can be see in admin interface.
It seem that I have to change
product_no = models.IntegerField(primary_key=True)
to
product_no = models.AutoField(primary_key=True)
and it's fixed.

One-To-Many models and Django Admin

I have the following models:
#models.py
class Section(models.Model):
name = models.CharField(max_length=20)
class Tags(models.Model):
parent = models.ForeignKey(Section)
name = models.CharField(max_length=255, blank=True)
class Article(TimeStampedMode):
...
tag = models.ForeignKey(Tags)
In Django admin, tag shows up as a HTML <select multiple>.
What I'm trying to do is:
A Section could have many Tags, and from Article I could pick a Tags from Section.
Also, it needs to be able to get a Article's Section(via tags.parent?).
Currently this works. But, instead of <select multiple>, Tags shows up as a <input> instead of a <select multiple>.
What I want is for both Tags and Section appear as <select multiple>.
edit:
What I want is:
By using a foreign key to define the relationship, you're limiting the number of Tags an Article may have to 1. For an Article to have more than one Tag, you'll want to use a ManyToMany relationship.
Read more on many-to-many relationships with Django here: https://docs.djangoproject.com/en/dev/topics/db/examples/many_to_many/
The Django admin site will automatically use a select if you're using a foreign key relationship, or a multi-select if you're using a many-to-many relationship.
Here's what a many-to-many will look like from Article to Tags:
class Article(TimeStampedMode):
...
tag = models.ManyToManyField(Tags)
I don't understand your need correctly. But according to your image, you need Section and Tags are set One-To-Many field in Article.
#models.py
class Section(models.Model):
name = models.CharField(max_length=20)
class TagName(models.Model):
tag_name = models.CharField(max_length=255, blank=True)
class Tags(models.Model):
parent = models.ForeignKey(Section)
name = models.ForeignKey(TagName)
class Article(TimeStampedMode):
...
tag = models.ForeignKey(Tags)
I think this method is more useful and matching with your need.
screenshot of the given model code:
this is Article page
go into tag and you can see select multiple for both fields
Thank you

Categories

Resources