Why doesn't {% if %} {% else %} construction work in Django template? - django

So I am trying to display "YOU" above the comment if it's in fact a comment that has been posted by current user, otherwise I am trying to display just a username of one who left a comment.
But somehow if/else doesn't work. Can anyone please tell me what am I doing wrong?
Thank you beforehand!
my models.py
class Comments(models.Model):
commented_by = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
comment = models.TextField(max_length=300)
def __str__(self):
return self.comment
my forms.py
class CommentForm(forms.ModelForm):
class Meta:
model = Comments
fields = ['comment']
widgets = {'comment': forms.Textarea(attrs={'class': 'form-control', 'rows': 5})}
my views.py
class Comment(CreateView, LoginRequiredMixin):
form_class = CommentForm
template_name = 'app/comments.html'
login_url = 'login'
success_url = reverse_lazy('comments')
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.commented_by = self.request.user
self.object.save()
if self.object:
messages.success(self.request, 'Success')
else:
messages.error(self.request, 'Error')
return redirect(self.get_success_url())
def get_initial(self):
initial = super().get_initial()
initial['comment'] = 'Please leave your comment here'
return initial
def get_context_data(self, **kwargs):
context = super().get_context_data()
context['comments'] = Comments.objects.all()
return context
and my condition from the template:
<div>
<div class="commentsContainer">
{% for comment in comments %}
{% if comment.commented_by == user.username %}
<h4>YOU</h4>
{% else %}
{{comment.commented_by}}
{% endif %}
<div class="comment">
{{comment}}
</div>
{% endfor %}
</div>

comment.commented_by will give you an instance of the user model, whereas user.username will give you a string which is the user's username. Hence comment.commented_by == user.username will always give you False. Instead you should write:
{% if comment.commented_by == user %}
<h4>YOU</h4>
{% else %}
{{ comment.commented_by }}
{% endif %}

Related

Django how to add a form to a DetailView with FormMixin

