Once a user had logged into my site he could write a post and update it.
Then I was making progress in adding functionality which allowed people to make comments. I was at the stage where I could add comments from the back end and they would be accurately displayed on the front end.
Now when I try and update posts I get an error message.
I assume it is because there is a foreign key linking the comments class to the post class. I tried Googling the problem and looking on StackOverflow but I wasn't entirely convinced the material I was reading was remotely related to my problem. I am struggling to fix the issue because I barely even understand / know what the issue is.
models.py
def save(self, *args, **kwargs):
self.url= slugify(self.title)
super().save(*args, **kwargs)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('article_detail', kwargs={'slug': self.slug})
class Comment(models.Model):
post = models.ForeignKey(Post,on_delete=models.CASCADE,related_name='comments')
name = models.CharField(max_length=80)
email = models.EmailField()
body = models.TextField()
created_on = models.DateTimeField(auto_now_add=True)
active = models.BooleanField(default=False)
class Meta:
ordering = ['created_on']
def __str__(self):
return 'Comment {} by {}'.format(self.body, self.name)
def get_absolute_url(self):
return reverse('article_detail', kwargs={'slug': self.slug})
views.py
def post_detail(request, pk):
template_name = 'post_detail.html'
comments = Comment.objects.filter(post=pk ,active=True)
post = Post.objects.get(pk=pk)
new_comment = None
# Comment posted
if request.method == 'POST':
comment_form = CommentForm(data=request.POST)
if comment_form.is_valid():
# Create Comment object but don't save to database yet
new_comment = comment_form.save(commit=False)
# Assign the current post to the comment
new_comment.post = post
# Save the comment to the database
new_comment.save()
else:
comment_form = CommentForm()
return render(request, template_name, {'post': post,
'comments': comments,
'new_comment': new_comment,
'comment_form': comment_form})
You have no field in your model Comment called slug (aka SlugField) so it's not going to work.
The save method should be defined in the class also - see here. You also don't need the get_absolute_url under save() either.
Related
i am practicing CBV , so i thought to check if i can override methodes, well one of biggest problems is that idk how to use data(like data just submitted ), i wrote this code for a DetailView so i could see post and comments under it:
class ArtDetailView(FormView, DetailView):
model = Art
form_class = CommentForm
def get_context_data(self, **kwargs):
context = super(ArtDetailView, self).get_context_data(**kwargs)
context['time'] = timezone.now()
context['form'] = self.get_form()
return context
def form_valid(self, form):
form.instance.writer = self.request.user
form.instance.text = self.post
#form.instance.art = Art.objects.get(id=self.pk)
form.save()
return super().form_valid(form)
def get_success_url(self) -> str:
return reverse('pages:art_detail', args=(self.kwargs['pk'],))
forms.py:
from django import forms
from .models import Art, Comment
class CommentForm(forms.ModelForm):
class Meta():
model = Comment
fields = ['text','art']
but when i post something it is in this shape:screen_shot(2nd comment)
,i think problem is withform.instance.text = self.post but i don't know how to fix it
can you please also explain a little because all i want is to learn.
and i tried to also add art as autofill(i added as comment) but wasn't successful, can you pls check it it too.
You can overide post method to save form as well. Since you only adding form which is always a text and no possible errors in forms unless blank. You code snippet will look like this
# In forms.py
def CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('text', )
# In views.py
class ArtDetailView(FormView, DetailView):
# same as previous but remove form_valid method
def post(self, request, *args, **kwargs):
form = CommentForm(request.POST)
if form.is_valid():
form_instance = form.save(commit=False)
# this will give form instance and then add related field and save again (commit=True by default)
form_instance.writer = request.user
# since it is art detail view, self.get_object() will give art object
form_instance.art = self.get_object()
form_instance.save()
return HttpResponseRedirect(self.get_success_url())
else:
return super().form_invalid(from)
If you go to FormView declaration you will find like this
I have a method on my model to change an object from being published to unpublished. The redirect works alright but in my database, nothing happens. If the object is published it remains so with no changes to it when the button is clicked to unpublish the object(blog post article)
This is the model and the method
class Post(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True)
published = models.BooleanField(default=False, null=True, blank=True)
def unpublish(self):
self.published == False
self.save()
My view
def unpublish_post(request, slug):
post = get_object_or_404(Post, slug=slug)
post.unpublish
return redirect('dashboard')
Edit 2: My view
#require_http_methods(['POST', 'DELETE'])
def unpublish_post(request, slug):
post = get_object_or_404(Post, slug=slug)
if post.published == True:
post.unpublish()
return redirect('dashboard')
else:
return messages.warning(request, "Post already not published")
return redirect('dashboard')
My urls.py
path('unpublish-post/<slug>/', unpublish_post, name='unpublish-post'),
EDIT 1: Now I updated the view logic with:
def unpublish_post(request, slug):
post = get_object_or_404(Post, slug=slug)
if post.published == True:
post.unpublish()
return redirect('dashboard')
else:
return messages.warning(request, "Post already not published")
There are three minor mistakes:
you set a variable with a single equal sign (=), not a double equal sign (==);
you should call the .unpublish() method; and
you should only allow to access this view in a POST or DELETE request.
In your model, we thus rewrite the logic to:
class Post(models.Model):
# …
def unpublish(self):
self.published = False
self.save()
and in the view, we call the method and restrict access to a POST and/or a DELETE request:
from django.views.decorators.http import require_http_methods
#require_http_methods(['POST', 'DELETE'])
def unpublish_post(request, slug):
post = get_object_or_404(Post, slug=slug)
post.unpublish()
return redirect('dashboard')
The client thus then will need to make a POST/DELETE request, not a GET request. The template thus should look like:
<form method="POST" action="{% 'unpublish-post' post.slug %}">
<input type="submit" value="unpublish">
</form>
I am trying to create a comment system for the blog portion of my app with Django. I have attempted to mix my detail view with the form mixin and I'm struggling a bit. When the form is submitted, it doesn't save and no error is present. If any of you can help I would greatly appreciate it.
Here is my View
class DetailPostView(FormMixin, DetailView):
model = Post
template_name = "blog/post_detail.html"
context_object_name = "posts"
form_class = CommentForm
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["form"] = CommentForm
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 get_success_url(self):
return reverse("post-detail", kwargs={"pk": self.object.pk})
The model
class Comment(models.Model):
comment = models.ForeignKey(Post, on_delete=models.CASCADE)
title = models.CharField(max_length=200)
content = models.TextField()
author = models.CharField(max_length=50)
created_on = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ["-created_on"]
def __str__(self):
return self.title
The reason that this happens is because you construct a new form that you pass to the context data, as a result, it will not render any errors, since you construct a form without validating the request data and render that form, you thus do not display the form that rejected the data in the first place.
But you do not need to do that. Django's FormMixin [Django-doc] already takes care of that. You thus should not override the .get_context_data(…) method [Django-doc].
Another problem is that you did not save your form, you can override a the form_valid method, or you can inherit from ModelFormMixin [Django-doc].
Finally you better first create the form, and then assign self.object, otherwise it will pass this as an instance to the form:
from django.views.generic.edit import ModelFormMixin
class DetailPostView(ModelFormMixin, DetailView):
model = Post
template_name = 'blog/post_detail.html'
context_object_name = 'posts'
form_class = CommentForm
# no get_context_data override
def post(self, request, *args, **kwargs):
# first construct the form to avoid using it as instance
form = self.get_form()
self.object = self.get_object()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def get_success_url(self):
return reverse('post-detail', kwargs={'pk': self.object.pk})
I am learning to build a Q&A app using Django. I have set up models for posts, comments and users. I am not able to successfully link the id for the posts to the comments.
I have tried linking the comments to the posts by overriding the ModelForm on Django unsuccessfully.
This is in my views.py file:
class CommentCreateView(LoginRequiredMixin, CreateView):
model = Comment
fields = ['comment']
def form_valid(self, form):
form.instance.author = self.request.user
form.instance.post_id = self.kwargs['post_id']
return super(CommentCreateView, self).form_valid(form)
My models.py has the model for comments are follows:
class Comment(models.Model):
cid = models.AutoField(primary_key=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
post_id = models.ForeignKey(Post, on_delete=models.CASCADE)
comment = models.TextField()
comment_date = models.DateTimeField(default=timezone.now)
def save(self, *args, **kwargs):
super(Comment, self).save(*args, **kwargs)
def __str__(self):
return self.comment
def get_absolute_url(self):
return reverse('blog-home')
and my url path is as follows:
path('comment/<int:post_id>', PostCommentListView.as_view(), name='user-comments')
I expect the comments to get linked with the posts via the ForeignKey. But when I tried doing the same, I get an error as described below:
ValueError at /post/11/comment/
Cannot assign "11": "Comment.post_id" must be a "Post" instance.
Exception Value:
Cannot assign "11": "Comment.post_id" must be a "Post" instance.
The error show that it expect an Post object. Try this
form.instance.post_id = Post.objects.get(pk=self.kwargs['post_id'])
I am attempting to reuse my create form (EntryForm) for editing a model in Django. My Entry model has a unique slug that is generated on save. This works fine when creating an Entry, but shows the following error when I attempt to edit it:
Entry with this Slug already exists.
I saw several similar questions, but most were failing to set instance= when instantiating the form. I'm pretty sure I'm doing that part correctly.
I've removed other model fields from the code below for clarity.
Here is my model:
class Entry(models.Model):
title = models.CharField(max_length=128, blank=True)
slug = models.SlugField(unique=True, blank=True)
def save(self, *args, **kwargs):
if not self.title:
self.title = self.date.strftime('%B %-d, %Y')
self.slug = slugify(self.title)
super(Entry, self).save(*args, **kwargs)
My view:
def edit_entry(request, entry_slug):
entry = get_object_or_404(Entry, slug=entry_slug)
form = EntryForm(instance=entry, label_suffix='')
if request.method == 'POST':
form = EntryForm(request.POST, instance=entry, label_suffix='')
if form.is_valid():
form.save(commit=True)
return index(request)
else:
print(form.errors)
return render(request, 'journal/entry/form.html', {'form': form})
My form:
class EntryForm(forms.ModelForm):
title = forms.CharField(required=False, max_length=128, label="Title (defaults to date)")
slug = forms.CharField(widget=forms.HiddenInput(), required=False)
class Meta:
model = Entry
exclude = ()
Any ideas?
I did finally figure this out.
The issue was stemming from the fact that I was reusing my create template for the edit form, but forgot to set the form action dynamically depending on which action I desired. So, my 'edit' form was rendering correctly, but actually submitting via the 'create' action.
Thanks to those who commented and to what ultimately led me to debugging the problem, this handy code snippet equivalent to Ruby's binding.pry:
import code; code.interact(local=dict(globals(), **locals()))
I did also take #xyres's advice and remove the slug from my form, as it was unnecessary.
New form:
class EntryForm(forms.ModelForm):
title = forms.CharField(required=False, max_length=128, label="Title (defaults to date)")
class Meta:
model = Entry
exclude = ['slug']
New final line of edit_entry():
return render(request, 'journal/entry/form.html', {'form': form, 'entry': entry})
Form action:
{% if entry %}
<form id="entry_form" method="post" action="/journal/entry/{{ entry.slug }}/edit">
{% else %}
<form id="entry_form" method="post" action="/journal/new_entry/">
{% endif %}