How to get comment for the particular Blog Post in Django? - python

#Models.py
#BlogPost Model
class BlogPost(models.Model):
POST_CATEGORY = (
('Education','Education'),
('Tech','Tech'),
('Automobile','Automobile'),
('Other','Other')
)
title = models.CharField(max_length=150)
thumbnail = models.ImageField(upload_to='Blog Thumbnail')
category = models.CharField(max_length=100, choices = POST_CATEGORY )
content = models.TextField()
timestamp = models.DateTimeField(auto_now_add=True)
slug = models.CharField(max_length=200, unique=True, null=True)
tags = models.CharField(max_length=150, null=True, blank=True)
writer = models.ForeignKey(User,on_delete=models.CASCADE,null=False)
def __str__(self):
return self.title
#BlogComment Model
class BlogComment(models.Model):
post = models.ForeignKey(BlogPost,on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
comment = models.TextField()
parent = models.ForeignKey('self',on_delete=models.CASCADE,null=True)
timestamp = models.DateTimeField(auto_now_add=True)
#Views Code
def blogPost(request, slug):
post = BlogPost.objects.filter(slug=slug)
'''How to get comment for particularly this post'''
comments = BlogComment.objects.filter(post=post) # It is giving a wrong answer
'''The error I am getting
ValueError: The QuerySet value for an exact lookup must be limited to one result using slicing.'''
print(comments)
context = {
'Post':post,
'Comments':comments
}
return render(request,'blogpost.html',context)
How to get the comment for the particulary for this blog post? The error I am getting -" ValueError: The QuerySet value for an exact lookup must be limited to one result using slicing."

objects.filter() returns a queryset.
The filter method is expecting an instance of BlogPost (to get the id) or a integer-id since objects.filter(post=pk)
Use objects.get(), that way you get a instance of BlogPost, not a queryset:
post = BlogPost.objects.get(slug=slug)
comments = BlogComment.objects.filter(post=post)
Additions:
You can also handle the exception that could happen if the post does not exist by different ways. One of them is returning Http404, and here is the easiest way to do that:
from django.shortcuts import get_object_or_404
post = BlogPost.objects.get_object_or_404(slug=slug)

Related

Django - Cannot assign "<Product: Test Product>": "Reply.comment" must be a "Comment" instance

I'm getting a Cannot assign "<Product: Test Product>": "Reply.comment" must be a "Comment" instance. error at new_reply = Reply(content=content, author=self.request.user, comment=self.get_object()) and don't know what to do to fix it.
views.py:
class ProductFeedbackView(DetailView):
model = Product
template_name = 'store/product_feedback.html'
def get_context_data(self , **kwargs):
data = super().get_context_data(**kwargs)
connected_comments = Comment.objects.filter(product=self.get_object())
number_of_comments = connected_comments.count()
data['comments'] = connected_comments
data['no_of_comments'] = number_of_comments
data['comment_form'] = CommentForm()
connected_replies = Reply.objects.filter(comment=self.get_object())
number_of_replies = connected_replies.count()
data['replies'] = connected_replies
data['no_of_replies'] = number_of_replies
data['reply_form'] = ReplyForm()
return data
def post(self , request , *args , **kwargs):
if self.request.method == 'POST':
reply_form = ReplyForm(self.request.POST)
if reply_form.is_valid():
content = reply_form.cleaned_data['content']
new_reply = Reply(content=content, author=self.request.user, comment=self.get_object())
new_reply.save()
return redirect(self.request.path_info)
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, product=self.get_object())
new_comment.save()
return redirect(self.request.path_info)
models.py:
class Product(models.Model):
author = models.ForeignKey(User, default=None, on_delete=models.CASCADE)
title = models.CharField(max_length=120, unique=True)
description = models.CharField(max_length=300, blank=True, null=True)
class Comment(models.Model):
product = 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=True, blank=False)
class Reply(models.Model):
comment = models.ForeignKey(Comment, on_delete=models.CASCADE)
author = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True,)
content = models.TextField(null=True, blank=False)
It's the same error as I pointed out in the other question you asked. Here is the line of code that is not working:
new_reply = Reply(content=content, author=self.request.user, comment=self.get_object())
The cause of this is also the same as your other question:
self.get_object() returns a Product instance as you defined model = Product on your View.
To fix this, you'll need to get the comment in your post method before creating the reply.
One solution for this could be posting the comment id with the reply data, for example as a hidden field. This id can then be used to get the comment instance where this reply belongs to. If you choose this solution, also make sure the comment actually belongs to this product as you don't want someone to just change the value of the comment id and be able to reply to any other comments.
Some additional feedback:
You don't need to check for the request method in the post method as it's already the post method.
You're now trying to save both a comment and reply whenever a post is done. You should either create different views for each post or add an if statement to check which form is posted. For example also using a hidden field with a value for that.
The comment model defines a product and an author relation. Both being null=True and blank=True. I think you always want a comment to belong to a product and an author. So you should remove those. Same for reply.
After a successful post, use a HttpResponseRedirect where you reverse the url of the product.

