prefetch_related in Django displaying all objects instad of related ones - python

I am trying to get a list of all categories and below each category the list of all related articles.
The problem is that I am getting the list of all articles under each category. I read documentation and few answers but nothing seems to be working and I am not sure where I am making the mistake.
models.py
class Category(models.Model):
title = models.CharField(max_length=75, default='', blank=False, null=False)
body = CharField(max_length=2000, default='', null=True, blank=True)
slug = models.SlugField(unique=True, blank=True)
publish = models.DateTimeField('publish', default=timezone.now)
class Meta:
ordering = ('-publish',)
verbose_name = 'category'
verbose_name_plural = 'categories'
get_latest_by = 'publish'
class Article(models.Model):
id = models.AutoField(primary_key=True)
author = models.ForeignKey(User, blank=True, null=True, on_delete=models.SET_NULL) #settings INSTALLED_APPS
title = models.CharField('title', max_length=200)
body = CKEditor5Field('Body', config_name='extends')
last_updated = models.DateTimeField(auto_now=True)
publish = models.DateTimeField('publish', default=timezone.now)
#tags = TagField(required=False, widget=LabelWidget)
category = models.ForeignKey(Category, on_delete = models.CASCADE, blank=True, related_name='category')
slug = models.SlugField(unique=True, blank=True)
views.py
#login_required
def hal_home(request):
top_categories = Category.objects.all()[:3]
category_related_articles = Article.objects.prefetch_related('category').all()
context = {
'category_related_articles': category_related_articles
}
return render(request, 'hal/homepage.html', context)
homepage.html
{% for category in top_categories %}
<div class="btn btn-light text-center text-justify">
<div>{{ category.title }}</div>
</div>
<br>
<p>{% for article in category_related_articles %}
{{article.title}}<br>
{% endfor %}</p>
{% empty %}
<div>No categories</div>
{% endfor %}

You should use prefetch_related with your category queryset.
And also I would suggest you to change the related_name to articles since category.articles.all() seems more readable and easier than category.category.all() .
category = models.ForeignKey(Category, on_delete = models.CASCADE, blank=True, related_name='articles')
Now you can change some code in your views and templates.
top_categories = Category.objects.prefetch_related("articles").all()[:3]
context = {'top_categories': top_categories}
Now you can get articles by category in template with:
{% for category in top_categories %}
{{category.title}}
# articles of each category
{% for article in category.articles.all %}
{{article}}
{% endfor %}
{% endfor %}

Related

Django - How to add a comment limit of 1

I currently have a comment functionality and I want to add a limit of one comment for each user but don't know how to do that.
I tought on making a 'posted' field in the user model which would be true when the user posted a comment but I don't know how to do that and most importantly if that is the better way of doing that...
class Comment(models.Model):
service = models.ForeignKey(Product, on_delete=models.CASCADE, blank=True, null=True, related_name='comments')
author = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True,)
content = models.CharField(max_length=200, null=False, blank=True)
date = models.DateTimeField(auto_now_add=True)
def get_absolute_url(self):
return reverse('product-feedback', kwargs={'pk': self.pk})
class ProductFeedbackView(DetailView):
model = Product
template_name = 'services/product-feedback.html'
def get_context_data(self , **kwargs):
data = super().get_context_data(**kwargs)
connected_comments = Comment.objects.filter(service=self.get_object())
number_of_comments = connected_comments.count()
data['comments'] = connected_comments
data['no_of_comments'] = number_of_comments
data['comment_form'] = CommentForm()
return data
def post(self , request , *args , **kwargs):
if self.request.method == 'POST':
comment_form = CommentForm(self.request.POST)
if comment_form.is_valid():
content = comment_form.cleaned_data['content']
new_comment = Comment(content=content, author=self.request.user , service=self.get_object())
new_comment.save()
return redirect(self.request.path_info)
{% block content %}
{% if user.is_authenticated %}
<form action="" method="POST" id="main_form" class="comment_form">
<div>
<label for="comment">Type Comment here</label>
{{ comment_form.content }} {% csrf_token %} <input type="submit" value="Post"></div>
</div>
</form>
{% else %}
<h2>You need to Login to comment</h2>
{% endif %}
{% for comment in comments %}
<h3> <b>{{ comment.author }} : </b> {{ comment.content }}</h3>
{% endfor %}
{% endblock content %}
If you are really sure a user should post one and only one comment, you should use a OneToOneField. This way, the unique constraint shall be be automatically generated and the object easier to manage.
class Comment(models.Model):
service = models.ForeignKey(Product, on_delete=models.CASCADE, blank=True, null=True, related_name='comments')
author = models.OneToOneField(User, on_delete=models.CASCADE, blank=True, null=True,)
content = models.CharField(max_length=200, null=False, blank=True)
If you want the user to post only one comment per product but still allow him to write comments on as many products as he wants, you should add a constraint in the Meta class :
class Comment(models.Model):
service = models.ForeignKey(Product, on_delete=models.CASCADE, blank=True, null=True, related_name='comments')
author = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True,)
content = models.CharField(max_length=200, null=False, blank=True)
class Meta:
constraints = [
models.UniqueConstraint(fields=["service", "author"], name="One comment per user per product")
]

