I'm new to coding with django, and I'm trying to add comments to my blog app, but I'm having trouble with the validation of this form, it always returns False with form.is_valid(), so the object is never saved
views.py
def blog_post_detail_view(request, slug):
obj = get_object_or_404(BlogPost, slug=slug)
comments = Comment.objects.filter(blog_post=obj)
initial_data = {
"blog_post": obj,
}
form = CommentModelForm(request.POST or None, initial=initial_data)
if form.is_valid():
comment_obj = form.save(commit=False)
comment_obj.user = request.user
comment_obj.save()
form = CommentModelForm()
else:
print('not clean')
context = {
"object": obj,
"comments": comments,
"form": form,
}
template_name = 'blog/detail.html'
return render(request, template_name, context)
forms.py
from django import forms
from .models import Comment
class CommentModelForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['content','blog_post']
HTML
<form method='POST' action='.'> {% csrf_token %}
{{ form.as_p }}
<button type='submit'>Send</button>
</form>
models.py
class Comment(models.Model):
content = models.TextField(max_length=300)
user = models.ForeignKey(User, null=True, on_delete=models.SET_NULL, default=1)
blog_post = models.ForeignKey(BlogPost, null=True, on_delete=models.CASCADE)
timestamp = models.DateTimeField(auto_now_add=True)
def __unicode__(self):
return str(self.user.username)
def __str__(self):
return str(self.user.username)
Related
I have django application where I want user to be able to upload videos. My view looks like this:
class CreateVideo(View):
def post(self, request):
videos = models.Video.objects.all().order_by('-created_on')
form = forms.VideoUploadForm(request.POST)
if form.is_valid():
print('form is valid')
video = form.save(commit=False)
video.save()
print('video uploaded')
else:
print('form not valid')
context = {
'video_list': videos,
'form': form,
}
return redirect('index')
def get(self, request):
videos = models.Video.objects.all().order_by('-created_on')
form = forms.VideoUploadForm()
context = {
'video_list': videos,
'form': form,
}
return render(request, 'videos/upload_video.html', context)
My form:
class VideoUploadForm(forms.ModelForm):
class Meta:
model = Video
fields = ['title', 'description', 'file']
and model:
class Video(models.Model):
video_id = models.UUIDField(
primary_key=True,
default=uuid.uuid4,
editable=False,
unique=True
)
title = models.CharField(max_length=50, null=True)
description = models.CharField(max_length=500, null=True)
file = models.FileField(null=True)
created_on = models.DateTimeField(default=timezone.now, null=True)
at = models.ForeignKey(at, on_delete=models.CASCADE, null=True)
and my template:
<div>
<form method="post">
{% csrf_token %}
{{ form | crispy }}
<button>Submit!</button>
</form>
</div>
When I click submit button, I get: form not valid in terminal. I want form to be created, but form is just never valid. Where is the problem?
Are you setting the title, description and file fields when submitting the form? For now, they're considered required by your form until you set blank=True on the model's fields (you can read more about it here: https://stackoverflow.com/a/8609425/7196167).
I started learning Django about a month ago and just finished Django for Beginners by William Vincent. The book ends with Ch.15: Comments and shows how the admin can add comments to posts.
Q: Can someone, please, show me or point me in the right direction as to how I can let registered users also add comments to posts? Is there perhaps a 3rd party app for that?
What I have so far:
Models:
class Article(models.Model):
title = models.CharField(max_length=255)
body = models.TextField()
date = models.DateTimeField(auto_now_add=True)
image = models.ImageField(upload_to='images/', null=True, blank=True, height_field=None, width_field=None)
upload = models.FileField(upload_to='files/', null=True, blank=True)
author = models.ForeignKey(
get_user_model(),
on_delete=models.CASCADE,
)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('article_detail', args=[str(self.id)])
class Comment(models.Model):
article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='comment')
comment = models.CharField(max_length=140)
date = models.DateTimeField(auto_now_add=True)
author = models.ForeignKey(
get_user_model(), on_delete=models.CASCADE,
)
def __str__(self):
return self.comment
def get_absolute_url(self):
return reverse('article_list')
forms.py
class PostForm(forms.ModelForm):
class Meta:
model = Article
fields = ('title', 'body', 'image', 'upload')
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('comment', 'author')
Views:
class ArticleListView(LoginRequiredMixin, ListView):
model = Article
template_name = 'article_list.html'
comment_form = CommentForm
login_url = 'login'
class ArticleDetailView(LoginRequiredMixin, DetailView):
model = Article
template_name = 'article_detail.html'
login_url = 'login'
class ArticleUpdateView(LoginRequiredMixin, UpdateView):
model = Article
fields = ('title', 'body', 'image', 'upload')
template_name = 'article_edit.html'
login_url = 'login'
def dispatch(self, request, *args, **kwargs):
obj = self.get_object()
if obj.author != self.request.user:
raise PermissionDenied
return super().dispatch (request, *args, **kwargs)
class ArticleDeleteView(LoginRequiredMixin, DeleteView):
model = Article
template_name = 'article_delete.html'
success_url = reverse_lazy('article_list')
login_url = 'login'
def dispatch(self, request, *args, **kwargs):
obj = self.get_object()
if obj.author != self.request.user:
raise PermissionDenied
return super().dispatch (request, *args, **kwargs)
class ArticleCreateView(LoginRequiredMixin, CreateView):
model = Article
form_class = PostForm
template_name = 'article_new.html'
login_url = 'login'
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
URLs:
urlpatterns = [
path('<int:pk>/edit/', ArticleUpdateView.as_view(), name='article_edit'),
path('<int:pk>/', ArticleDetailView.as_view(), name='article_detail'),
path('<int:pk>/delete/', ArticleDeleteView.as_view(), name='article_delete'),
path('', ArticleListView.as_view(), name='article_list'),
path('new/', ArticleCreateView.as_view(), name='article_new'),]
Thank you for your attention.
Solved. In my views.py I added the following function:
def add_comment(request, pk):
article = get_object_or_404(Article, pk=pk)
if request.method == "POST":
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save(commit=False)
comment.article = article
comment.save()
return redirect('article_detail', pk=article.pk)
else:
form = CommentForm()
return render(request, 'add_comment.html', {'form': form})
Then the following .html file was added to templates:
add_comment.html
{% extends 'base.html' %}
{% block content %}
<h4>Add a Comment</h4>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<p>{{ form.as_p }}</p>
<button type="submit" class="btn btn-success">Submit</button>
</form>
{% endblock content %}
P.S.: Initially I was getting an ImportError: cannot import name 'add_comment' from 'articles.views'.
I thought it was a circular import problem and what worked for me was just getting the def add_comment indentation right.
My code is exactly the same as https://simpleisbetterthancomplex.com/series/2017/09/18/a-complete-beginners-guide-to-django-part-3.html#rendering-bootstrap-forms this. But when I click on the "post" button in the template it shows me the same page without the errors like the field is required.
In my virtualenv,
Python 3.7.4,
Django 2.2.7
and I've installed Django-widgets-improved.
//view
def new_topic(request, pk):
board = get_object_or_404(Board, pk=pk)
user = User.objects.first()
if request.method == 'POST':
form = NewTopicForm(request.POST)
if form.is_valid():
topic = form.save(commit=False)
topic.board = board
topic.starter = user
topic.save()
post = Post.objects.create(
message = form.cleaned_data.get('message'),
topic = topic,
created_by = user
)
return redirect('board_topics', pk = board.pk)
form = NewTopicForm()
return render(request, 'new_topic.html', {'board': board, 'form': form})
//form html
<form method="post" novalidate>
{% csrf_token %}
{{ form.non_field_errors }}
{{ form.errors }}
{{ form.as_p }}
<button type="submit" class="btn btn-success">Post</button>
</form>
//models.py
from django.db import models
from django.contrib.auth.models import User
class Board(models.Model):
name = models.CharField(max_length=25, unique=True)
description = models.CharField(max_length=100)
def __str__(self):
return self.name
class Topic(models.Model):
subject = models.CharField(max_length=255)
last_updated = models.DateTimeField(auto_now_add=True)
board = models.ForeignKey(Board, on_delete=models.CASCADE, related_name='topics')
starter = models.ForeignKey(User, on_delete=models.CASCADE, related_name='topics')
class Post(models.Model):
message = models.TextField(max_length=4000)
topic = models.ForeignKey(Topic, on_delete=models.CASCADE, related_name='posts')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(null=True)
created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
updated_by = models.ForeignKey(User, null=True, on_delete=models.CASCADE, related_name='+')
//forms.py
from django import forms
from .models import Topic
class NewTopicForm(forms.ModelForm):
message = forms.CharField(
widget=forms.Textarea(
attrs={'rows':5, 'placeholder':'What is in your mind?'}
),
max_length=4000,
help_text='The max length of the text is 4000.')
class Meta:
model = Topic
fields = ['subject', 'message']
//main/urls.py
from django.contrib import admin
from django.urls import path
from boards import views
urlpatterns = [
path('admin/', admin.site.urls),
path('', views.home, name='home'),
path('boards/<int:pk>/', views.board_topics, name='board_topics'),
path('boards/<int:pk>/new/', views.new_topic, name='new_topic'),
]
Inital form form = NewTopicForm() must be declared before if request.method == 'POST': or in else: condition. In your case the clean form always arrives at the template.
def new_topic(request, pk):
board = get_object_or_404(Board, pk=pk)
user = User.objects.first()
form = NewTopicForm() # <---
if request.method == 'POST':
form = NewTopicForm(request.POST)
if form.is_valid():
topic = form.save(commit=False)
topic.board = board
topic.starter = user
topic.save()
post = Post.objects.create(
message = form.cleaned_data.get('message'),
topic = topic,
created_by = user
)
return redirect('board_topics', pk = board.pk)
return render(request, 'new_topic.html', {'board': board, 'form': form})
or
def new_topic(request, pk):
board = get_object_or_404(Board, pk=pk)
user = User.objects.first()
if request.method == 'POST':
form = NewTopicForm(request.POST)
if form.is_valid():
topic = form.save(commit=False)
topic.board = board
topic.starter = user
topic.save()
post = Post.objects.create(
message = form.cleaned_data.get('message'),
topic = topic,
created_by = user
)
return redirect('board_topics', pk = board.pk)
else:
form = NewTopicForm() # <---
return render(request, 'new_topic.html', {'board': board, 'form': form})
I am working on a simple blog which has a model Post. I am trying to create a form for adding blog posts (or adding comments to posts for that matter) so that end users don't have to fill out a form box asking the end user for a username. I would like to be able to just ask for a title and body text for a blog post, and when hit post, it will be posted as the authenticated user.
I tried not including 'user' field in fields in forms, but it seems to be mandatory. Maybe I need to just make it hidden somehow using widgets? In templates, I could maybe write the following:
{% if user.is_authenticated %}
<p>Posting as {{request.user}}</p>
{% else %}
<p><a href={% url 'register' %}Please register to add a blog post</a></p>
{% endif %}
Though I am not sure, I think it would make more sense to have logic in my views.py file.
Here's my 'blog.models' file:
class Post(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
title = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
text = models.TextField()
published_date = models.DateTimeField(auto_now=True)
# pip install Pillow
image = models.ImageField(null=True, blank=True,
upload_to='photos/%Y/%m/%d/',)
def summary(self):
"""Return a summary for very long posts to
get a glimpse from admin panel"""
return self.text[:100]
def _get_unique_slug(self):
"""Assigns a number to the end of a given slug field to prevent
duplicated slug error. if title of a post is 'ayancik', and another
user creates another post with the same title, second posts' slug
is assigned a value: 'ayancik-2'"""
slug = slugify(self.title)
unique_slug = slug
num = 1
while Post.objects.filter(slug=unique_slug).exists():
unique_slug = '{}-{}'.format(slug, num)
num += 1
return unique_slug
def save(self, *args, **kwargs):
"""Automatically assign slug to objects
by overriding save method"""
self.slug = self._get_unique_slug()
super().save(*args, **kwargs)
def pub_date_pretty(self):
return self.published_date.strftime('%b %e, %Y')
def __str__(self):
"""String representation"""
return self.title
def get_absolute_url(self):
# what does kwargs={'slug':self.slug} really achieve here?
# where would we use 'key-value' pair?
"""Returns the url to access a detailed post"""
return reverse('post-detail', kwargs={"slug": self.slug})
class Meta:
ordering = ['-published_date',]
class Comment(models.Model):
post = models.ForeignKey('blog.Post', on_delete=models.CASCADE,
related_name='comments')
user = models.CharField(max_length=200)
text = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
approved_comment = models.BooleanField(default=False)
def approve_comment(self):
self.approved_comment = True
self.save()
def __str__(self):
return self.text
'blog.forms' file:
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['user', 'title', 'text', 'image']
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('user', 'text',)
and 'blog.views' file:
#login_required
def create_post(request):
if request.method == 'POST':
post_form = PostForm(request.POST)
if post_form.is_valid():
post = post_form.save(request)
post.save()
else:
print(post_form.errors)
else:
# when not POST request, display the empty form
# meaning -> if request.method=='GET':
post_form = PostForm()
context = {
'post_form': post_form,
}
return render(request, 'blog/addpost.html', context)
def add_comment_to_post(request, slug):
post = get_object_or_404(Post, slug=slug)
if request.method == 'POST':
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save(commit=False)
comment.post = post
comment.save()
return redirect('post-detail', slug=slug)
else:
form = CommentForm()
template_name = 'blog/add_comment_to_post.html'
return render(request, template_name , {'form': form })
You omit the user in the PostForm:
class PostForm(forms.ModelForm):
class Meta:
model = Post
# no user
fields = ['title', 'text', 'image']
or we can display all fields except 'user' like:
class PostForm(forms.ModelForm):
class Meta:
model = Post
exclude = ('user', )
and then you add the user to the instance in your view:
from django.shortcuts import redirect
#login_required
def create_post(request):
if request.method == 'POST':
post_form = PostForm(request.POST, request.FILES)
if post_form.is_valid():
post = post_form.save(commit=False)
post.user = request.user
post.save()
return redirect('some_view')
else:
post_form = PostForm()
context = {
'post_form': post_form,
}
return render(request, 'blog/addpost.html', context)
The commit=False thus prevents the from from saving the Post object to the database.
Since you want to upload an image, you should pass request.FILES [Django-doc] to the PostForm as well, otherwise you will not process uploaded files. You furthermore need to specify that you use enctype="multipart/form-data" in your form:
<form enctype="multipart/form-data" method="POST" action="{% url 'create_post' %}">
...
</form>
It is better to use a redirect [Django-doc] in case of a successful POST request, since this is the Post/Redirect/Get pattern [wiki]. By rendering a new form, if the user refreshes the page, he/she would create a second post, which is probably not what you want.
I'm trying to create a post and update my list of posts. I currently get this error IntegrityError at /posts/create/ NOT NULL constraint failed: posts_post.publish Not sure what the error means and how to fix it. The files below are my posts/views.py, forms.py, post_forms.py and models
def posts_create(request):
# return HttpResponse("<h1> Create a posts. </h1>")
form = PostForm(request.POST or None)
if form.is_valid():
instance = form.save(commit=False)
instance.user = request.user
instance.save()
context = {
"form": form
}
# if request.method == "POST":
# print("This is the content: ", request.POST.get("content"))
return render(request, "post_form.html", context)
def posts_detail(request, id):
instance = get_object_or_404(Post, id=id)
context = {
"user": instance.user,
"instance": instance
}
return render(request, "posts_detail.html", context)
def posts_list(request):
# return HttpResponse("<h1> List a posts. </h1>")
# TODO: Privacy stuff
queryset = Post.objects.all()
context = {
"object_list": queryset,
"user": "username"
}
return render(request, "post.html", context)
Models for post:
user = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)
status = models.CharField(max_length=6, choices=Status, default=POST)
content = models.TextField()
publish = models.DateField(auto_now=False, auto_now_add=False)
updated = models.DateTimeField(auto_now=True, auto_now_add=False)
timestamp = models.DateTimeField(auto_now=False, auto_now_add=True)
privacy = models.IntegerField(choices=Privacy, default=PUBLIC)
unlisted = models.BooleanField(default=False)
This is the post_form.html
<html>
<body>
<h3>Create Post</h3>
<form method="POST" action="">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Share" />
</form>
</body>
</html>
This is the respective forms.py
from django import forms
from .models import Post
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = [
"content"
]
from datetime import datetime
def posts_create(request):
# return HttpResponse("<h1> Create a posts. </h1>")
form = PostForm(request.POST or None)
if form.is_valid():
instance = form.save(commit=False)
instance.user = request.user
instance.publish = datetime.now()
instance.save()
context = {
"form": form
}
# if request.method == "POST":
# print("This is the content: ", request.POST.get("content"))
return render(request, "post_form.html", context)
do this in your view. import the first line then change your view