Inserting a 'hidden timestamp field' in a modelform?

I have a simple form that allows a user to create a post. The fields are post_title, post_content, post_date. The post_title and post_content will be provided by the user, however the post_date will be autogenerated by the system and will give the current timestamp. Due to this, I will only show both title and content in the form. When I try and submit a request however, this gives me an IntegrityError saying NOT NULL constraint failed: main_app_post.post_date. I am fairly new at Django so I really have no idea what to do. I want it that whenever the user sends a Post request (and if it passes the validations), it will generate the current timestamp to post_date before saving it to the database. Any advices in regards of this? Thanks a lot!
models.py:
class Post(models.Model):
post_title = models.CharField(max_length=100)
post_content = models.TextField(max_length=400)
post_date = models.DateTimeField()
def __str__(self):
return self.post_title
forms.py:
class PostForm(forms.ModelForm):
class Meta:
model = models.Post
fields = ('post_title', 'post_content')
views.py:
if request.method == 'POST':
form = forms.PostForm(request.POST)
if form.is_valid():
post_title = form.cleaned_data['post_title']
post_content = form.cleaned_data['post_content']
post_date = datetime.datetime.now().timestamp()
form.post_date = post_date
form.save(commit=True)
set auto_now_add=True
class Post(models.Model):
post_title = models.CharField(max_length=100)
post_content = models.TextField(max_length=400)
post_date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.post_title
Refernce: Django auto_now and auto_now_add
You can specify a default and make it non-editable, but in fact Django already has a solution for this: you can specify auto_now_add=True [Django-doc]:
class Post(models.Model):
post_title = models.CharField(max_length=100)
post_content = models.TextField(max_length=400)
post_date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.post_title
This will make the field non-editable, so it will not show up in a form by default, and furthermore it will use as default value the current timestamp. This thus means that the view and form no longer have to worry about this.

Django - Pass two Models into one view, and display both models