problem with displaying ManyToMany Field in Dango templates

I have an app for a shopping list, and I want to display values from my ingredients ManyToManyField but what I am getting instead is the name of the recipe that I created.
Could you please advise me on how to correctly do it?
models.py
class Ingredients(models.Model):
name=models.CharField(max_length=100, default='-', blank=True, unique=True)
class Meta:
ordering = ('name',)
verbose_name = 'składnik'
verbose_name_plural = 'składniki'
def __str__(self):
return str(self.name)
class Category(models.Model):
name=models.CharField(max_length=250, default='-', blank=True, unique=True)
slug = models.SlugField(unique=True, blank=True)
class Meta:
ordering = ('name',)
verbose_name = 'kategoria'
verbose_name_plural = 'kategorie'
def get_absolute_url(self):
return reverse("kategoria", kwargs={"slug": self.slug})
def __str__(self):
return str(self.name)
class Recipe(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE, default='-')
name = models.CharField(max_length=400, blank=False, unique=False)
body = models.TextField(blank=True, default='')
ingredients = models.ManyToManyField(Ingredients)
category = models.ForeignKey(Category, on_delete=models.CASCADE, blank=True)
date_created = models.DateTimeField('Utworzono', default=timezone.now)
last_updated = models.DateTimeField('Ostatnia zmiana',auto_now=True)
when_to_eat = models.DateField('Kalendarz', default=timezone.now)
tags = TaggableManager()
slug = models.SlugField(unique=True, blank=True)
class Meta:
ordering = ('name',)
verbose_name = 'przepis'
verbose_name_plural = 'przepisy'
def get_absolute_url(self):
return reverse("przepis", kwargs={"slug": self.slug})
def __str__(self):
return str(self.name)
views.py
class RecipeListView(ListView):
model = Recipe
template_name ='recipe_list.html'
queryset = Recipe.objects.all()
urls.py
urlpatterns = [
path('przepisy/', views.RecipeListView.as_view(), name='recipe_list'),
path('przepis/<slug:slug>/', views.RecipeDetailView.as_view(), name='przepis'),
]
recipe_list.html
<p class="card-text">Składniki:<br>
{% for ingredient in object_list %}
<li>{{ingredient.name}}</li>
{% endfor %}
</p>
you should try something like this:
{% for ingredients in object_list %}
{% for ingredient in ingredients.name.all %}
<li>{{ingredient.name}}</li>
{% endfor %}
{% endfor %}
Try this:
<p class="card-text">Składniki:<br>
{% for recipe in object_list %}
{% for ingredient in recipe.ingredients.all %}
<li>{{ingredient.name}}</li>
{% endfor %}
{% endfor %}
</p>
As you want to get all name from your Ingredients model but you are using {{ingredient.name}} which will show you all name from your Recipe Model. You need to be write {{ingredient.ingredients.name}} So the code will be looke like this
<p class="card-text">Składniki:<br>
{% for ingredient in object_list %}
<li>{{ingredient.ingredients.name}}</li>
{% endfor %}
</p>
As you are using Recipe model in your ListView so when you are writing {{ingredient.name}} it's similar to like this Recipe Model > name when we are writing {{ingredient.ingredients.name}} it's similar to Recipe Model > ManyToManyField of ingredients > garbing name from Ingredients model

Django: ValidationError ['ManagementForm data is missing or has been tampered with']

Django: ValidationError ['ManagementForm data is missing or has been tampered with'] i have been getting this error when i use the forminline factory module, im sorry if my question isnt placed properly, this is my first time here.
my form template is this:
{% extends 'accounts/main-form.html'%}
{% load static %}
{% block title %}
<title>CRM | Form</title>
{% endblock %}
{% block link %}
<link rel="stylesheet" type="text/css" href="{% static 'css/style1.css'%}">
{% endblock %}
{% block content %}
<div class="container">
<form action="" method="POST">
{{formset.management_form}}
{% for i in formset %}
{{i}}
{% endfor %}
<input type="submit" class="btn" value="submit">
</form>
</div>
{% endblock%}
this is the form code in views.py
def order_form(request, pk):
customers = Customer.objects.get(id=pk)
OrderFormSet = inlineformset_factory(Customer, Order, fields=('product', 'status',))
formset = OrderFormSet(request.POST)
if request.method == 'POST':
formset = OrderFormSet(request.POST, instance=customers)
if formset.is_valid():
formset.save()
return redirect('/')
context = {'formset': formset}
return render(request, 'accounts/order_form.html', context)
my models
from django.db import models
# Create your models here.
class Customer(models.Model):
name = models.CharField(max_length=200, null=True)
phone = models.CharField(max_length=200, null=True)
email = models.CharField(max_length=200, null=True)
date_created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
class Tag(models.Model):
name = models.CharField(max_length=200, null=True)
def __str__(self):
return self.name
class Product(models.Model):
CATEGORY = (
('Indoor', 'Indoor'),
('OutDoor', 'Outdoor')
)
name = models.CharField(max_length=200, null=True)
price = models.FloatField(null=True)
category = models.CharField(max_length=200, null=True, choices=CATEGORY)
description = models.CharField(max_length=200, null=True)
date_created = models.DateTimeField(auto_now_add=True)
tag = models.ManyToManyField(Tag)
def __str__(self):
return self.name
class Order(models.Model):
STATUS = (
('Pending', 'Pending'),
('Out for delivery', 'Out for delivery'),
('Delivered', 'Delivered')
)
customer = models.ForeignKey(Customer, null=True, on_delete= models.DO_NOTHING)
product = models.ForeignKey(Product, null=True, on_delete= models.DO_NOTHING)
date_created = models.DateTimeField(auto_now_add=True)
status = models.CharField(max_length=200, null=True, choices=STATUS)
def __str__(self):
return self.product.name
my form.py
from django.forms import ModelForm
from .models import Order
class Order_form(ModelForm):
class Meta:
model = Order
fields = ['product', 'status']

