Django Error from views when submitting Comment Button - django

Helloo,
I have created a comment button for my blog posts and I am getting "This page isn’t working" after submitting the comment button and I don't know the reason.
I can add comments from the admin but can not submit as a user from website
I am not sure what needs to be changed in the views.py
class PostDetailView(DetailView):
model = Post
template_name = "post_detail.html"
def get_context_data(self, *args, **kwargs):
context = super(PostDetailView, self).get_context_data()
post = get_object_or_404(Post, id=self.kwargs['pk'])
comments = Comment.objects.filter(post=post).order_by('-id')
total_likes = post.total_likes()
liked = False
if post.likes.filter(id=self.request.user.id).exists():
liked = True
if self.request.method == 'POST':
comment_form = CommentForm(self.request.POST or None)
if comment_form.is_valid():
content = self.request.POST.get('content')
comment = Comment.objects.create(
post=post, user=request.user, content=content)
comment.save()
return HttpResponseRedirect(post.get_absolute_url())
else:
comment_form = CommentForm()
context["total_likes"] = total_likes
context["liked"] = liked
context["comments"] = comments
context["comment_form"] = comment_form
return context
class PostListView(ListView):
model = Post
template_name = "score.html"
ordering = ['-date_posted']
context_object_name = 'posts'
paginate_by = 5
here is the template
<form method="post" class="comment-form" action=".">
{% csrf_token %}
{{ comment_form.as_p }}
{% if request.user.is_authenticated %}
<input type="submit" value="Submit" class="btn btn-outline-success">
{% else %}
<input type="submit" value="Submit" class="btn btn-outline-success" disabled> You must be Logged in to Comment
{% endif %}
</form>
here is the models.py
class Post(models.Model):
designer = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
likes = models.ManyToManyField(
User, related_name='liked')
slug = models.SlugField(blank=True, null=True, max_length=120)
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.title)
super(Post, self).save(*args, **kwargs)
def get_absolute_url(self):
return reverse("score:post-detail", kwargs={'slug': self.slug})
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
# reply = models.ForeignKey('Comment', null=True, related_name="replies")
content = models.TextField(max_length=160)
timestamp = models.DateTimeField(auto_now_add=True)
def __str__(self):
return '{}-{}'.format(self.post.title, str(self.user.username))

You are using parent class DetailView for your PostDetailView. However, there is no post method defined in DetailView. I believe when you submit your comment you are getting HTTP 405 method not allowed. You can check it from browsers developer tools.
I advice you to use a separate view for saving your comments.
class PostDetailView(DetailView):
model = Post
template_name = "post_detail.html"
def get_context_data(self, *args, **kwargs):
context = super(PostDetailView, self).get_context_data()
post = get_object_or_404(Post, id=self.kwargs['pk'])
comments = Comment.objects.filter(post=post).order_by('-id')
total_likes = post.total_likes()
liked = False
if post.likes.filter(id=self.request.user.id).exists():
liked = True
comment_form = CommentForm()
context["total_likes"] = total_likes
context["liked"] = liked
context["comments"] = comments
context["comment_form"] = comment_form
return context
class PostCommentCreateView(CreateView):
model = Comment
fields = ['content']
def get(self, request, post_id, *args, **kwargs):
raise Http404
def post(self, request, post_id, *args, **kwargs):
self.post = Post.objects.get_or_404(id=post_id)
self.user = request.user
super().post(request, *args, **kwargs)
def form_valid(self, form):
form.instance.post = self.post
form.instance.user = self.user
return super(StudentCreateView, self).form_valid(form)
You need to provide action to your form:
<form action={% url 'post-comment' Post.id %} method="post" class="comment-form" action=".">
{% csrf_token %}
{{ comment_form.as_p }}
{% if request.user.is_authenticated %}
<input type="submit" value="Submit" class="btn btn-outline-success">
{% else %}
<input type="submit" value="Submit" class="btn btn-outline-success" disabled> You must be Logged in to Comment
{% endif %}
</form>
To make your create view to work you need to add the following line to your urls:
path('post/<int:post_id>/comment', views.PostCommentCreateView.as_view(), name='post-comment'),
you need to add new url for the comment page with the post-comment name as well. And some additional imports.