I'm trying to pass two models into a create view, where i am trying to get the the primary key from the URL to retrieve the details from a food truck model so it can be displayed in the page, and where a user can write a review about food truck. Also, I'd like a list of the reviews to be displayed on the page.
views.py
class TruckReviewView(CreateView):
model = Review
template_name = 'truckReviews/detail.html'
fields = ['speedOfService', 'qualityAndTaste', 'valueForMoney', 'comment']
def get_queryset(self):
self.pk = self.kwargs['pk']
queryset = super(TruckReviewView, self).get_queryset()
return queryset
def get_context_data(self, **kwargs):
context = super(TruckReviewView, self).get_context_data(**kwargs)
context['truck'] = FoodTrucks.objects.get(truckID=get_queryset())
context['reviews'] = Review.objects.get(truckID=get_queryset())
return context
urls.py
urlpatterns = [
path('', TruckListView.as_view(), name='reviews-home'),
path('truck/<int:pk>/', TruckReviewView.as_view(), name='truck-detail'),
path('about/', About.as_view(), name='reviews-about'),
]
models.py
class FoodTrucks(models.Model):
truckID = models.IntegerField(primary_key=True, unique=True, null=False)
name = models.CharField(max_length=25)
category = models.CharField(max_length=20)
bio = models.TextField()
avatarSRC = models.TextField(default=None)
avatarALT = models.CharField(max_length=20, default=None)
avatarTitle = models.CharField(max_length=20, default=None)
coverPhotoSRC = models.TextField(default=None)
coverPhotoALT = models.CharField(max_length=20, default=None)
coverPhotoTitle = models.CharField(max_length=20, default=None)
website = models.TextField(default=None)
facebook = models.CharField(max_length=100, default=None)
instagram = models.CharField(max_length=30, default=None)
twitter = models.CharField(max_length=15, default=None)
class Review(models.Model):
reviewID = models.AutoField(primary_key=True, unique=True, serialize=False, null=False)
truckID = models.ForeignKey(FoodTrucks, on_delete=models.CASCADE)
userID = models.ForeignKey(User, on_delete=models.CASCADE)
datePosted = models.DateTimeField(default=timezone.now)
speedOfService = models.IntegerField()
qualityAndTaste = models.IntegerField()
valueForMoney = models.IntegerField()
comment = models.TextField(max_length=128)
I've tried to use get_queryset to get the pk from the URL and pass the pk into get_context_data and target the specific truck with that ID in the database.
The difficulty comes from the fact that you combine a list view and create view. If you want to combine this into one view, then you need to do a bit mixing and matching with different mixins of the Class Based Views.
It can be done, but it's not trivial. If you're new to Django, then this may be overshooting things. I've renamed fields an such and did it as an exercise. I haven't bothered with the form submission, it shouldn't be that difficult to implement as the other views don't deal with the methods involved (form_valid, get_success_url, etc). You can use it as a guide to see what you should be learning. The above linked site is extremely convenient to see how things are mixed together.
The result below will provide the variables "foodtruck", "reviews" and "form" to the template.
import typing as t
from django.views import generic
from .models import FoodTruck, Review
from .forms import ReviewForm
if t.TYPE_CHECKING:
from django.http import HttpRequest, HttpResponse
from django.contrib.auth.models import AbstractUser
class AuthenticatedRequest(HttpRequest):
user: AbstractUser = ...
class FoodTruckDetailReviewListCreateView(
generic.list.MultipleObjectMixin, generic.edit.CreateView,
):
template_name = "foodtrucks/detail.html"
model = Review
list_model = Review
context_list_name = "reviews"
context_object_name = "foodtruck"
detail_model = FoodTruck
form_class = ReviewForm
def get(self, request: "AuthenticatedRequest", *args, **kwargs) -> "HttpResponse":
"""
Combine the work of BaseListView and BaseDetailView
Combines the get implementation of BaseListView and BaseDetailView, but
without the response rendering. Then hands over control to CreateView's
method to do the final rendering.
Some functionality is stripped, because we don't need it.
:param request: The incoming request
:return: A response, which can be a redirect
"""
# BaseListView
self.object_list = self.get_queryset()
# BaseDetailView
self.object = self.get_object()
context = self.get_context_data(
object=self.object, object_list=self.object_list
)
# CreateView sets self.object to None, but we override form_kwargs, so
# we can leave it at a value.
return self.render_to_response(context=context)
def get_template_names(self):
# Bypass logic in superclasses that we don't need
return [self.template_name]
def get_object(self, queryset=None):
# We provide the queryset to superclasses with the other model
return super().get_object(queryset=self.detail_model.objects.all())
def get_queryset(self):
# This only gets called by MultipleObjectMixin
pk = self.kwargs.get(self.pk_url_kwarg)
if pk is None:
raise AttributeError(
"Unable to filter on food truck: {} is missing in url.".format(
self.pk_url_kwarg
)
)
queryset = self.list_model.objects.filter(food_truck_id=pk)
# print(str(queryset.query))
return queryset
def get_context_data(self, **kwargs):
if "object" in kwargs:
kwargs[self.context_object_name] = kwargs["object"]
if "object_list" in kwargs:
kwargs[self.context_list_name] = kwargs["object_list"]
return super().get_context_data(**kwargs)
def get_form_kwargs(self):
# Bypass ModelFormMixin, which passes in self.object as instance if it
# is set.
return super(generic.edit.ModelFormMixin, self).get_form_kwargs()
And as a reference, this is what I changed the models to:
import uuid
from django.contrib.auth import get_user_model
from django.db import models
from django.utils import timezone
class FoodTruck(models.Model):
name = models.CharField(max_length=25)
category = models.CharField(max_length=20)
bio = models.TextField()
avatar_url = models.URLField(blank=True)
avatar_alt_text = models.CharField(max_length=20, blank=True)
avatar_title = models.CharField(max_length=20, blank=True)
cover_photo_url = models.URLField(blank=True)
cover_photo_alt_text = models.CharField(max_length=20, default="No photo provided")
cover_photo_title = models.CharField(max_length=20, default="No photo provided")
website = models.URLField(blank=True)
facebook = models.CharField(max_length=100, blank=True)
instagram = models.CharField(max_length=30, blank=True)
# https://9to5mac.com/2017/11/10/twitter-display-name-limit/
twitter = models.CharField(max_length=50, blank=True)
def __str__(self):
return self.name
class Review(models.Model):
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4)
food_truck = models.ForeignKey(
FoodTruck, on_delete=models.CASCADE, related_name="reviews"
)
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
posted_at = models.DateTimeField(default=timezone.now)
speed_of_service = models.IntegerField()
quality_and_taste = models.IntegerField()
value_for_money = models.IntegerField()
comment = models.TextField(max_length=128)
def __str__(self):
return "Review about {} by {}".format(
self.food_truck.name, self.user.get_full_name()
)
And finally the form (with a bit of trickery to inject bootstrap classes):
class ReviewForm(forms.ModelForm):
def __init__(self, **kwargs):
super().__init__(**kwargs)
for field in self.fields.values():
if not field.widget.is_hidden:
field.widget.attrs.setdefault("class", "form-control")
class Meta:
model = Review
exclude = ("uuid", "user", "food_truck", "posted_at")
First, there's not need to create a truckID and reviewID primary key fields because Django creates a unique id field for each object automatically on which you can simply do .get(id=1) or .filter(id=1) etc.
Just like it is completely useless to put ID in fields with Foreign Key or any relational fields because Django will automatically take the name and append _id to it. For instance, just user would become user_id or truck would be truck_id in backend on which you can do .get(user__id=1) or .get(user_id=1) for example.
You should review this section of your code. You're actually not doing anything with the primary key:
def get_queryset(self):
queryset = super().get_queryset()
try:
item = queryset.get(id=self.kwargs['pk'])
except:
...
else:
# Do something with item here
...
finally:
return queryset
or, with get_context_data:
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
queryset = super().get_queryset()
try:
item = queryset.get(id=kwargs['pk'])
except:
...
else:
# Do something with item here
context['item'] = item
finally:
return context
I'm sorry but your question is a bit confusing. If you're trying to get details from a model you should use a DetailView. Also, on a DetailView assuming you want the details of a Review since you have the truck on a review you could simply override the get_context_data and set truck in the context by doing self.object.truck.
If you're trying to create a review then it's right to use the CreateView but that should only be for the Review model.
To list you should use a ListView.
So, to my understanding, you have a truckID and want to create a review for that. In that case, it'd have a CreateView for Review model.
Have a look at the CreateView, DetailView and ListView docs

