show comments to unique to specific blog post - python

I want to show comments unique to the blog post on each blog post but when I click submit on the comment posting form it throws an error saying
IntegrityError at /post/6/comment/
NOT NULL constraint failed: blog_postcomment.Post_id
I add comments in the database manually but it doesn't show on my page.
HTML
{% block content %}
<div class="container">
<div class="row">
<div class="col-md-8 card mb-4 mt-3 left top">
<div class="card-body">
{% if comment_list %}
<h2 class="post-title">Comments</h2>
{% for comment in blog_postcomment.post %}
<p class=" text-muted">{{ comment.author }} | {{ comment.post_date }}</p>
<p class="card-text ">{{ comment.description | safe }}</p>
{% endfor %}
{% endif %}
</div>
</div>
</div>
</div>
{% block content %}
model
class PostAuthor(models.Model):
user = models.OneToOneField(User, on_delete=models.SET_NULL, null=True)
bio = models.TextField(max_length=400, help_text="Enter your bio details here.")
class Meta:
ordering = ["user", "bio"]
def get_absolute_url(self):
return reverse('post-by-author', args=[str(self.id)])
def __str__(self):
return self.user.username
class Post(models.Model):
title = models.CharField(max_length=200, unique=False)
slug = models.SlugField(max_length=200, null=True, blank=True)
author = models.ForeignKey(PostAuthor, on_delete=models.CASCADE, null=True, blank=True)
updated_on = models.DateTimeField(auto_now=True)
content = models.TextField()
created_on = models.DateTimeField(auto_now_add=True)
status = models.IntegerField(choices=STATUS, default=0)
class Meta:
ordering = ['-created_on']
def get_absolute_url(self):
return reverse('post-detail', args=[str(self.id)])
def __str__(self):
return self.title
class PostComment(models.Model):
description = models.TextField(max_length=1000, help_text="Enter your comment here.")
author = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
post_date = models.DateTimeField(auto_now_add=True)
Post = models.ForeignKey(Post, on_delete=models.CASCADE)
class Meta:
ordering = ["post_date"]
def __str__(self):
len_title = 75
if len(self.description) > len_title:
titlestring = self.description[:len_title] + '...'
else:
titlestring = self.description
return titlestring
View
class PostListbyAuthorView(generic.ListView):
model = Post
template_name = 'blog/post_list_by_author.html'
def get_queryset(self):
id = self.kwargs['pk']
target_author = get_object_or_404(PostAuthor, pk=id)
return Post.objects.filter(author=target_author)
def get_context_data(self, **kwargs):
context = super(PostListbyAuthorView, self).get_context_data(**kwargs)
context['post'] = get_object_or_404(PostAuthor, pk=self.kwargs['pk'])
return context
class IndexPage(generic.ListView):
queryset = Post.objects.filter(status=1).order_by('-created_on')
template_name = 'blog/index.html'
class PostList(generic.ListView):
queryset = Post.objects.filter(status=1).order_by('-created_on')
template_name = 'blog/all_posts.html'
class PostDetail(generic.DetailView):
model = Post
template_name = 'blog/post_detail.html'
class PostCreate(CreateView):
model = Post
fields = '__all__'
template_name = 'blog/post_form.html'
class PostUpdate(UpdateView):
model = Post
fields = ['content', 'title']
template_name = 'blog/edit_post.html'
class PostComment(LoginRequiredMixin, CreateView):
model = PostComment
fields = ['description', ]
template_name = 'blog/comment_form.html'
def get_context_data(self, **kwargs):
context = super(PostComment, self).get_context_data(**kwargs)
context['post'] = get_object_or_404(Post, pk=self.kwargs['pk'])
return context
def form_valid(self, form):
form.instance.author = self.request.user
form.instance.post = get_object_or_404(Post, pk=self.kwargs['pk'])
return super(PostComment, self).form_valid(form)
def get_success_url(self):
return reverse('post-detail', kwargs={'pk': self.kwargs['pk'], })
urlpatterns = [
path('', views.IndexPage.as_view(), name='index'),
path('posts/', views.PostList.as_view(), name='all-posts'),
path('post/detail/<int:pk>', views.PostListbyAuthorView.as_view(), name='post-by-author'),
path('post/author/<int:pk>', views.PostDetail.as_view(), name='post-detail'),
path('post/create/', views.PostCreate.as_view(), name='post-create'),
path('post/<int:pk>/edit/', views.PostUpdate.as_view(), name='post-edit'),
path('post/<int:pk>/comment/', views.PostComment.as_view(), name='post-comment'),
path('accounts/', include('django.contrib.auth.urls')),
]

first problem:
different case of first letter in post
here: Post = models.ForeignKey(...)
and here: form.instance.post = get_object_or_404(...)

Related

Django 'x' object has no attribute 'get_form'