I think you are missing a total_likes() method in Post model:
class Post(models.Model):
# ...
def total_likes(self):
return self.likes.all().count()
Alternatively, you can change the view:
class PostDetailView(DetailView):
def get_context_data(self, *args, **kwargs):
# ...
total_likes = post.likes.all().count()
# ...

Related

Django - User not passing through form

I am making an auction site and I try passing the user who posted a bid on a listing through a form. I already asked this about passing the creator of a listing and I tried the same method but I cannot manage to do it.
My view looks something like this (I shortened it because it's very long):
def show_listing(request, listing_id):
listing = AuctionListing.objects.get(id=listing_id)
bidding = listing.bidlisting.last()
if bidding is None:
field_name = "starting_bid"
starting_bid = getattr(listing, field_name)
createbidform = CreateBid(initial={"bids": starting_bid, "user":request.user})
else:
field_name2 = "bids"
highest_bid = getattr(bidding, field_name2)
createbidform = CreateBid(initial={"bids": highest_bid, "user":request.user})
if request.method == "POST":
form = CreateBid(request.POST)
if bidding is None and float(form['bids'].value()) >= float(starting_bid):
if form.is_valid():
message = "Your bid is placed"
form.instance.listing = listing
form.save()
createbidform = CreateBid(initial={"bids": form['bids'].value(), "user":request.user})
amount_bids = len(Bid.objects.filter(listing=listing_id))
return render(request, "auctions/listing.html", {
"createbidform" : createbidform
})
else:
print(form.errors)
return render(request, "auctions/listing.html", {
"bidform" : createbidform
})
listing.html looks something like this:
<form method="POST">
{% csrf_token %}
${{ bidform.bids }}
<button type="submit" name="bidbid" class="btn btn-primary save btn-sm">Place your bid</button>
</form>
RIGHT NOW form.errors prints:
<ul class="errorlist"><li>user<ul class="errorlist"><li>This field is required.</li></ul></li></ul>
Here is the model Bid:
class Bid(models.Model):
listing = models.ForeignKey(AuctionListing, on_delete=models.CASCADE, related_name="bidlisting")
bids = models.DecimalField(max_digits=6, decimal_places=2)
user = models.ForeignKey(User, on_delete=models.CASCADE, db_constraint=False, related_name="userrr")
def __str__(self):
return str(self.bids)
And here is the form CreateBid:
class CreateBid(forms.ModelForm):
class Meta:
model = Bid
fields = ('bids', 'user')
widgets = {
'user': forms.HiddenInput(),
}
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
super().__init__(*args, **kwargs)
self.fields['user'].initial = user.id
For some reason it doesn't provide the user who posted the bidding to the form, causing the form to not be valid. How to fix this?
You already do it with bids so you don't need extra kwarg:
createbidform = CreateBid(initial={'bids': starting_bid, 'user': request.user})
you can remove __init__ method form CreateBid form class
Template:
<form method="POST">
{% csrf_token %}
${{ bidform }}
<button type="submit" name="bidbid" class="btn btn-primary save btn-sm">Place your bid</button>
</form>

Inlineformset_factory saving parent without child and not displaying validation errors if child is none

I am having 2 issues, one if you submit and click back and then submit again it duplicates the instance in the database - in this case Household. In addition it is saving the parent 'Household' without the child 'Applicants' despite me setting min_num=1
can someone point me in the right direction to resolve this issue.
Many thanks in advance
class Application(models.Model):
name = models.CharField(max_length=100, blank=True, null=True)
application_no = models.CharField(max_length=100, unique=True, default=create_application_no)
created_date = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE
)
class HouseHold(models.Model):
name = models.CharField(max_length=100)
application = models.ForeignKey(Application, on_delete=models.CASCADE)
no_of_dependents = models.PositiveIntegerField(default=0)
class Applicant(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
household = models.ForeignKey("HouseHold", on_delete=models.CASCADE)
forms.py
class ApplicationForm(ModelForm):
class Meta:
model = Application
fields = (
"name",
)
class ApplicantForm(ModelForm):
class Meta:
model = Applicant
fields = [
"household",
"first_name",
"last_name"
]
class HouseHoldForm(ModelForm):
class Meta:
model = HouseHold
fields = [
'name',
'application',
'no_of_dependents'
]
def __init__(self, application_id=None, *args, **kwargs):
super(HouseHoldForm, self).__init__(*args, **kwargs)
self.fields['name'].label = 'House Hold Name'
if application_id:
self.fields['application'].initial = application_id
self.fields['application'].widget = HiddenInput()
ApplicantFormset = inlineformset_factory(
HouseHold, Applicant, fields=('household', 'first_name', 'last_name'), can_delete=False, extra=1, validate_min=True, min_num=1)
views.py
class HouseHoldCreateView(LoginRequiredMixin, generic.CreateView):
model = models.HouseHold
template_name = "households/household_create.html"
form_class = HouseHoldForm
def get_parent_model(self):
application = self.kwargs.get('application_pk')
return application
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if self.request.POST:
context['application'] = models.HouseHold.objects.filter(application_id=self.kwargs['application_pk']).last()
context['house_hold_formset'] = ApplicantFormset(self.request.POST, instance=self.object)
else:
context['application'] = models.Application.objects.get(id=self.kwargs['application_pk'])
context['house_hold_formset'] = ApplicantFormset()
return context
def get_form_kwargs(self):
kwargs = super(HouseHoldCreateView, self).get_form_kwargs()
print(kwargs)
kwargs['application_id'] = self.kwargs.get('application_pk')
return kwargs
def form_valid(self, form):
context = self.get_context_data()
applicants = context['house_hold_formset']
with transaction.atomic():
self.object = form.save()
if applicants.is_valid():
applicants.instance = self.object
applicants.save()
return super(HouseHoldCreateView, self).form_valid(form)
def get_success_url(self):
if 'addMoreApplicants' in self.request.POST:
return reverse('service:household-create', kwargs={'application_pk': self.object.application.id})
return reverse('service:household-list', kwargs={'application_pk': self.object.application.id})
I had a similar problem, I solved it by adding the post() method to the view. The example is an UpdateView but the usage is the same.
(the indentation is not correct but that's what stackoverflow's editor let me do, imagine all methods are 4 spaces to the right)
class LearnerUpdateView(LearnerProfileMixin, UpdateView):
model = User
form_class = UserForm
formset_class = LearnerFormSet
template_name = "formset_edit_learner.html"
success_url = reverse_lazy('home')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
learner = User.objects.get(learner=self.request.user.learner)
formset = LearnerFormSet(instance=learner)
context["learner_formset"] = formset
return context
def get_object(self, queryset=None):
user = self.request.user
return user
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form_class = self.get_form_class()
form = self.get_form(form_class)
user = User.objects.get(learner=self.get_object().learner)
formsets = LearnerFormSet(self.request.POST, request.FILES, instance=user)
if form.is_valid():
for fs in formsets:
if fs.is_valid():
# Messages test start
messages.success(request, "Profile updated successfully!")
# Messages test end
fs.save()
else:
messages.error(request, "It didn't save!")
return self.form_valid(form)
return self.form_invalid(form)
Keep in mind that to save the formset correctly you have to do some heavy lifting in the template as well. I'm referring to the hidden fields which can mess up the validation process. Here's the template corresponding to the view posted above:
<form action="" method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ form|crispy }}
{{ learner_formset.management_form}}
{% for form in learner_formset %}
{% if forloop.first %}
{% comment %} This makes it so that it doesnt show the annoying DELETE checkbox {% endcomment %}
{% for field in form.visible_fields %}
{% if field.name != 'DELETE' %}
<label for="{{ field.name }}">{{ field.label|capfirst }}</label>
<div id="{{ field.name }}" class="form-group">
{{ field }}
{{ field.errors.as_ul }}
</div>
{% endif %}
{% endfor %}
{% endif %}
{% for field in form.visible_fields %}
{% if field.name == 'DELETE' %}
{{ field.as_hidden }}
{% else %}
{# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{% endif %}
{% endfor %}
{% endfor %}
<input class="btn btn-success" type="submit" value="Update"/>
Additional reading :
https://medium.com/#adandan01/django-inline-formsets-example-mybook-420cc4b6225d
Save formset in an UpdateView
Inspired by Beikini
I have solved it using the create View
class HouseHoldCreateView(LoginRequiredMixin, generic.CreateView):
model = HouseHold
template_name = "households/household_create3.html"
form_class = HouseHoldForm
def get_parent_model(self):
application = self.kwargs.get('application_pk')
return application
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if self.request.POST:
context['application'] = HouseHold.objects.filter(
application_id=self.kwargs['application_pk']).last()
context['house_hold_formset'] = ApplicantFormset(self.request.POST)
else:
context['application'] = Application.objects.get(id=self.kwargs['application_pk'])
context['house_hold_formset'] = ApplicantFormset()
return context
def get_form_kwargs(self):
kwargs = super(HouseHoldCreateView, self).get_form_kwargs()
kwargs['application_id'] = self.kwargs.get('application_pk')
return kwargs
def form_valid(self, form):
context = self.get_context_data()
applicants = context['house_hold_formset']
application_id = self.kwargs['application_pk']
household_form = self.get_form()
if form.is_valid() and applicants.is_valid():
with transaction.atomic():
self.object = form.save()
applicants.instance = self.object
applicants.save()
messages.success(self.request, 'Applicant saved successfully')
return super(HouseHoldCreateView, self).form_valid(form)
else:
messages.error(self.request, 'please add an applicant to the household')
return self.form_invalid(form)
def get_success_url(self):
return reverse('service:household-list', kwargs={'application_pk': self.object.application.id})

update formset with class based view

i've created web blog with django 2.2 each post has multiple images , but when i try to update the post
images wont updated
i use class based view
class Post(models.Model):
user= models.ForeignKey(Account,on_delete=models.CASCADE)
title= models.CharField(max_length=100)
#others
class PostImage(models.Model):
post= models.ForeignKey(Post,on_delete=models.CASCADE,related_name='images')
media_files = models.FileField(upload_to=random_url)
and this my forms.py
class PostImageForm(forms.ModelForm):
class Meta:
model = PostImage
fields = [
'media_files'
]
class PostUpdateForm(forms.ModelForm):
class Meta:
model = Post
fields = [
'title','description',#and others
]
my views.py
PostImageFormSet = inlineformset_factory(
Post,PostImage,form=PostImageForm,extra=1,can_delete=True,can_order=False
)
class PostUpdateView(LoginRequiredMixin,UserPassesTestMixin,UpdateView):
model = Post
form_class = PostUpdateForm
template_name = 'posts/update_post.html'
def get_context_data(self,**kwargs):
data = super().get_context_data(**kwargs)
if self.request.POST:
data['images'] = PostImageFormSet(self.request.POST or None,self.request.FILES,instance=self.object)
else:
data['images'] = PostImageFormSet(instance=self.object)
return data
def form_valid(self,form):
context = self.get_context_data()
images = context['images']
with transaction.atomic():
if form.is_valid() and images.is_valid():
self.object = form.save()
images.instance = self.object
images.save()
return super().form_valid(form)
def test_func(self):
post = self.get_object()
if self.request.user.username == post.user.username:
return True
return False
def get_success_url(self):
return reverse_lazy('post:post-detail',kwargs={'slug':self.object.slug})
my templates
<form enctype="multipart/form-data" method="post" action="">
{% csrf_token %}
{{images.management_form }}
{{ form|crispy }}
{% for img in images %}
<label>{{img.media_files.label}}</label>
{{img.media_files}}
{% endfor %}
<button type="submit" class="btn btn-block btn-primary">update</button>
</form>
i'm wondering why didnt update the posts image !?
thanks for replay ..

Showing user's comments on posts in Django

I am adding a comment section for my posts in a blog, I reached to the part where I can add new comments and get it saved in the db and I can view them in the admin, but I am stuck to showing the username and the comment in the comment section, I have to go the admin and choose the name of the user and the blog name to appear in the page, how do i link them together
Here is the Models.py
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, null=True, blank=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
content = models.TextField(max_length=160)
timestamp = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.content
Here is the views.py:
class PostDetailView(DetailView):
model = Post
template_name = "post_detail.html"
def get_context_data(self, *args, **kwargs):
context = super(PostDetailView, self).get_context_data()
post = get_object_or_404(Post, slug=self.kwargs['slug'])
comments = Comment.objects.filter(post=post).order_by('-id')
total_likes = post.total_likes()
liked = False
if post.likes.filter(id=self.request.user.id).exists():
liked = True
if self.request.method == 'POST':
comment_form = CommentForm(self.request.POST or None)
if comment_form.is_valid():
content = self.request.POST.get('content')
comment = Comment.objects.create(
post=post, user=request.user, content=content)
comment.save()
return HttpResponseRedirect("post_detail.html")
else:
comment_form = CommentForm()
context["total_likes"] = total_likes
context["liked"] = liked
context["comments"] = comments
context["comment_form"] = comment_form
return context
class PostCommentCreateView(LoginRequiredMixin, CreateView):
model = Comment
fields = ['content', ]
success_url = reverse_lazy('score:post-detail')
def post(self, request, *args, **kwargs):
form = CommentForm(request.POST)
if form.is_valid():
post = form.save()
post.save()
print(args, kwargs, request.POST)
return redirect('score:post-detail', slug=kwargs['slug'])
here is the template
<form action={% url 'score:post-comment' post.slug %} method="post" class="comment-form" action=".">
{% comment %} <form method="post" class="comment-form" action="."> {% endcomment %}
{% csrf_token %}
{{ comment_form.as_p }}
{% if request.user.is_authenticated %}
<input type="submit" value="Submit" class="btn btn-outline-success">
{% else %}
<input type="submit" value="Submit" class="btn btn-outline-success" disabled> You must be Logged in to Comment
{% endif %}
</form>
here is the form
class CommentForm(forms.ModelForm):
content = forms.CharField(label="", widget=forms.Textarea(
attrs={'class': 'form-control', 'placeholder': 'Text goes here!!!', 'rows': '4', 'cols': '50'}))
class Meta:
model = Comment
fields = ('content',)
A CreateView is defined to remove most of the boilplate code, so you should not reimplement that in the post method, but let the CreateView do its work.
What you here should do is override the .form_valid(…) method [Django-doc] and the .form_invalid(…) method [Django-doc], and the .get_success_url(…) method [Django-doc] to redirect to the proper view:
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.urls import reverse
class PostCommentCreateView(LoginRequiredMixin, CreateView):
model = Comment
form_class = CommentForm
def form_valid(self, form):
post = get_object_or_404(Post, slug=self.kwargs['slug'])
form.instance.user = self.request.user
form.instance.post = post
return super().form_valid(form)
def form_invalid(self, form):
return HttpResponseRedirect(self.get_success_url())
def get_success_url(self):
return reverse('score:post-detail', kwargs=dict(slug=self.kwargs['slug']))

Django web app not redirecting and not posting

I an not sure where is the problem as I press submit button, it went through with no error shown. It was supposed to update the database with the data filled in form then redirect them back to 'search' page (option.html).
models.py:
OptionChoice = (
('A','A'),
('B','B'),
('C','C'),
)
class OptionPlan(models.Model):
option = models.CharField(max_length=200, choices=OptionChoice, default="DEFAULT", blank=True)
...
updated = models.DateField(max_length=20, null=True)
updatedBy = models.CharField(max_length=10, null=True)
urls.py:
app_name = 'Benefits'
urlpatterns = [
path('simple_upload', views.simple_upload, name='simple_upload'),
#path('search', views.search, name='search'),
path('search/', FilterView.as_view(filterset_class=BenefitsFilter, template_name='Benefits/option.html'), name='search'),
path('OptionUpdate/<int:id>', views.OptionUpdate.as_view(), name='OptionUpdate')
]
views.py:
def search(request):
option = OptionPlan.objects.get_queryset()
option_filter = BenefitsFilter(request.GET, queryset=option)
return render(request, 'Benefits/option.html', {'filter':option_filter})
class OptionUpdate(UpdateView):
model = OptionPlan
fields =[
'option',
...
'cb_updatedBy',
'cb_updated',
]
template_name = 'Benefits/OptionUpdate.html'
slug_field = 'id'
slug_url_kwarg = 'id'
def form_valid(self, request, obj, form, change):
OptionPlan = form.save(commit=False)
if OptionPlan.option and 'option' in form.changed_data:
OptionPlan.updatedBy = str(self.request.user)
OptionPlan.updated = timezone.localtime(timezone.now())
OptionPlan.save()
return redirect('Benefits:search')
optionUpdate.html:
{% if user.is_authenticated %}
<div style="margin-left:100px;margin-right:100px;">
<form method="POST">
<div class="row">
<div class="col">
<h2 class="alert alert-primary">...</h2>
{% csrf_token %}
<div class="row">
<div class="col-4" style="font-size:30px;">
{{ form.option|as_crispy_field }}
</div>
</div>
...
<div class="col">
<h2 class="alert alert-success">...</h2>
...
<div class="col" style="font-size:30px;">
{{ form.cb_remarks|as_crispy_field }}
</div>
</div>
<button type="submit" class="btn btn-primary btn-block">2020 Option Form Update</button>
</div>
</form>
</div>
{% else %}
Thank you so much for helping!
Edit:
What version of Django are you using? A cursory look at generic UpdateView* from Django v1.3 through v2.2 use this same post method:
def post(self, request, *args, **kwargs):
"""
Handle POST requests: instantiate a form instance with the passed
POST variables and then check if it's valid.
"""
form = self.get_form()
if form.is_valid(): <--- you passed here so your form is valid
return self.form_valid(form) <-- you overwrote this method (problem area)
else:
return self.form_invalid(form)
*get familiar with that website if you are using class based views, its a life saver.
I am not sure how your form_valid method is being called with four variables: request, obj, form, change when it only expects one variable.
I typically split this logic apart and would make a separate form:
forms.py
class OptionPlanUpdateForm(forms.ModelForm):
class Meta:
model = OptionPlan
fields = [
'option',
'cb_updatedBy',
...
'cb_updated',
]
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
def save(self, **kwargs):
option_plan = super().save(commit=False)
if 'option' in self.changed_data:
option_plan.updatedBy = str(self.user)
option_plan.updated = timezone.localtime(timezone.now())
option_plan.save()
return option_plan
Since we have a form we have to update our view to handle the form. We pass the user to the form through the get_form_kwargs method.
class OptionUpdate(UpdateView):
form_class = OptionPlanUpdateForm
model = OptionPlan
slug_field = 'id'
slug_url_kwarg = 'id'
template_name = 'Benefits/OptionUpdate.html'
success_url = reverse_lazy('Benefits:search')
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
***To test for errors in your form you can do the following:
def post(self, request, *args, **kwargs):
form = self.form_class(self.request.POST)
print(form.errors)
return super().post(request, *args, **kwargs)
If I were to assume, the form is throwing some type of error that you aren't printing nor handling. Try checking if any form errors exists to begin with.