Django unclear 404 - python

EDIT : Found the solution myself !
This is what I did. I'm pretty sure it's not a best practice, but it worked for me.
class CommentCreateView(CreateView):
def get(self, request, *args, **kwargs):
context = {'form': CommentForm()}
return render(request, 'news/add_comment_to_article.html', context)
def post(self, request, *args, **kwargs):
form = CommentForm(request.POST)
if form.is_valid():
article = get_object_or_404(Article, pk=kwargs.get('pk'))
print(article)
comment = form.save(commit=False)
comment.post = article
comment.save()
return HttpResponseRedirect(reverse('news:article', kwargs={'article_id': article.pk}))
I have a question, after converting my view (included below) from function based view to class based view I keep getting an error (page not found - 404) after trying to submit a comment on an article. Why is that ?
the view now :
class CommentCreateView(RedirectView):
model = Comment
form_class = CommentForm
template_name = 'news/add_comment_to_article.html'
def form_valid(self, *args, **kwargs):
article = get_object_or_404(Article, pk=kwargs.get('pk'))
comment = form.save(commit=False)
comment.post = article
comment.save()
return HttpResponseRedirect(reverse('news:article', kwargs={'article_id': article.pk}))
the same view how it used to be, function based (working) :
def add_comment_to_article(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.post = article
comment.save()
return HttpResponseRedirect(reverse('news:article', kwargs={"article_id": article.pk}))
else:
form = CommentForm()
return render(request, 'news/add_comment_to_article.html', {'form': form})
comment form:
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('author', 'text',)

Use FormView instead of RedirectView. (We can see that RedirectView doesn't have form_valid method.)
Read more about FormView in Django's official documentation.

Related

Django prepopulate form in UpdateView

Would like to know if it's possible to prepopulate my CommentForm in UpdateView. Updating comments work's except that the form is not loaded prepopulated.
When testing using a separate template it's loaded prepopulated, but I would like to use the same template (PostDetail) using a modal to update the comment.
views.py:
class PostDetail(View):
def get(self, request, slug, pk, *args, **kwargs):
queryset = Post.objects.all()
post = get_object_or_404(queryset,slug=slug, pk=pk)
comments = post.comments.order_by('-created_on')
return render(
request,
'blog/post_detail.html',
{
'post': post,
'comments': comments,
'comment_form': CommentForm()
},
)
def post(self, request, slug, pk, *args, **kwargs):
if request.user.is_authenticated:
queryset = Post.objects.all()
post = get_object_or_404(queryset, slug=slug, pk=pk)
comment_form = CommentForm(data=request.POST)
if comment_form.is_valid():
comment = comment_form.save(commit=False)
comment.post = post
comment.author = request.user
comment.save()
messages.info(request, 'Comment added')
return HttpResponseRedirect(reverse('post_detail', args=[slug, pk]))
class CommentUpdate(LoginRequiredMixin, UserPassesTestMixin, generic.UpdateView):
model = Comment
template_name = 'blog/post_detail.html'
form_class = CommentForm
def get_success_url(self):
post = Post.objects.get(pk=self.object.post.pk)
messages.info(self.request, 'Comment updated')
return post.get_absolute_url()
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
def test_func(self):
comment = self.get_object()
if self.request.user == comment.author:
return True
return False`
forms.py:
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('body',)
post_detail.html (form):
<form action="{% url 'comment_update' post.slug comment.pk %}" method="POST">
{% csrf_token %}
{{ comment_form | crispy }}
<button type="submit" class="btn">Update</button>
</form>
Please need help :)
I have tested adding:
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['instance'] = self.get_object()
return kwargs
The form is still not prepopulated. It works if I use a separate template and render the form using {{ form | crispy }}.
Perhaps it's because I have two {{ comment_form | crispy }} in my post_detail.html, one when creating the comment and one to update it?
I've spend alot of time trying to figure this out :)
You can pass that instance of the Comment model which you'd like to update in CommetForm. In the get_form_kwargs() method, you can add the instance keyword argument to the form kwargs with the instance of the Comment model to update. Then, the CommentForm will be pre-populated with the data from that instance while displaying in frontend so:
class CommentUpdate(LoginRequiredMixin, UserPassesTestMixin, generic.UpdateView):
model = Comment
template_name = 'blog/post_detail.html'
form_class = CommentForm
def get_success_url(self):
post = Post.objects.get(pk=self.object.post.pk)
messages.info(self.request, 'Comment updated')
return post.get_absolute_url()
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['instance'] = self.get_object()
return kwargs
def test_func(self):
comment = self.get_object()
if self.request.user == comment.author:
return True
return False

The view post.views.view didn't return an HttpResponse object. It returned None instead

I want to create a new post using PostCreateView and go to the details page of the new post in the next step, but I get this error:
(The view post.views.view didn't return an HttpResponse object. It returned None instead.)
views
class PostDetailView(View):
"""see detail post"""
def get(self, request, post_id, post_slug):
post = Post.objects.get(pk=post_id, slug=post_slug)
return render(request, "post/detail.html", {"post": post})
class PostCreateView(LoginRequiredMixin, View):
form_class = PostCreateUpdateForm
def get(self, request, *args, **kwargs):
form = self.form_class
return render(request, "post/create.html", {"form": form})
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
new_post = form.save(commit=False)
new_post.slug = slugify(form.cleaned_data["body"][:20])
new_post.user = request.user
new_post.save()
messages.success(request, "you created a new post", "success")
return redirect("post:post-detail", new_post.id, new_post.slug)
models
class Post(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
body = models.TextField()
slug = models.SlugField()
img = models.ImageField(upload_to="%Y/%m/%d/")
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
urls
app_name = 'post'
urlpatterns = [
path('', views.BlogView.as_view(), name="home"),
path('detail/<int:post_id>/<slug:post_slug>/', views.PostDetailView.as_view(), name="post-detail"),
path('delete/<int:post_id>/', views.PostDeleteView.as_view(), name="post-delete"),
path('update/<int:post_id>/', views.PostUpdateView.as_view(), name="post-update"),
path('create/', views.PostCreateView.as_view(), name="post-create"),
]
In case the form is not valid, you should rerender the template with the form, so:
class PostCreateView(LoginRequiredMixin, View):
form_class = PostCreateUpdateForm
def get(self, request, *args, **kwargs):
form = self.form_class
return render(request, "post/create.html", {"form": form})
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
new_post = form.save(commit=False)
new_post.slug = slugify(form.cleaned_data['body'][:20])
new_post.user = request.user
new_post.save()
messages.success(request, 'you created a new post', 'success')
return redirect('post:post-detail', new_post.id, new_post.slug)
return render(request, 'post/create.html', {'form': form})
But you are implementing a lot of boilerplate code here. What you here do is implementing a CreateView [Django-doc]:
from django.contrib.messages.views import SuccessMessageMixin
from django.views.generic import CreateView
class PostCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
form_class = PostCreateUpdateForm
template_name = 'post/create.html'
success_message = 'you created a new post'
def form_valid(self, form):
form.instance.slug = slugify(form.cleaned_data['body'][:20])
form.instance.user = request.user
return super().form_valid()
def get_success_url(self):
return reverse('post:post-detail', args=(new_post.id, new_post.slug))
Your "post" method in PostCreateView only returns a response if the form is valid. If it isn't valid, it will return None, causing an error.
Modify that method so it looks like this:
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
new_post = form.save(commit=False)
new_post.slug = slugify(form.cleaned_data["body"][:20])
new_post.user = request.user
new_post.save()
messages.success(request, "you created a new post", "success")
return redirect("post:post-detail", new_post.id, new_post.slug)
return render(request, "post/create.html", {"form": form})

How would I rewrite this funtional view as a generic CreateView in django

The model for the view is Chapter but it needs to call Book so that the chapter is saved to the right book.
def new_chapter(request, book_id):
"""Adds a new chapter"""
book = Book.objects.get(id=book_id)
if request.method != 'POST':
#No data submitted; create a blank form
form = ChapterForm()
else:
# POST data submitted; process data
form = ChapterForm(data=request.POST)
if form.is_valid():
new_chapter = form.save(commit=False)
new_chapter.book = book
new_chapter.save()
return redirect('fallen_worlds:book', book_id=book_id)
# Display a blank or invalid form.
context = {'book': book, 'form': form}
return render(request, 'fallen_worlds/new_chapter.html', context)
You can specify the book in the .form_valid(…) method [Django-doc]:
from django.views.generic.edit import CreateView
from django.urls import reverse
ChapterCreateView(CreateView):
model = Chapter
form_class = ChapterForm
template_name = 'fallen_worlds/new_chapter.html'
def form_valid(self, form):
form.instance.book_id = self.kwargs['book_id']
return super().form_valid(form)
def get_success_url(self):
return reverse(
'fallen_worlds:book',
kwargs={'book_id': self.kwargs['book_id']}
)

Django URL regex complication

I am having trouble defining the URLs for my django app.
I want it so that when I type,
http://example.com/post/3 :: I can read a post
http://example.com/post/3/edit :: I can edit a post
At the moment, I defined it like the following:
url(r'^post/(?P<id>\d+)/edit/$',
'board.views.edit_post',
name='edit_post'),
url(r'^post/(?P<id>\d+)',
'board.views.view_post',
name='view_post'),
However this does not seem to work because when I click on my "edit" link which links to
"/post/{{ post.id }}/edit"
I get the intended url in my address bar but am not taken to the edit view...
===================================
--EDIT--
#login_required
def edit_post(request, id):
if id:
post = get_object_or_404(Post, id=id)
if post.owner_user != request.user:
return HttpResponseForbidden()
# else:
# post = Post(owner_user=request.user)
if request.POST:
form = PostEditForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('')
else:
form = PostEditForm()
return (request,'edit_post.html', {
'form': form})
And
def view_post(request, id):
#context = RequestContext(request)
if request.method == 'POST':
form = CommentForm(request.POST)
if form.is_valid():
form = form.save(commit=False)
form.owner = request.user
form.parent_post = get_object_or_404(Post, id=id)
form.comment_type = 0
form.save()
return HttpResponseRedirect('')
else:
form = CommentForm()
return render(request,'view_post.html', {
#'comment' : get_object_or_404(Comment, parent_post = id),
'comment': Comment.objects.filter(parent_post=id),
'form': form,
'post': get_object_or_404(Post, id=id)
})
Well, I'll recommend you begin using Class Based Views, besides, why not. Can make your life easier, most times, whiles you write less
class UpdatePost(UpdateView):
model = Post # assuming you've made from your_app.models import Post call already
fields = ['title', 'content', 'pub_date'] # for demonstration purposes
template_name = 'post_form.html'
#method_decorator(login_required) #Use login required on your dispatch method
def dispatch(self, request, *args, **kwargs):
pulled = Post.objects.get(id=kwargs['id'])
if pulled.created_by == request.user: #just to verify person making edit is rightly disposed to do so
return super(UpdatePost, self).dispatch(request, *args, **kwargs)
raise PermissionDenied
Sweetly intercept the id in your url like so
url(r'^app/edit/(?P<id>\d+)/$', views.UpdatePost.as_view(), name='edit'),
That's how I do mine. I pulled this from my working project github.com/seanmavley/menpha.git, but haven't tested this tweaked version. But It should work.

Django - Catch argument in Class based FormView

On my page, i need to display the post detail and a comment form for viewer to post comment. I created 2 generic views:
# views.py
class PostDetailView (DetailView):
model = Post
context_object_name = 'post'
template_name = 'post.html'
def get_context_data(self, **kwargs):
context = super(PostDetailView, self).get_context_data(**kwargs)
context['comment_form'] = CommentForm()
return context
class AddCommentView(FormView):
template_name = 'post.html'
form_class = CommentForm
success_url = '/'
def form_valid(self, form):
form.save()
return super(AddCommentView, self).form_valid(form)
def form_invalid(self, form):
return self.render_to_response(self.get_context_data(form=form))
detail = PostDetailView.as_view()
add_comment = AddCommentView.as_view()
# urls.py
....
url(r'^(?P<pk>\d+)/$', view='detail'),
url(r'^(?P<post_id>\d+)/add_comment/$', view='add_comment'),
....
Error would occur in the AddCommentView,since I haven't specified the post's id for the comment. How can I access the post_id in the AddCommentView?
self.kwargs['post_id'] or self.args[0] contains that value
Docs

Categories

Resources