I have a generic DetailView and I'm trying to do a form for the comment of an user after I display the details of the model but I keep getting the error 'ProductFeedbackView' object has no attribute 'get_form'.
I don't know if the templates have any problem because the error is in the view when I try to get the form into a variable.
Here is comment's model:
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)
...
def get_absolute_url(self):
return reverse('product-feedback', kwargs={'pk': self.pk})
Comment's form:
class CommentForm(forms.ModelForm):
content = forms.CharField()
class Meta:
model = Comment
fields = ['content']
View:
class ProductFeedbackView(DetailView):
model = Product
template_name = 'services/product-feedback.html'
form_class = CommentForm
def get_success_url(self):
return reverse('product-feedback', kwargs={'pk': self.object.id})
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = CommentForm(initial={'content': self.object})
return context
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
form.instance.author = self.request.user
form.save()
return super().form_valid(form)
urls's:
...
path('feedback/<int:pk>/', ProductFeedbackView.as_view(), name='product-feedback'),
Template:
Details
Feedback
<p>{{ product.author }}</p>
<h1>{{ product.title }}</h1>
<p>{{ product.description }}</p>
{% if user.is_authenticated %}
<form method="POST">
<label for="comment">Type comment</label>
{{ form.as_p }} {% csrf_token %} <input type="submit" value="Post">
</form>
{% else %}
...
{% endif %}
{% for comment in comment.service.all %}
<p>{{ comment.author }}</p>
<p>{{ comment.content }}</p>
{% endfor %}
Product model:
class Product(models.Model):
author = models.ForeignKey(User, default=None, on_delete=models.CASCADE)
title = models.CharField(max_length=120, unique=True)
category = models.ForeignKey(Category, default=None, on_delete=models.PROTECT)
description = models.CharField(max_length=300, blank=True, null=True)
...
views = models.IntegerField(default=0)
featured = models.BooleanField(default=False)
date_posted = models.DateTimeField(default=timezone.now)
def get_absolute_url(self):
return reverse('product-detail', kwargs={'pk': self.pk})
You need to inherit the form view mixing like so:
from django.views.generic.edit import FormMixin
class ProductFeedbackView(DetailView, FormMixin):
...
As per django classy class based views guide:
https://ccbv.co.uk/projects/Django/4.1/django.views.generic.edit/FormView/
However, mixing a detail view and an edit view probably doesn't match the usual djagno-esque methodology.
Youre likely better off having a detail view and an edit view (FormView) respectively and using the built in behaviour of django.
Just add an edit button to your detail page which is a reverse to the form view :)
If you need more help, just ping a comment.

Back button to a related object