Django editing a model instance fails to find the instance

I'm in the process of making a Recipe Book. For some reason, whenever I try to pull up a recipe from the DB to edit it, I keep getting an error where it can't find the recipe I've specified. I'm using slugs, and my logic is that I'm going from a detailView where I've already pulled up the db information, to an updateView. I'm attempting to pass the recipe object I already pulled from the detailView to the updateView, but when I do, it keeps telling me that it can't find the recipe specified.
views.py:
The base views I'm calling here are only providing a default post method for handling a search so that I don't have to put it in for every view I create so I have some code reusability
class RecipeDetailView(BaseDetailView):
model = Recipe
template_name = 'RecipeBook/recipe_detail.html'
context_object_name = 'recipe_view'
queryset = None
slug_field = 'slug'
slug_url_kwarg = 'slug'
def get_context_data(self, *args, **kwargs):
context = super(RecipeDetailView, self).get_context_data()
recipe = self.object
recipe.ingredients = recipe.ingredients_list.split('\n')
context['recipe'] = recipe
return context
class RecipeEditView(BaseUpdateView):
model = Recipe
template_name = 'RecipeBook/edit_recipe.html'
context_object_name = 'recipe_edit'
queryset = None
slug_field = 'slug'
slug_url_kwarg = 'slug'
form_class = RecipeForm
def get_context_data(self, *args, **kwargs):
context = super(RecipeEditView, self).get_context_data()
recipe = self.object
print(recipe.name)
recipe.ingredients = recipe.ingredients_list.split('\n')
recipe.categories_list = ""
categories = Category.objects.filter(recipe=recipe)
for category in categories:
if category != categories[-1]:
recipe.categories_list += (category + ", ")
else:
recipe.categories_list += category
recipe_edit_form = RecipeForm(initial={'name': recipe.name, 'ingredients_list': recipe.ingredients,
'directions': recipe.directions, 'prep_time': recipe.prep_time,
'cook_time': recipe.cook_time, 'servings': recipe.servings,
'source': recipe.source, 'category_input': recipe.categories_list})
context['recipe'] = recipe
context['recipe_edit_form'] = recipe_edit_form
return context
models.py:
class Recipe(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100, default="")
ingredients_list = models.TextField(default="")
servings = models.IntegerField(default=0, null=True, blank=True)
prep_time = models.IntegerField(default=0, null=True, blank=True)
cook_time = models.IntegerField(default=0, null=True, blank=True)
directions = models.TextField(default="")
source = models.CharField(max_length=100, default="", null=True, blank=True)
categories = models.ManyToManyField(Category, blank=True)
slug = models.CharField(max_length=200, default="")
def __str__(self):
return self.name
urls.py
# ex: /Recipes/Grilled_Chicken/
path('Recipes/<slug>/', views.RecipeDetailView.as_view(), name='view_recipe'),
path('Recipes/<path:slug>/', views.RecipeDetailView.as_view(), name='view_recipe'),
# ex: /Recipes/edit/Steak/
path('Recipes/edit/<slug>/', views.RecipeEditView.as_view(), name='edit_recipe'),
path('Recipes/edit/<path:slug>/', views.RecipeEditView.as_view(), name='edit_recipe'),
link in recipe_detail.html:
Edit Recipe
I've been going nuts trying to figure it out. By everything that I have in here, the recipe that I'm pulling up in the detailView should be able to be passed to the editView, but every time I try to open up the edit_recipe page, it keeps telling me that it can't find the recipe specified. The URL that it generates shows the proper slug and link that it should though. I don't know what I'm missing at this point...
Try this way:
Edit Recipe
I ended up having to go back through and change the view to be a DetailView. That was the only way I could get the recipe instance to be pushed through. There's something very specific about using an update view with models that isn't very clear...
Once I changed to a DetailView, the page would populate with the form initialized to the recipe values. I could then make tweaks to make sure everything worked from there.
Thanks for those who responded, it at least got my brain working in a different direction to get this figured out.

