How to iterate through Django model items in a ManyToManyField - python

Brand new to Django/Python and thought I'd start by building a simple blog app. I would like the User to be able to post book references that include the book name and a link to the book.
My current References class in my models.py looks like this:
class References(models.Model):
link = models.URLField(max_length=150, default=False)
title = models.CharField(max_length=100, default=False)
def __str__(self):
return self.title
and my Post class looks like this:
class Post(models.Model):
title = models.CharField(max_length=31)
content = models.TextField()
thumbnail = models.ImageField()
displayed_author = models.CharField(max_length=25, default=True)
shortquote = models.TextField()
reference_title = models.ManyToManyField(References)
publish_date = models.DateTimeField(auto_now_add=True)
last_updated = models.DateTimeField(auto_now=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
slug = models.SlugField()
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse("detail", kwargs={
'slug': self.slug
})
def get_love_url(self):
return reverse("love", kwargs={
'slug': self.slug
})
#property
def comments(self):
return self.comment_set.all()
#property
def get_comment_count(self):
return self.comment_set.all().count()
#property
def get_view_count(self):
return self.postview_set.all().count()
#property
def get_love_count(self):
return self.love_set.all().count()
I understand that i'm only returning the title in my References class, I've tried returning self.title + self.linkbut this gives me both the title and link together when being called in the template.
My template calling the references class looks like this:
{% for title in post.reference_title.all %}
<a href="{{ link }}">
<li>{{ title }}</li>
</a>
{% endfor %}
I've tried a combination of different things in order to get the link AND title to render independently as shown in the template, but the issue comes with knowing how to display different items from a class through a ManyToManyField. Any help on this would be great as I do believe it's just something I haven't learnt yet. Thanks in advance!

title is not the title, it is a References object, it is only because you implemented __str__ to return the title, that {{ title }}, will indeed render the .title attribute of that References object.
You thus iterate over it, and access the attribute:
{% for reference in post.reference_title.all %}
<a href="{{ reference.link }}">
<li>{{ reference }}</li>
</a>
{% endfor %}
You thus can replace {{ reference }} with {{ reference.title }} here, although the two are equivalent because the __str__ returns the title.

Related

Django - Can't access data from object in many to many relationship

In my Django project, I am trying to create a website that streams TV shows. Each show belongs in many categories, hence the use of many to many relations in my model. What I want to do with a certain page on my website is dynamically load a page of shows belonging to a specific category. However, all of my attempts have ended in failure as I am unable to figure out a way on how to access the actual category data from each show.
In views.py
def shows_in_category(request, category_slug):
category = get_object_or_404(Category, slug=category_slug)
showsall = theShow.objects.all()
shows = []
for show in showsall:
print(show.category.name, category.name)
if show.category.name == category.name:
shows.append(show)
print(shows)
return render(request, 'show/show_list_view.html', {'category':category, 'shows': shows})
In models.py
class Category(models.Model):
name = models.CharField(max_length=255, db_index=True)
slug = models.SlugField(max_length=255, unique=True)
class Meta:
verbose_name_plural = 'Categories'
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse("theshowapp:shows_in_category", args=[self.slug])
class theShow(models.Model):
english_name = models.CharField(max_length=400)
show_type = models.CharField(max_length=200, blank=True)
is_active = models.BooleanField(default=False)
category = models.ManyToManyField(Category)
slug = models.SlugField(max_length=400,unique=True)
class Meta:
verbose_name_plural = 'Shows Series'
def __str__(self):
return self.english_name
In the template (show_list_view.html)
{% for show in shows %}
<script> console.log("I'm trying to get in")</script>
<script> console.log("{{ show.name }} {{show.category.name}}")</script>
<script> console.log("I'm in")</script>
<div class="row">
<div class="col-lg-4 col-md-6 col-sm-6">
<div class="product__item">
<div class="product__item__text">
<ul>
{% for genre in show.category %}
<li>{{ show.category }}</li>
{% endfor %}
</ul>
<h5>{{ show.english_name }}</h5>
</div>
</div>
</div>
</div>
{% endfor %}
Any insight on this matter would be much appreciated.
What you're doing here violates some of the best practices of Django and also isn't using the Django ORM to its full potential. Please replace the lines
showsall = animeShow.objects.all()
shows = []
for show in showsall:
print(show.category.name, category.name)
if show.category.name == category.name:
shows.append(show)
print(shows)
with
shows = animeShow.objects.filter(category__name=category.name)
Also in the template change <li>{{ show.category }}</li> to <li>{{ genre }}</li> since that's the iterating variable.
I read up a bit more on the many to many fields examples in Django's documentation and figured out that I should use this:
shows = animeShow.objects.all().filter(category__name=category)

How to filter Django BooleanFields and display the results in a template

Brand new to Python/Django and have hit a wall trying to figure out how to only show posts marked as True in certain parts of the same template.
For eg. I have a blog, the User can make posts but I want the posts marked as science_post, using a Django BooleanField, to be displayed separately from the rest of the blog posts.
Here's what I have for my Post model:
class Post(models.Model):
title = models.CharField(max_length=31)
content = models.TextField()
thumbnail = models.ImageField()
picture_description = models.CharField(max_length=100, default=True)
displayed_author = models.CharField(max_length=25, default=True)
shortquote = models.TextField()
reference_title = models.ManyToManyField(References)
publish_date = models.DateTimeField(auto_now_add=True)
last_updated = models.DateTimeField(auto_now=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
slug = models.SlugField()
science_post = models.BooleanField(default=False)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse("detail", kwargs={
'slug': self.slug
})
def get_love_url(self):
return reverse("love", kwargs={
'slug': self.slug
})
#property
def comments(self):
return self.comment_set.all()
#property
def get_comment_count(self):
return self.comment_set.all().count()
#property
def get_view_count(self):
return self.postview_set.all().count()
#property
def get_love_count(self):
return self.love_set.all().count()
My idea was to simply filter out the posts marked science_post in the template using something along the lines of
{% if science_post %}
SHOW SCIENCE POST CONTENT HERE
{% endif %}
...But this returns nothing. Any help or a point in the right direction with this would be great. And if any additional info is needed, please let me know. Thanks.
In order to realize two seperate displays of posts you should use two seperate queries in the backend instead of seperating them using if-statements in the frontend.
Try passing two QuerySets to your template, using two seperate queries where you filter the different kinds of posts:
...
science = Post.objects.filter(science_post=True)
non_science = Post.objects.filter(science_post=False)
...
And then render them seperately in your template:
...
{% for post in science_post %}
SHOW SCIENCE POST CONTENT HERE
{% endfor %}
...
{% for post in non_science %}
SHOW NON-SCIENCE POST CONTENT HERE
{% endfor %}
...

Getting the number of comments from a specific post in django

I have a little problem with a query. I work on a blog website with django. For posts I have the first page where i display all the posts as a list, with their details (title, date posted etc.) and I want to display the number of comments for each post along with title, date posted and tags. I'm not sure how to make that, I need to implement something on the model classes or in view function that renders the page ?
Here are the model classes.
class Post(models.Model):
author = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
title = models.CharField(max_length=500)
content = models.TextField()
tags = models.CharField(max_length=100)
date_posted = models.DateTimeField(default=timezone.now)
def __str__(self):
return self.title
class Comment(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
comment_text = models.TextField()
date_posted = models.DateTimeField(default=timezone.now)
def __str__(self):
return f'{self.user.username} Comment ' + str(self.id)
and the view function
def blog(request):
context = {
'posts': Post.objects.all(),
'title': 'Blog',
'banner_page_title': 'Blog',
'page_location': 'Home / Blog'
}
return render(request, 'blog/blog.html', context)
Method 1:
You can use this in your template.
{% for post in posts %}
{{ post.comment_set.count }}
{% endfor %}
Method 2:
You can implement a model method like this:
class Post(models.Model):
....
def __str__(self):
return self.title
#property
def comment_count(self):
return self.comment_set.count()
And you can call the model method in your template like this:
{% for post in posts %}
{{ post.comment_count }}
{% endfor %}
Use a reverse lookup in the template and count to get the number of related objects:
{{ post.comment_set.count }}
Something like this?
template.html
...
{% for post in posts %}
<div> Title: {{post.title}} </div>
<div> Date Posted: {{post.date_posted}} </div>
<div> Number of Comments: {{post.comment_set.count}} </div>
{% endfor %}

Retrieving metadata from a post, then sorting posts by said metadata in django

I have two interconnected models in my blog app; Category and Post. The blog front page displays a list of posts and their corresponding metadata, like it should; fairly standard stuff.
Aside from displaying the posts on the front page, they're also displayed on the individual user's profile page in short form (just the category and the headline).
What I'm interested in doing is sorting all the posts that belong in a category, however the only way I've managed to make it work is something like this:
NEWS
some title
NEWS
another title
PYTHON
another arbitrary title
NEWS
yet another title
I'd like to sort it thusly instead:
NEWS
some title
another title
yet another title
PYTHON
another arbitrary title
Alas, my mind keeps turning into a bowl of spaghetti when I try to come up with a method, so without further ado; how should I go about this bit?
I reckon that there's something off with calling the category from the post's metadata only to try and categorize the posts via the retrieved data, but aside from that, I'm somewhat lost.
Here's the template snippet from user_profile.html:
{% if user.post_set.exists %}
<p>
{% for post in user.post_set.all|dictsortreversed:"date_posted" %}
<span style="margin-right: 5px; padding: 3px 6px; border-radius:12px; color:#FFF; background-color:#FFA826;">{{ post.category }}</span><br/>
<a style="margin-left:3px;" href="{% url 'blog:post-detail' post.slug %}">{{ post.title|truncatechars_html:30 }}</a><br/>
{% endfor %}
</p>
{% endif %}
The models:
class Category(models.Model):
class Meta:
verbose_name = 'category'
verbose_name_plural = 'categories'
name = models.CharField(max_length=30)
def __str__(self):
return self.name
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=60)
category = models.ForeignKey(Category, blank=True, null=True, on_delete=models.CASCADE)
content = RichTextUploadingField(
external_plugin_resources=[(
'youtube',
'/static/ckeditor/ckeditor/plugins/youtube/',
'plugin.js'
)],
blank=True,
null=True,
)
date_posted = models.DateTimeField(default=timezone.now)
updated = models.DateTimeField(auto_now=True)
slug = models.SlugField(max_length=70, blank=True, null=True, help_text='<font color="red">don\'t. touch. the. slug. field. unless. you. mean. it.</font> (it will auto-generate, don\'t worry.)')
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('blog:post-detail', kwargs={'slug': self.slug})
And finally the view which relate to the post_list.html:
class PostListView(ListView):
model = Post
template_name = 'blog/home.html'
context_object_name = 'posts'
ordering = '-date_posted'
paginate_by = 6
Should I be doing it in a different manner altogether, I wonder? And if so, what would be considered 'best practice'?
Thank you :)
You can add the ordering in your model:
class Post(models.Model):
...
class Meta:
ordering = ['category', '-date_posted']
See the documentation for more details:
update
Maybe its better to use custom manager for this:
from django.db import models
class CustomManager(models.Manager):
# subclass model manager
def custom_category_dict(self, **kwargs):
# add a new method for building a dictionary
nDict = dict()
for i in self.get_queryset().filter(**kwargs): # filter queryset based on keyword argument passed to this method
current_list = nDict.get(i.category.name, [])
current_list.append(i)
nDict.update({i.category.name: current_list})
return nDict
class Posts(models.Model):
# override posts model with manager
objects = CustomManager()
Usage:
# view
class PostListView(ListView):
...
def get_context_data(self, **kwargs):
context = super(PostListView, self).get_context_data(**kwargs)
context['category_wise_sorted_posts'] = Posts.objects.custom_category_dict() # you can pass filter logic as well, like Posts.objects.custom_category_dict(author_id=1)
return context
# template
{% for category, posts in category_wise_sorted_posts.items %}
<!-- Or {% for category, posts in user.posts_set.custom_category_dict.items %} -->
{{ category }}
{% for p in posts %}
{{ p.title }}
{% endfor %}
{% endfor %}

Django: Querying to ImageField all images are being rendered

So my problem is that when I try to query to the image url so it can be posted to its corresponding Post all the images that have been uploaded to the media folder is being rendered, even though in the admin panel it shows that each post has it's own image and they are assigned to different posts, instead all of them are being rendered together for each and every post.
The models that I have are SellPost which is for creating a post and SellPostImage is for assigning the image to the post.
models.py
class SellPost(models.Model):
user = models.ForeignKey(User)
title = models.CharField(max_length=128)
category = models.ForeignKey(Category)
type = models.ForeignKey(SellPostType, default=None)
body = models.CharField(max_length=400)
price = models.DecimalField(decimal_places=1, max_digits=5, default=0.0)
views = models.IntegerField(default=0)
likes = models.IntegerField(default=0)
slug = models.SlugField(unique=True, default='automatic')
def save(self, *args, **kwargs):
self.slug = slugify(self.title)
super(SellPost, self).save(*args, **kwargs)
def __unicode__(self):
return self.title
class SellPostImage(models.Model):
user = models.ForeignKey(User, null=True)
post = models.ForeignKey(SellPost)
pictures = models.ImageField(upload_to='post_images', blank=True)
def __str__(self):
return "{}".format(self.post)
class Meta:
verbose_name_plural = "Post Images"
In the view I tried to create a context dict (because I'm a newbie in Django and have learned that from Tango with Django so I went with it) for the post and then the images:
views.py
def post(request, post_name_slug):
context_dict = {}
try:
post = SellPost.objects.get(slug=post_name_slug)
context_dict['post'] = post
post_image = SellPostImage.objects.all()
context_dict['post_image'] = post_image
except SellPost.DoesNotExist:
pass
return render(request, 'p.html', context_dict)
and here is how I tried to render them in the HTML file.
p.html
<ul>
{% for post in posts %}
<li>{{ post.title }} </li>
{% for post_images in post_image %}
<img style="width:200px; height:200px;" src="{{ post_images.pictures.url }}" />
{% endfor %}
{% endfor %}
</ul>
You'll want to filter the SellPostImage for the retrieved post:
post = SellPost.objects.get(slug=post_name_slug)
context_dict['post'] = post
post_image = SellPostImage.objects.filter(post=post)
context_dict['post_image'] = post_image
But you can just as easily put that logic part directly into your template:
{% for post in posts %}
<li>{{ post.title }} </li>
{% for post_images in post.sellpostimage_set.all %}
<img style="width:200px; height:200px;" src="{{ post_images.pictures.url }}" />
{% endfor %}
{% endfor %}
and then you can remove the SellPostImage in your views:
try:
post = SellPost.objects.get(slug=post_name_slug)
context_dict['post'] = post
except SellPost.DoesNotExist:
pass
In your post method you query for all SellPostImages:
post_image = SellPostImage.objects.all()
That's why you get all images for each post.
You can filter only the images associated with a post by doing the following instead:
post_image = SellPostImage.objects.filter(post=post)
It will provide all images for that specific post.

Categories