Django filter a queryset based on the template name

In my Django App, I have 2 models. One called Post and one called Categories. When a user clicks on a category, I want only the posts that are in that category to appear in the category detail view. For example if a user clicks on the medical category, I only want the posts in the medical category to appear.
Models:
class Category(models.Model):
title = models.CharField(max_length=200)
colorcode = models.CharField(max_length=20, blank=True, null=True)
description = models.TextField()
image = models.ImageField(blank=True, null=True)
slug = models.SlugField(unique=True)
class Post(models.Model):
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
title = models.CharField(max_length=200)
text = models.TextField()
sub_description = models.TextField(blank=True, null=True)
created_date = models.DateTimeField(default=timezone.now)
published_date = models.DateTimeField(blank=True, null=True)
category = models.ForeignKey(Category, on_delete=models.CASCADE, blank=True, null=True)
image = models.ImageField(blank=True, null=True)
live = models.BooleanField(default=False)
slug = models.SlugField(unique=True)
Views:
class CategoryDetailView(DetailView):
model = Category
def get_context_data(self, **kwargs):
context = super(CategoryDetailView, self).get_context_data(**kwargs)
context['category_posts'] = Post.objects.filter(live=True)
return context
Template:
{% for post in category_posts %}
<div class="post">
<div class="post-title">
{{ post.title }}
</div>
<div class="post-author">
{{ post.author }}
</div>
</div>
{% endfor %}
In a DetailView, you have access to the actual object being rendered (in your case the Category instance) through self.object.
So in your get_context_data method you can do:
context['category_posts'] = Post.objects.filter(live=True, category=self.object)
Note that self.object might be None so you may want to deal with that case.

Finding the Cross-Section of two List views in one Django template

I have two models that feed one view.
models.py
class Item(models.Model):
item_name = models.CharField(max_length=100)
item_type = models.ForeignKey(Item_type, on_delete=models.SET_NULL, null=True)
owned_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)****
added_at = models.DateTimeField('date item added')
updated_at = models.DateTimeField('last update')
def __str__(self):
return self.item_name
class Item_status(models.Model):
item = models.ForeignKey(Item, on_delete=models.SET_NULL, null=True)
borrower = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
loaned_at = models.DateTimeField(default=None, blank=True, null=True)
due_back = models.DateTimeField(default=None, blank=True, null=True)
def __time__(self):
return self.loaned_at
def itemname(self):
return (self.item.item_name)
I have the following view
views.py
class LoanedItemsByUserListView(LoginRequiredMixin,generic.ListView):
model = Item_status
template_name ='catalog/item_status_list_borrowed_user.html'
paginate_by = 10
def get_queryset(self):
return Item_status.objects.filter(borrower=self.request.user).order_by('due_back')
def get_context_data(self, **kwargs):
context = super(LoanedItemsByUserListView, self).get_context_data(**kwargs)
context['Owned_list'] = Item.objects.filter(owned_by=self.request.user, item_type = 1)
context['Loaned_list'] = Item_status.objects.exclude(borrower=self.request.user).exclude(borrower__isnull=True)
return context
I would like to find the cross section of the 'Owned_list' and the 'Loaned_list' in a single template
Something like
<h2>Loaned Books</h2>
{% if Owned_list %}
<ul>
{% for thing in Owned_list.item_name and in Loned_list.item.item_name %}
<li>
{{thing}}
</li>
{% endfor %}
</ul
{% else %}
<p>There are no books in the library.</p>
{% endif %}
I have take a look at the django documentation here https://docs.djangoproject.com/en/1.11/topics/class-based-views/generic-display/, and around SO but not found exactly what I am looking for.
Thanks!

Categories