We have a blog-like wagtail site and would like to add comments to our post types. Each post is a page object.
We thought about using django-contrib-comments or implement an own plain django comments app with ajax.
But what would be the "all-wagtail-approach" for having a comment functionality on the public wagtail site (only for logged in wagtail users, using ajax)?
We're not looking for a complete implementation, we just need some hints or tips for a wagtail-sensible approach.
Our actual approach is having comments available in in wagtail admin as an InlinePanel on every PostPage. But we're struggling to render a django form for adding new comments on the frontend:
# blog/models.py
class PostPage(RoutablePageMixin, Page):
...field definitions...
#route(r'^comment/new/$')
def add_comment_to_post(self, request):
from .forms import CommentForm
if request.method == 'POST':
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save()
return render(request, self.template, {
'page': self,
'comment': comment,
})
else:
form = CommentForm()
return render(request, self.template, {
'page': self,
'form': form,
})
content_panels = Page.content_panels + [
...FieldPanels...
InlinePanel('comments', label="Comments"),
]
class Comment(models.Model):
text = models.TextField()
panels = [FieldPanel('text'),]
def __str__(self):
return self.text
class Meta:
abstract = True
class PostPageComments(Orderable, Comment):
page = ParentalKey('PostPage', related_name='comments')
# blog/forms.py
from django import forms
from .models import PostPageComments
class CommentForm(forms.ModelForm):
class Meta:
model = PostPageComments
fields = ['text']
# blog/templates/blog/post_page.html
{% extends "core/base.html" %}
{% load wagtailcore_tags %}
{% block content %}
{% include 'stream/includes/post_list_item.html' with include_context="post_detail_page" post=self %}
<h3>New comment</h3>
<form method="post" action="comment/new/" id="comment-new">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="save btn btn-default">Send</button>
</form>
{% endblock %}
But: The form ({{ form.as_p }}) is not rendered - any hints? A django admin for PostPageComments works as expected.
Some minor changes to my model and template and I have my simple comment form (code not mentioned is unchanged in relation to the question; unrelated code omitted for brevity):
# blog/models.py
class PostPage(Page):
def serve(self, request):
from .forms import CommentForm
if request.method == 'POST':
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save(commit=False)
comment.page_id = self.id
comment.save()
return redirect(self.url)
else:
form = CommentForm()
return render(request, self.template, {
'page': self,
'form': form,
})
class Comment(models.Model):
text = models.TextField()
class Meta:
abstract = True
class PostPageComments(Orderable, Comment):
page = ParentalKey('PostPage', related_name='comments')
# blog/templates/blog/post_page.html
<form method="post" id="comment-new">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Send</button>
</form>
Related
I am trying to send a form with an image, I select the image but I always get: "this field is required" when I send the form.
here is my code:
models.py:
from django.db import models
from django.contrib.auth.models import User
class Picture(models.Model):
created_at = models.DateField(auto_now_add=True)
update_at = models.DateField(auto_now=True)
image = models.ImageField()
caption = models.CharField(max_length=50)
author = models.ForeignKey('auth.user', on_delete=models.CASCADE, related_name='pictures')
forms.py:
from django import forms
class PictureForm(forms.Form):
image = forms.ImageField()
caption = forms.CharField(max_length=50)
views.py:
from django.shortcuts import render
from django.http import HttpResponseRedirect
from .models import Picture
from .forms import PictureForm
from django.contrib.auth.models import User
def pictures_view(request):
pictures = Picture.objects.all()
context = {'pictures': pictures}
return render(request, 'pictures/pictures.html', context)
def picture_form_view(request):
if request.method == 'POST':
form = PictureForm(request.POST, request.FILES)
if form.is_valid():
clean_data = form.cleaned_data()
Picture.objects.create(clean_data)
return HttpResponseRedirect('/')
else:
form = PictureForm()
return render(request, 'pictures/picture_form.html', {'form': form})
HTML:
{% extends 'pictures/base.html' %}
{% block title %}publish{% endblock %}
{% block content %}
<form class="form" action="" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit">
</form>
{% endblock %}
And a Little question, how can I complete the field author automaticly with the actual user?
first of all ImageField in model takes one compulsory argument upload_to so in your models
class Picture(models.Model):
###
image = models.ImageField(upload_to='upload/to/path')
###
now in your view
def picture_form_view(request):
if request.method == 'POST':
form = PictureForm(request.POST, request.FILES)
if form.is_valid():
clean_data = form.cleaned_data()
Picture.objects.create(**clean_data)
return HttpResponseRedirect('/')
else:
form = PictureForm()
return render(request, 'pictures/picture_form.html', {'form': form})
and to save current logged in user as default override form_valid()
method in Form class like
def form_valid(self, form):
form.instance.created_by = self.request.user
return super().form_valid(form)
Try to set enctype to "multipart/form-data" in the form tag.
<form action="#" method="post" enctype="multipart/form-data">
input here ...
</form>
In short:
I have created a Post model and Comment model and created a comment form, I am serving a single url which will show all posts, related comment and a comment form to enter new comments. With a submission page is reloaded with new comments. But when I submit the comment I get the error:
django.db.utils.IntegrityError: NOT NULL constraint failed: book_comment.related_post_id
This is one answer that looked promising but I am unable to do something.
I think it is not getting parent post id.
Long Version:
This is my model File:
def user_image_path(instance, filename):
return f"profile/user_{random.randint(1,1000)}_{filename}"
class Post(models.Model):
post_title = models.CharField(max_length=250)
post_creator = models.CharField(max_length=150)
creator_pic = models.ImageField(upload_to=user_image_path)
post_body = models.TextField()
post_created = models.DateTimeField(auto_now_add=True)
post_updated = models.DateTimeField(auto_now=True)
def __str__(self):
return f"{self.post_title} **{self.post_creator}**"
class Comment(models.Model):
related_post = models.ForeignKey(Post, related_name="comments")
comment_creator = models.CharField(max_length=150)
comment_body = models.CharField(max_length=1024)
comment_created = models.DateTimeField(auto_now_add=True)
comment_updated = models.DateTimeField(auto_now=True)
def __str__(self):
return f"{self.comment_creator}"
This is my form:
from django import forms
from .models import Post, Comment
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['comment_creator', 'comment_body']
This is views:
from django.shortcuts import render, HttpResponseRedirect,reverse
from .models import Comment, Post
from .forms import CommentForm
# Create your views here.
def servePage(request):
if request.method == 'POST':
form = CommentForm(request.POST)
if form.is_valid():
form.save()
HttpResponseRedirect(reverse('serve'))
else:
form = CommentForm()
posts = Post.objects.all()
return render(request, 'book/showpost.html', {'posts': posts, 'form': form})
This is my html template:
{% extends 'book/base.html' %}
{% block content %}
<h1>Welcome to book of life</h1>
<h2>New posts</h2>
<ul>
{% for post in posts %}
<li>{{ post.post_title }} by <small>{{ post.post_creator }}</small></li>
<p>{{ post.post_created|timesince }}</p>
<p>Content: <span>{{ post.post_body }}</span></p>
{# <br>#}
<h3>Comments:</h3>
{% for comment in post.comments.all %}
<p>{{ comment.comment_creator }} => {{ comment.comment_body }}</p>
{% endfor %}
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" name="comment">
</form>
<br><br>
{% endfor %}
</ul>
{% endblock %}
Sorry for any mistakes. Thanks in advance.
I believe this error has occurred because you have tried to create a new record in the comment model that leaves the related_post field blank, when it shouldn't be. If you are happy for this field to be left blank, you can change the field to be the following in models.py :
related_post = models.ForeignKey(Post, related_name="comments", null=True)
Alternatively, you may not want this to be blank. If you add related_post to the fields in the form, a drop down box will be created with all the posts and you can select one of these to comment on.
You may also be able to automatically detect what post you are commenting on, but I'm unsure how this is done.
Thanks to #cbuch1800 I finally got the answer. Here it is and the changes to file:
In template file after {{form.as_p}} I added a line to pass the current post Primary Key(id) to view.
Template file:
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="hidden" name="post_pk" value={{ post.pk }}>
<input type="submit" name="comment">
</form>
In view file retrieved the post object and added the post object to related comment.
View:
def servePage(request):
if request.method == 'POST':
form = CommentForm(request.POST)
post = Post.objects.get(pk=request.POST['post_pk'])
if form.is_valid():
comment = form.save(commit=False)
comment.related_post = post
## related_post from Comment model
comment.save()
HttpResponseRedirect(reverse('serve'))
else:
form = CommentForm()
posts = Post.objects.all()
return render(request, 'book/showpost.html', {'posts': posts, 'form': form})
I've come to the end of the DjangoGirls tutorial and have been trying to add some extra functionality of my own
I have a new model called Subject. Each blog post now has a subject such as cookery, gardening, astrophysics, general, etc. When a blogger writes a new post, I want to force the Subject dropdown to default to 'General', but my template (post_edit.html) doesn't give me access to the SELECT so I can't set a default value
post_edit.html:
{% extends 'blog/base.html' %}
{% block content %}
<div>
<h1>New post</h1>
<form method="POST" class="post-form">{% csrf_token %}
{% if form.non_field_errors %}
<ul>
{% for error in form.non_field_errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
{% for field in form %}
<div id="{{ field.auto_id }}_container"> {{ field.help_text }}
<div>
<span class="staticdata">{{ field.label_tag }}</span>
<span class="staticdata">{{ field }}</span>
</div>
<div id="{{ field.auto_id }}_errors">
{{ field.errors }}
</div>
</div>
{% endfor %}
<button type="submit" class="save btn btn-default">Save</button>
</form>
</div>
{% endblock %}
forms.py
from django import forms
from .models import Post, Subject
from django.contrib.auth.models import User
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ('title', 'text', 'subject', 'author')
models.py
from django.db import models
from django.utils import timezone
class Post(models.Model):
author = models.ForeignKey('auth.User')
title = models.CharField(max_length=200)
text = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
published_date = models.DateTimeField(blank=True, null=True)
subject = models.ForeignKey('Subject')
def publish(self):
self.published_date = timezone.now()
self.save()
def __str__(self):
return self.title
class Subject(models.Model):
description = models.CharField(max_length=200)
def __str__(self):
return self.description
views.py
from django.shortcuts import render, get_object_or_404
from django.shortcuts import redirect
from django.contrib.auth.models import User
from django.contrib.auth import authenticate,logout,login
from django.utils import timezone
from .models import Post, Subject
from .forms import PostForm
def post_list(request):
posts = Post.objects.filter(published_date__lte=timezone.now()).order_by('-published_date')
return render(request, 'blog/post_list.html', {'posts': posts})
def post_detail(request, pk):
post = get_object_or_404(Post, pk=pk)
return render(request, 'blog/post_detail.html', {'post': post})
def post_new(request):
if request.method == "POST":
form = PostForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.published_date = timezone.now()
post.save()
return redirect('post_detail', pk=post.pk)
else:
form = PostForm()
return render(request, 'blog/post_edit.html', {'form': form})
def post_edit(request, pk):
post = get_object_or_404(Post, pk=pk)
if request.method == "POST":
form = PostForm(request.POST, instance=post)
if form.is_valid():
post = form.save(commit=False)
#... post.author = request.user
post.published_date = timezone.now()
post.save()
return redirect('post_detail', pk=post.pk)
else:
form = PostForm(instance=post)
return render(request, 'blog/post_edit.html', {'form': form})
def subject_list(request):
subjects = Subject.objects.all().order_by('description')
return render(request, 'blog/subject_list.html', {'subjects': subjects})
How can I set a default value for this so that the dropdown is populated with a default option when the user navigates to the page?
Try setting an initial value, in the following way
def post_new(request):
if request.method == "POST":
form = PostForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.published_date = timezone.now()
post.save()
return redirect('post_detail', pk=post.pk)
else:
# Get the default subject.
# Perhaps you want to give your subject a name, or use an id here,
# but assuming your default subject's description is 'General' for
# this example
default_subject = Subject.objects.get(description="General")
# Set the default value
form = PostForm(initial={"subject":default_subject})
return render(request, 'blog/post_edit.html', {'form': form})
I'm trying to allow users to create category and post(that will be tagged to some category) I think I wrote the code right, but I'm getting 404 error with No Post matches the given query.
Here is my code.
this is my form
class CategoryForm(forms.ModelForm):
name = forms.CharField(max_length=128, help_text="aa")
likes = forms.IntegerField(widget=forms.HiddenInput(), initial=0)
slug = forms.CharField(widget=forms.HiddenInput, required=False)
class Meta:
model = Category
fields =('name',)
class PostForm(forms.ModelForm):
title = forms.CharField(max_length=128, help_text="bb")
views = forms.IntegerField(widget=forms.HiddenInput(), initial=0)
class Meta:
model = Post
fields = ['title', 'content', 'image', 'views', 'category']
this is my view
#for adding category
def add_category(request):
if request.method == 'POST':
form = CategoryForm(request.POST)
if form.is_valid():
form.save(commit=True)
return index(request)
else:
print form.errors
else:
form = CategoryForm()
return render(request, 'main/add_category.html', {'form':form})
#for adding post/see diff style :)
def add_post(request):
context = RequestContext(request)
if request.method == "POST":
form = PostForm(request.POST, request.FILES)
if form.is_valid():
form.save(commit=True)
return redirect(index)
else:
print form.errors
else:
form = PostForm()
return render_to_response('main/add_post.html', {'form':form}, context)
And this is my url
url(r'^add_post/', views.add_post, name='add_post'),
url(r'^add_category/$', views.add_category, name='add_category'),
]
And finally my templates
{
% extends 'base.html' %}
{% block content %}
<form id="post_form" method="post" action="/main/add_post/" enctype="multipart/form-data">
{% csrf_token %}
{{form}}
<input type="submit" name="submit" value="Create Post">
</form>
{% endblock %}
You should use action="{% url 'main:add_post' %}" instead of action="/main/add_post/".
Also check that in your projects's urls.py main app's urls included with namespace like
url(r'^main/', include('main.urls', namespace="main"))
If you don't use namespace, than change to action="{% url 'add_post' %}".
If error reoccurs, please provide more information.
Which ulr exactly you enter in browser to get this error?
Your template makes the form post to main/add_post/ while your url for the view is add_post/. You should make them consistent.
I have two models:
class Post(models.Model):
image = models.ImageField(upload_to='%Y/%m/%d')
title = models.CharField(max_length=200)
class Addimg(models.Model):
addimages = models.ForeignKey('Post', null=True)
addimg = models.ImageField(upload_to='images')
I wish to add images to my Post model with the "Addimg" model which works fine so far, but now i want that when I edit my parent model (Post), all the appended "Addimg" models also appear in the form. How could i do that? What would be the simplest solution?
Here is my view which handles my "parent" form:
def edit(request, pk):
post = get_object_or_404(Post, pk=pk)
if request.method == "POST":
form = PostForm(request.POST, request.FILES, instance=post)
if form.is_valid():
post = form.save(commit=False)
post.save()
return redirect('blog.views.detail', pk=post.pk)
else:
form = PostForm(instance=post)
return render(request, 'blog/edit.html', {'form': form})
and my forms.py:
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ('image', 'title',)
class AddimgForm(forms.ModelForm):
class Meta:
model = Addimg
fields = ('addimages', 'addimg',)
In my form template I have:
<form enctype="multipart/form-data" method="POST" class="post-form">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" >Save</button>
</form>
I would be very happy about any tips or usefull links since i'm very new to django/programming I even don't know the right keywords to search for. Thanks
Have a look at inline formsets.
Ok got it easy thing for django:
in forms.py:
from django.forms.models import inlineformset_factory
MyFormSet = inlineformset_factory(Post, Addimg, extra=1, fields = ('addimages', 'addimg',))
in views.py:
from .forms import PostForm, MyFormSet
def manageimages(request, pk):
post = get_object_or_404(Post, pk=pk)
if request.method == "POST":
formset = MyFormSet(request.POST, request.FILES, instance=post)
if formset.is_valid():
formset.save()
post.save()
return redirect('blog.views.someview')
else:
formset = MyFormSet(instance=post)
return render(request, 'blog/myformsettemplate.html', {'formset': formset})
and in myformsettemplate.html:
<form enctype="multipart/form-data" method="POST" class="post-form">
{% csrf_token %}
{{ formset.management_form }}
{% for form in formset %}
<div class="third">
{{ form.as_p }}
</div>
{% endfor %}
<button type="submit" >Save</button>
</form>