keep getting integrityError when trying to implement django-multiupload

I keep getting the following integrityError when trying to implement the django-multiupload app:
null value in column "post_id" violates not-null constraint
DETAIL: Failing row contains (9, post_images/1899987_718836594813781_835045800_n.jpg, , null, null, null).
I am trying to allow my users to upload multiple pictures to a post. I think where I am going wrong is that it may be saving before being able to get the post id. In my views I have changed the form_valid which may also be effecting it.
Ideally I would like to pass the picture_author and project instances to each image as well, but to get started I have put them = null to figure out how to pass just the post instance at the moment.
Im new to coding & django, so any help in trying to solve it would be much appreciated!
models:
class ProjectPost(models.Model):
project = models.ForeignKey(UserProject)
title = models.CharField(max_length=100)
post_overview = models.CharField(max_length=1000)
date_created = models.DateTimeField(auto_now_add=True)
post_views = models.IntegerField(default=0)
post_likes = models.IntegerField(default=0)
post_author = models.ForeignKey(User, null=True)
class PostImages(models.Model):
post_picture = models.FileField(upload_to='post_images', blank=True)
post = models.ForeignKey(ProjectPost)
picture_description = models.CharField(max_length=100)
picture_author = models.ForeignKey(User, null=True)
project = models.ForeignKey(UserProject, null=True)
forms
class ProjectPostForm(forms.ModelForm):
class Meta:
model = ProjectPost
fields = ('title', 'post_overview')
files = MultiFileField(min_num=1, max_num=20, max_file_size=1024*1024*5)
def save(self, commit=True):
instance = super(ProjectPostForm, self).save(commit)
for each in self.cleaned_data['files']:
PostImages.objects.create(post_picture=each, post=instance)
return instance
views
class NewPost(CreateView):
model = ProjectPost
form_class = ProjectPostForm
template_name = 'howdidu/new_post.html'
def form_valid(self, form):
self.object = form.save(commit=False)
project = UserProject.objects.get(slug=self.kwargs["slug"])
self.object.project = project
form.instance.post_author = self.request.user
self.object.save()
return super(NewPost, self).form_valid(form)
def get_success_url(self):
project_username = self.request.user.username
project_slug = self.object.project.slug
return reverse('user_project', kwargs={'username':project_username, 'slug': project_slug})
You have to save the post before you can add images to it. You need to explicitly tell the get_or_create what the post_id is supposed to be before an image will be saved, and I dont think you're doing that in your code.

Categories