I am attempting to add a form for comments to a DetailView. The DetailView displays notes for specific projects. So the comments have a foreign key that is the specific note and the note has a foreign key for the specific project.
I am attempting to use FormMixin with DetailView. So far I have not bee successful. Currently I can get the form to display but it does not save and in the terminal I see the following error Method Not Allowed (POST): /projects/project/1/note/1/
I can get these to work separately but not with the form in the DetailView.
Here are my models:
class ProjectNotes(models.Model):
title = models.CharField(max_length=200)
body = tinymce_models.HTMLField()
date = models.DateField(auto_now_add=True)
project = models.ForeignKey(Project, default=0, blank=True, on_delete=models.CASCADE, related_name='notes')
def __str__(self):
return self.title
class ProjectNoteComments(models.Model):
body = tinymce_models.HTMLField()
date = models.DateField(auto_now_add=True)
projectnote = models.ForeignKey(ProjectNotes, default=0, blank=True, on_delete=models.CASCADE, related_name='comments')
The View:
class ProjectNotesDetailView(DetailView, FormMixin):
model = ProjectNotes
id = ProjectNotes.objects.only('id')
template_name = 'company_accounts/project_note_detail.html'
comments = ProjectNotes.comments
form_class = NoteCommentForm
def form_valid(self, form):
projectnote = get_object_or_404(ProjectNotes, id=self.kwargs.get('pk'))
comment = form.save(commit=False)
comment.projectnote = projectnote
comment.save()
return super().form_valid(form)
def get_success_url(self):
return reverse('project_detail', args=[self.kwargs.get('pk')])
The form:
class NoteCommentForm(forms.ModelForm):
class Meta:
model = ProjectNoteComments
fields =['body',]
widgets = {
'body': forms.TextInput(attrs={'class': 'form-control'})
}
The template:
% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block content %}
<div class="section-container container">
<div class="project-entry">
<h2>{{ projectnotes.title }}</h2>
<p>{{ projectnotes.body | safe }}</p>
</div>
<div><b>Comments on {{projectnotes.title}}</b></div>
{% if projectnotes.comments.all %}
{% for comment in projectnotes.comments.all %}
<div class="notecomments" style="padding: 10px;">
{{ comment.body | safe }}
</div>
{% endfor %}
{% else %}
<p>No comments have been have been added yet.</p>
{% endif %}
<h2>add note</h2>
<h1>Add Comment</h1>
<form action="" method="post">
{% csrf_token %}
{{ form.media }}
{{ form|crispy }}
<input type="submit" value="save">
</form>
{% endblock content %}
Try to change the order between DetailView and FormMixin in ProjectNotesDetailView then implement the post method (enabled by the `FormMixin):
class ProjectNotesDetailView(FormMixin, DetailView):
model = ProjectNotes
id = ProjectNotes.objects.only('id')
template_name = 'company_accounts/project_note_detail.html'
comments = ProjectNotes.comments
form_class = NoteCommentForm
def form_valid(self, form):
projectnote = get_object_or_404(ProjectNotes, id=self.kwargs.get('pk'))
comment = form.save(commit=False)
comment.projectnote = projectnote
comment.save()
return super().form_valid(form)
def get_success_url(self):
return reverse('project_detail', args=[self.kwargs.get('pk')])
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden()
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
Check how to use formmixin with detailview (documentation).

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})

Combining FormView and DetailView doesn't show form on the page

I have a DetailView, in which I show contents of a post and as well I wanted to add comments functionality to that view. I found 2 ways to do it: combine a DetailView and FormView or make a custom view with mixins. Since I am new to Djanfgo, I went on the 1st way, guided by this answer: Django combine DetailView and FormView but i have only a submit button and no fields to fill on a page.
Here is a necessary code:
#urls.py
from . import views
app_name = 'bankofideas'
urlpatterns = [
path('<int:pk>/', views.DetailView.as_view(), name='idea'),
path('<int:idea_id>/vote', views.vote, name='vote'),
path('<formview', views.MyFormView.as_view(), name='myform')
]
#views.py
class DetailView(generic.DetailView):
model = Idea
template_name = 'bankofideas/detail.html'
context_object_name = 'latest_question_list'
def get_queryset(self):
return Idea.objects.filter(pub_date__lte=timezone.now())
def get_context_data(self, **kwargs): # передача формы
context = super(DetailView, self).get_context_data(**kwargs)
context['comment_form'] = CommentForm#(initial={'post':
self.object.pk})
return context
class MyFormView(generic.FormView):
template_name = 'bankofideas/detail.html'
form_class = CommentForm
success_url = 'bankofideas:home'
def get_success_url(self):
post = self.request.POST['post']
Comment.objects.create()
return '/'
def form_valid(self, form):
return super().form_valid(form)
#models.py
class Idea(models.Model):
main_text = models.CharField(max_length=9001, default='')
likes = models.IntegerField(default=0)
pub_date = models.DateTimeField('date published',
default=timezone.now)
def __str__(self):
return self.main_text
def save(self, *args, **kwargs):
''' On save, update timestamps '''
if not self.id:
self.pub_date = timezone.now()
#self.modified = timezone.now()
return super(Idea, self).save(*args, **kwargs)
class Comment(models.Model):
post = models.ForeignKey(Idea, on_delete=models.PROTECT) #
related_name='comments',
user = models.CharField(max_length=250)
body = models.TextField(default=' ')
created = models.DateTimeField(default=timezone.now)
def __str__(self):
return self.body
def get_absolute_url(self):
return reverse('post_detail', args=[self.post.pk])
#forms.py
from .models import Idea, Comment
from django import forms
class IdeaForm(forms.ModelForm):
class Meta:
model = Idea
fields = ('main_text',)
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
exclude = ['post', 'datetime']
#detail.html
{% extends 'base.html' %}
{% load bootstrap3 %}
{% block body %}
<div class="container">
<p>{{ idea.main_text }} {{ idea.likes }} like(s)</p>
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
<form action="{% url 'bankofideas:vote' idea.id %}" method="post">
{% csrf_token %}
<input type="submit" name="likes" id="idea.id" value="{{ idea.likes }}" />
</form>
{% for comment in idea.comment_set.all %}
<p>{{comment.body}}</p>
{% empty %}
<p>No comments</p>
{% endfor %}
<form action="{% url "bankofideas:myform" %}" method="post">
{% csrf_token %}
{{ myform. }}
<input type='submit' value="Отправить" class="btn btn-default"/>
</form>
</div>
{% endblock %}
As a result, I have an ability to see post, read all comments, like it, but cannot leave a comment. I tried to rename the form both in view and template, but it didn't work.
The question is: What should i do to bring comment form on the page?

What is the best way to implement Query Form in Django ListView?

Last week I faced a task related to listView and Forms in Django, I was wondering what is the best way (+Pythonic) to implement a search form inside a ListView, after I read 1 and 2 I got a main idea so I implemented a first solution and I would like to receive your Feedback. The goal here is to perform query by code field and keep the queryset in order to synchronize it with the pagination.
forms.py
class InscriptionQueryForm(forms.Form):
query_inscription = forms.CharField(label=_('Code'), required=False)
models.py
class Inscription(models.Model):
code = models.CharField(max_length=10, unique=True)
start_on = models.DateField()
finish_on = models.DateField()
active = models.BooleanField(default=False)
views.py
class InscriptionListView(ListView, FormMixin):
model = Inscription
paginate_by = 4
context_object_name = 'inscriptions'
form_class = InscriptionQueryForm
form = None
object_list = None
search = False
def get_queryset(self):
form = self.form_class(self.request.POST)
if form.is_valid() and self.request.method == 'POST':
self.request.session['query_inscription'] = \
form.cleaned_data['query_inscription']
return self.model.objects.filter(
code__icontains=form.cleaned_data['query_inscription']).\
order_by('-active')
if self.request.method == 'GET' and \
'query_inscription' in self.request.session:
return self.model.objects.filter(
code__icontains=self.request.session.get(
'query_inscription', '')).order_by('-active')
return self.model.objects.all().order_by('-active')
def get(self, request, *args, **kwargs):
# From ProcessFormMixin
self.form = self.get_form(self.form_class)
# From BaseListView
if self.request.GET.get('page', False) or self.search:
self.object_list = self.get_queryset()
else:
self.search = False
self.object_list = self.model.objects.all().order_by('-active')
if 'query_inscription' in self.request.session:
del self.request.session['query_inscription']
context = self.get_context_data(
object_list=self.object_list, form=self.form)
return self.render_to_response(context)
def post(self, request, *args, **kwargs):
self.search = True
return self.get(request, *args, **kwargs)
What do you think guys?, I'm sure there are many others better ways.
Last week I faced the similar problem. My model was a common django User. However the same approach can be used here.
I guess you want to search through your Inscriptions using search field and paginating your results. And when you go through the pages you expect to see the results of your search query.
Your models.py stays as it is. We are going to modify forms.py as it will be able to initialize the search request
class InscriptionQueryForm(forms.Form):
query_inscription = forms.CharField(label='Code', required=False)
def __init__(self, query_inscription):
super(InscriptionQueryForm, self).__init__()
self.fields['query_inscription'].initial = query_inscription
Now let's look at the views.py. You don't need to store your request value in the session as schillingt mentioned in his comment. All you need is to init your form with the search request.
class InscriptionListView(ListView):
model = Inscription
paginate_by = 4
context_object_name = 'inscriptions'
query_inscription = ''
def get_context_data(self, **kwargs):
context = super(InscriptionListView, self).get_context_data(**kwargs)
context['form'] = InscriptionQueryForm(self.query_inscription)
# this parameter goes for the right pagination
context['search_request'] = ('query_inscription=' +
unicode(self.query_inscription))
return context
def get(self, request, *args, **kwargs):
self.query_inscription = request.GET.get('query_inscription', '')
return super(InscriptionListView, self).get(request, *args, **kwargs)
def get_queryset(self):
if not self.query_inscription:
inscription_list = Inscription.objects.all()
else:
inscription_list = Inscription.objects.filter(
code__icontains=self.query_inscription)
return inscription_list.order_by('-active')
And the last thing to mention is the pagination
inscription_list.html
<form action="">
{{ form.as_p }}
<input type="submit" value="search"/>
</form>
<hr/>
{% if inscriptions %}
{% for inscription in inscriptions %}
{{ inscription.code }} {{ inscription.start_on }} {{ inscription.finish_on }}
<hr/>
{% endfor %}
{% if is_paginated %}
<div class="pagination">
<span class="page-links">
{% if page_obj.has_previous %}
previous
{% endif %}
<span class="page-current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
</span>
{% if page_obj.has_next %}
next
{% endif %}
</span>
</div>
{% endif %}
{% endif %}
That's it!

ModelForm is not displaying entry fields

I am struggling with displaying fields in my view in Django.
The submit button appears but the fields related to models do
not.
Here is my code:
Forms.py
from django.forms import ModelForm
from blog.models import Comments
class CommentForm(ModelForm):
class Meta:
model = Comments
Models.py
class Comments(models.Model):
entries_id = models.ForeignKey(Entries)
author = models.CharField(max_length=40)
text = models.TextField()
created_on = models.DateTimeField("date published")
Views.py
class CommentListView(ListView):
model = Comments
def get_context_data(self, **kwargs):
context = super(CommentListView, self).get_context_data(**kwargs)
context['now'] = timezone.now()
return context
def add_comment(self, request, *args, **kwargs):
if request.method == 'GET':
form = CommentForm()
elif request.method == 'POST':
form = CommentForm(request.POST.copy())
if form.is_valid():
context = dict(form = form)
return render_to_response('comments_list.html', context) # etc.
The template:
{% extends 'flatpages/default.html' %}
{% block body %}
{% for Comments in object_list reversed %}
<p>{{ Comments.text }} </p></br>
<p>{{ Comments.author }}</p>
{% empty %}
<p>No comments yet</li></p>
{% endfor %}
<div="form" >
<form method = 'post' action =''> {% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit" />
</form>
</div>
{% endblock %}
I would appreciate any help. Thank you.
You are not always giving a value to form. At least, I would do that:
def get_context_data(self, **kwargs):
context = super(CommentListView, self).get_context_data(**kwargs)
context['now'] = timezone.now()
context['form'] = CommentForm()
return context