I'm building an app where I can add recipes and add ingredients to those recipes. On view recipe_details I have a button to add_new_ingredient. When I'm on new_ingredient_form I want to have back button to get back to the details of recipe. I'm trying to pass recipe's pk but it doesn't work. How am I able to pass recipe's pk to be able to back to previous view?
models.py
class Recipe(Timestamp):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
title = models.CharField(max_length=100, unique=True)
preparation = models.TextField()
def __str__(self):
return self.title
class Ingredient(Timestamp):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
amount = models.PositiveSmallIntegerField(blank=True, null=True)
unit = models.ForeignKey('Unit', on_delete=models.SET_NULL, blank=True, null=True)
def __str__(self):
return self.name
views.py
class RecipeView(generic.DetailView):
model = Recipe
context_object_name = 'recipe'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['ingredients_list'] = Ingredient.objects.filter(recipe=self.object.pk)
return context
class AddIngredientView(generic.edit.CreateView):
model = Ingredient
fields = [
'name',
'amount',
'unit'
]
success_url = '/'
template_name = 'recipes/add_ingredient.html'
def dispatch(self, request, *args, **kwargs):
self.recipe = get_object_or_404(Recipe, pk=self.kwargs['pk'])
return super().dispatch(request, *args, **kwargs)
def form_valid(self, form):
form.instance.recipe = self.recipe
return super().form_valid(form)
def get_success_url(self):
if 'add_another' in self.request.POST:
url = reverse_lazy('recipes:add_ingredient', kwargs={'pk': self.object.recipe_id})
else:
url = reverse_lazy('recipes:recipe', kwargs={'pk': self.object.recipe_id})
return url
add_ingredient.html
{% extends "base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<form method="POST">
{% csrf_token %}
{{ form|crispy }}
<button class="btn btn-success" type="submit">Save</button>
<button class="btn btn-success" type="submit" name="add_another">Save and add another</button>
Back
</form>
{% endblock %}
You can go to the previous page by adding {{request.META.HTTP_REFERER}} to the href property of the button.
Django request to find previous referrer

How I can upload file in django?

I'm trying to upload file in a specific folder and store its path in DB through forms(CBV), but it doesn't work!!
this is my code (forms, models, views, template).
I'm selecting the file through forms then I submit the form, but it doesn't submit.
#views.py
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
# fields = ['title', 'content']
success_url = reverse_lazy('blog/new_post.html')
template_name = 'blog/new_post.html'
form_class = PostCreateForm
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST, request.FILES)
if form.is_valid():
form.save()
return redirect(self.success_url)
else:
return render(request, self.template_name, {'form': form})
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
#model.py
class Post(models.Model):
title = models.CharField(max_length=1000)
content = models.TextField()
xml_file = models.FileField(null=True, upload_to='xml_files')
rate = models.FloatField(null=True, blank=True, default=None)
post_date = models.DateTimeField(default=timezone.now)
post_update = models.DateTimeField(auto_now=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.title
def get_absolute_url(self):
# return '/detail/{}'.format(self.pk)
return reverse('detail', args=[self.pk])
class Meta:
ordering = ('-post_date', )
#forms.py
class PostCreateForm(forms.ModelForm):
title = forms.CharField( max_length=1000)
content = forms.CharField(widget=forms.Textarea(
attrs={'rows': 15, 'placeholder': 'write here'}
), max_length=50000)
xml_file = forms.FileField(label='upload file')
class Meta:
model = Post
fields = ['title', 'content', 'xml_file', ]
#new_post.html
{% block content %}
{% load crispy_forms_tags %}
<div class="border p-4 mb-5">
<legend class="border-bottom pb-1 mb-3">new post</legend>
<form method="POST">
{% csrf_token %}
{{form|crispy}}
<input class="btn btn-secondary mt-4" type="submit" value="post">
</form>
</div>
{% endblock content %}
When you are uploading media like what you've got, it is necessary to add the following part to your <form>:
enctype=multipart/form-data

'Category' object has no attribute 'post_set'

so I am trying to add a category system for posts by following a tutorial on this website https://djangopy.org/how-to/how-to-implement-categories-in-django/ (I changed my code up a little)
Everything works like creating categories, adding a post, viewing a post, but if I try to go to the category page to view posts only in that category so /category/CATNAME but it shows me this error
'Category' object has no attribute 'post_set'
models.py
from django.db import models
from django.contrib.auth.models import User
from django.utils.text import slugify
from markdownx.models import MarkdownxField
from markdownx.utils import markdownify
from taggit.managers import TaggableManager
class Category(models.Model):
name = models.CharField(max_length=100)
short_desc = models.CharField(max_length=160)
slug = models.SlugField()
parent = models.ForeignKey('self', blank=True, null=True, related_name='children', on_delete=models.CASCADE)
class Meta:
unique_together = ('slug', 'parent',)
verbose_name_plural = "Categories"
def __str__(self):
full_path = [self.name]
k = self.parent
while k is not None:
full_path.append(k.name)
k = k.parent
return ' -> '.join(full_path[::-1])
def save(self, *args, **kwargs):
value = self.title
self.slug = slugify(value, allow_unicode=True)
super().save(*args, **kwargs)
class Thread(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=150)
content = MarkdownxField()
tags = TaggableManager()
slug = models.SlugField(unique=True)
category = models.ForeignKey('Category', null=True, blank=True, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
class Meta:
verbose_name_plural = 'Threads'
def get_cat_list(self):
k = self.category
breadcrumb = ["dummy"]
while k is not None:
breadcrumb.append(k.slug)
k = k.parent
for i in range(len(breadcrumb)-1):
breadcrumb[i] = '/'.join(breadcrumb[-1:i-1:-1])
return breadcrumb[-1:0:-1]
def save(self, *args, **kwargs):
value = self.title
self.slug = slugify(value, allow_unicode=True)
super().save(*args, **kwargs)
views.py
from django.shortcuts import render, redirect, get_object_or_404
from .models import Category, Thread
from .forms import NewThreadForm
def show_thread_view(request, hierarchy=None):
category_slug = hierarchy.split('/')
category_queryset = list(Category.objects.all())
all_slugs = [ x.slug for x in category_queryset ]
for slug in category_slug:
if slug in all_slugs:
# parent = get_object_or_404(Category, slug=slug, parent=parent)
parent = Category.objects.filter(slug__in=category_slug, parent=None).first()
thread = get_object_or_404(Thread, slug=slug)
instance = get_object_or_404(Thread, slug=slug)
breadcrumbs_link = instance.get_cat_list()
category_name = [' '.join(i.split('/')[-1].split('-')) for i in breadcrumbs_link]
breadcrumbs = zip(breadcrumbs_link, category_name)
context = {
'thread': thread,
'instance': instance,
'breadcrumbs': breadcrumbs,
}
return render(request, "forums/threads/thread_detail.html", context)
def show_category_view(request, hierarchy=None):
category_slug = hierarchy.split('/')
category_queryset = list(Category.objects.all())
all_slugs = [ x.slug for x in category_queryset ]
parent = None
for slug in category_slug:
if slug in all_slugs:
#parent = get_object_or_404(Category, slug=slug, parent=parent)
parent = Category.objects.filter(slug__in=category_slug, parent=None).first()
context = {
'category': parent,
'post_set': parent.post_set.all(),
'sub_categories': parent.children.all(),
}
return render(request, "forums/categories.html", context)
def new_thread_form_view(request):
if request.method == "POST":
form_data = request.POST or None
form = NewThreadForm(form_data)
if form.is_valid():
news = form.save(commit=False)
news.author = request.user
news.save()
return redirect('/forums')
else:
form = NewThreadForm()
context = {
'form': form
}
return render(request, "forums/threads/thread_form.html", context)
categories.html
{% extends 'base.html' %}
{% load static %}
{% block content %}
<br>
{% if sub_categories %}
<h3>Sub Categories</h3>
{% for i in sub_categories %}
{{ i.name }}
{% endfor %}
{% endif %}
<div class="row small-up-1 medium-up-3" >
{% if post_set %}
{% for i in post_set %}
<div class="columns">
<div class=" card-article-hover card">
<a href="{{ i.slug }}">
<img src="{{ i.cover_photo.url }}">
</a>
<div class="card-section">
<a href="{{ i.slug }}">
<h6 class="article-title">{{ i.title | truncatechars:30}}</h6>
</a>
</div>
<div class="card-divider flex-container align-middle">
{{ i.user.get_full_name }}
</div>
<div class="hover-border">
</div>
</div>
</div>
{% endfor %}
{% endif %}
</div>
{% endblock %}
The model which has ForeignKey relation to Category model is Thread(as per shared code from the question). So you need to use parent.thread_set.all() to get all the threads related to that category. Also if you define related_name inside Thread to Category ForeignKey like following example:
class Thread(..):
category = models.ForeignKey('Category', null=True, blank=True, on_delete=models.CASCADE, related_name='threads')
Then you can get the threads by parent.threads.all(). More information can be found in documentation.

Problem in saving formset in django Updateview

I am new in Django and Stackoverflow, so please accept my apology if my codes is not standard.
I try to create a blogging website. Users can create and update posts and each post can have one or more categories or no category. I use form for Post and Formset for Category. However, in Updateview for some reason I couldnt save the formset!!!!
models.py
class Post(models.Model):
title = models.CharField(max_length=128)
text = models.TextField(blank=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
created_date = models.DateTimeField(auto_now_add=True)
modified_date = models.DateTimeField(auto_now=True)
published_date = models.DateTimeField(blank=True, null=True)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post-detail', kwargs={'pk': self.pk})
class Category(models.Model):
name = models.CharField(max_length=128, unique=True)
description = models.TextField(blank=True)
posts = models.ManyToManyField(Post,
blank=True,related_name='categories')
class Meta:
verbose_name_plural = 'Categories'
def __str__(self):
return self.name
forms.py
from django import forms
from blogging.models import Post, Category
class PostUpdateForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'text', 'published_date']
CATEGORY_CHOICES = [('', 'Choose from the list')]
for c in Category.objects.all():
CATEGORY_CHOICES.append((c, c))
class CategoryUpdateForm(forms.ModelForm):
class Meta:
model = Category
fields = ['name']
labels = {'name': 'Category'}
help_texts = {'name': 'Choose category for your post'}
widgets = {
'name': forms.Select(choices=CATEGORY_CHOICES)
}
CategoryFormset = forms.modelformset_factory(Category,
form=CategoryUpdateForm, extra=1,
max_num=3, can_delete=True)
views.py
from blogging.models import Post, Category
from blogging.forms import PostUpdateForm, CategoryFormset
class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Post
template_name = 'blogging/post_update.html'
form_class = PostUpdateForm
formset_class = CategoryFormset
def form_valid(self, form):
form.instance.author = self.request.user
context = self.get_context_data()
formset = context['formset']
if formset.is_valid():
category_form = formset.save(commit=False)
category_form.posts.add(self.get_object())
category_form.save()
formset.save()
return super().form_valid(form)
def test_func(self):
post = self.get_object()
if self.request.user == post.author:
return True
return False
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
post = self.get_object()
if self.request.POST:
context['formset'] = self.formset_class(self.request.POST)
else:
context['formset'] =
self.formset_class(queryset=post.categories.all())
return context
template
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Profile Info</legend>
{{ u_form | crispy }}
{{ p_form | crispy }}
</fieldset>
<div class="form-group">
<button class="btn btn-outline-info"
type="submit">Update</button>
</div>
</form>
I could find the answer. By using extra-view package and use the CreateInlineView, and UpdateInlineView all can be solved. https://django-extra-views.readthedocs.io/en/latest/

Categories