I use Django class-based views. And I have two classes: one for displaying form in the page, and second for handling it:
views.py:
class CommentFormView(CreateView):
form_class = AddCommentForm
model = Comment
success_url = '/'
def form_valid(self, form):
form.instance.author = self.request.user
form.instance.post = ????
return super(CommentFormView, self).form_valid(form)
class BlogFullPostView(BlogBaseView, DetailView):
model = Post
template_name = 'full_post.html'
pk_url_kwarg = 'post_id'
context_object_name = 'post'
def get_context_data(self, **kwargs):
context = super(BlogFullPostView, self).get_context_data(**kwargs)
context['form'] = AddCommentForm(initial={'post': self.object})
return context
full_post.html:
<form action="/addcomment/" method="post" >
{% csrf_token %}
{{ form }}
<button type="submit" >Add comment</button>
</form>
urls:
url(r'^blog/post/(?P<post_id>\d+)/$', BlogFullPostView.as_view()),
url(r'^addcomment/$', CommentFormView.as_view()),
And in def form_valid I need to fill field post, which value I have passed in BlogFullPostView in get_context_data: initial={'post': self.object}
But how can I get it in CommentFormView?
I have solved it in this way: Firstly, I try to use get_initial method. But I it does not return anything. So, I decide to hide auto filled field post - I make it as hidden field. So, then CreateView easily can create Comment object:
widgets = {
'content': forms.Textarea(),
'post': forms.HiddenInput(),
}
Related
I need to autoincrement value in my checkbox and reset value when I generated new massive of checkbox
forloop.count dont reset
{% for ans in Answ %}
{% if ans.question_id_id == Questions.id %}
<input type="hidden" value="{{ Questions.id }}" name="id">
<div class="form-check" ><label><input type="checkbox" value="{{ ans.id }}" name="answer"> {{ ans.answer }} </label></div>
{% endif %}
{% endfor %}
views.py
class AnswerQuestionView (LoginRequiredMixin, DetailView):
login_url = '/login'
redirect_field_name = 'redirect_to'
model = Question
template_name = 'index.html'
context_object_name = 'Questions'
slug_field = 'pk'
def get_context_data(self, **kwargs):
context = super(AnswerQuestionView, self).get_context_data(**kwargs)
context['user_group'] = self.request.user.groups.values_list()[0][1]
context['Answ'] = QuestAnswer.objects.all()
return context
This is one of the many reasons why you should not do filtering in the template. Another very important one is performance: as the number of answers will grow, eventually the template rendering will take a lot of time.
You can filter in the view with:
class AnswerQuestionView(LoginRequiredMixin, DetailView):
login_url = '/login'
redirect_field_name = 'redirect_to'
model = Question
template_name = 'index.html'
context_object_name = 'question'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['user_group'] = self.request.user.groups.values_list()[0][1]
context['answers'] = QuestAnswer.objects.filter(question_id=self.object)
return context
You probably can even use the related name:
class AnswerQuestionView(LoginRequiredMixin, DetailView):
login_url = '/login'
redirect_field_name = 'redirect_to'
model = Question
template_name = 'index.html'
context_object_name = 'question'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['user_group'] = self.request.user.groups.values_list()[0][1]
context['answers'] = self.object.questionanswer_set.all()
return context
Note: Normally one does not add a suffix …_id to a ForeignKey field, since Django
will automatically add a "twin" field with an …_id suffix. Therefore it should
be question, instead of question_id.
I have a Django view and I want to send the request data to a form.
class PostDetailView(DetailView):
model = Post
form = CommentForm
def get_form_kwargs(self):
kwargs = super(PostDetailView, self).get_form_kwargs()
kwargs['user'] = self.request.user.username
return kwargs
def post(self, request, *args, **kwargs):
form = CommentForm(request.POST)
if form.is_valid():
post = self.get_object()
form.instance.user = request.user
form.instance.post = post
form.save()
return redirect(reverse("post-detail", args=[post.pk]))
from django import forms
from .models import Comment
from djrichtextfield.widgets import RichTextWidget
class CommentForm(forms.ModelForm):
user = forms.ChoiceField(required=True)
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
super(CommentForm, self).__init__(*args, **kwargs)
self.fields['user'].choices = self.user
content = forms.CharField(widget=RichTextWidget(attrs={
'class': 'md-textarea form-control',
'placeholder': 'Comment here ...',
'rows': '4',
}))
class Meta:
model = Comment
fields = ('author', 'content',)
<form method="POST" href="{% url 'post-detail' post.id %}">
{% csrf_token %}
<div class="form-group">
{{form | safe}}
</div>
<button class="btn btn-primary btn-block" type="submit">Comment</button>
</form>
urlpatterns = [
path('post/<int:pk>/', PostDetailView.as_view(), name="post-detail"),
]
According to the docs, get_form_kwargs allows key-value pairs to be set in kwargs. The kwargs is then passed into the form. The form's init function should then be able to pick up the user value from kwargs.
However, self.user returns None, and debugging showed that get_form_kwargs did not run at all.
I have two questions: how do functions in view classes get executed? And what is the correct method to pass data from a view to a form?
EDIT
I have refactored the comment feature into another view.
class AddCommentView(UpdateView):
model = Post
form = CommentForm
def post(self, request, *args, **kwargs):
form = CommentForm(request.POST)
if form.is_valid():
post = self.get_object()
form.instance.user = request.user
form.instance.post = post
form.save()
return redirect(reverse("post-detail", args=[post.pk]))
def get_form_kwargs(self):
kwargs = super(PostDetailView, self).get_form_kwargs()
kwargs.pop('instance', None)
kwargs['user'] = self.request.user.username
return kwargs
<form method="POST" href="{% url 'add-comment' post.id %}">
{% csrf_token %}
<div class="form-group">
{{form | safe}}
</div>
<button class="btn btn-primary btn-block" type="submit">Comment</button>
</form>
urlpatterns = [
path('post/<int:pk>/', AddCommentView.as_view(), name="add-comment")
]
However UpdateView cannot handle POST requests (405).
It's not explicitly described in the docs but get_form_kwargs is only triggered with a CreateView or an UpdateView.
In your case you can use UpdateView, and then use form_valid to do your post process. But note that we need to delete kwargs['instance'], because by default this view will think we are working with a Post object when in fact it's a Comment:
Try with this:
class PostDetailView(UpdateView):
model = Post
form = CommentForm
def get_form_kwargs(self):
kwargs = super(PostDetailView, self).get_form_kwargs()
# Remove the post object as instance,
# since we are working with a comment
kwargs.pop('instance', None)
kwargs['user'] = self.request.user.username
return kwargs
def form_valid(self, form):
instance = form.save(commit=False)
instance.user = self.request.user
instance.post = self.get_object() # returns the post
instance.save()
return redirect(reverse("post-detail", args=[post.pk]))
or if you don't want to use UpdateView (not recommended), you can just explicitly call get_form_kwargs when you build your form. You cannot call super().get_form_kwargs() though, since as discussed the parent class doesn't have this method:
class PostDetailView(DetailView):
model = Post
form = CommentForm
def get_form_kwargs(self):
kwargs = {'user': self.request.user.username}
return kwargs
def post(self, request, *args, **kwargs):
form = CommentForm(request.POST, **self.get_form_kwargs())
if form.is_valid():
post = self.get_object()
form.instance.user = request.user
form.instance.post = post
form.save()
return redirect(reverse("post-detail", args=[post.pk]))
Lastly, do consider renaming this class, since we are working with adding a comment to post, and not really about "post detail", so something like PostAddCommentView?
I'm trying make a comment section for my Q&A project.I made the model for comment , the form part in question_detail.html an , also the QuestionCommentForm() in form.py .
model.py
class QuestionComment(models.Model):
user = models.ForeignKey(User,on_delete=models.CASCADE)
question =
models.ForeignKey(Question,on_delete=models.CASCADE)
created_date = models.DateTimeField(auto_now_add= True)
body = models.CharField(max_length=200, null= True ,
blank = True, default ='')
def __str__(self):
return str(self.body)
forms.py
{%if c_form%}
<form method="POST" action= "{% usl 'blog:question-commet' question.id >{% csrf_token %}
{{c_form.media}}
{{ c_form.as_p}}
<button type = "submit" , name = "question_id", value = "{{question.pk}}", class ="btn btn-secondary btn-sm">submit comment</button>
views.py
#api_view(['POST','GET'])
def question_comment(request, *args, **kwargs):
form = QuestionCommentForm()
print('finction comment started'*20)
if request.method == 'POST':
c_form = QuestionCommentForm(request.POST)
if c_form.is_valid():
new_comment = c_form.save(commit=False)
new_comment.refresh_from_db()
c_form.instance.user = request.user
question = Question.objects.get(id = request.POST.get('question_id')
new_comment.question = question
new_comment.bldy =c_form.cleaned_data.get('body')
new_comment.save()
context['c_form'] = c_form
return render(request, 'blog/question_detail.html',context)
class QuestionDetail(DetailView):
template_name = 'blog/question_detail.html'
model = Question
context_object_name = 'question'
count_hit = True
def get_queryset(self):
return Question.objects.filter(id = self.kwargs['pk'])
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
c_form = QuestionCommentForm()
context = super(QuestionDetail,self).get_context_data(**kwargs)
self.obj= get_object_or_404(Question, id = self.kwargs['pk'])
self.object.save()
self.object.refresh_from_db()
answers = Answer.objects.filter (question_id = self.obj.id).order_by('-created_date')
liked =self.obj.like.filter(id =self.request.user.id).exists()
print('liked in class question not checked still' *10)
comments= QuestionComment.objects.filter(question = self.kwargs['pk'])
context['comments']= comments
context["answers"]=answers
context["liked "] = liked
context['c_form'] = c_form
return context
def post(request, *args, **kwargs):
print('post started'*100)
c_form = QuestionCommentForm()
c_form = QuestionCommentForm(request.POST)
if c_form.is_valid():
new_comment = c_form.save(commit=False)
new_comment.refresh_from_db()
new_comment.user = request.user
new_comment.question = c_form.cleaned_data.get('question_id')
new_comment.bldy =c_form.cleaned_data.get('body')
new_comment.save()
context['c_form'] = c_form
else:
c_form= QuestionCommentForm()
return render(request, 'blog/question_detail.html',context)
url.py
...
path('question-comment/<int:pk>/', question_comment, name = 'question-comment'),
]
in my view, first I tried to use a another function to handle the comment, got no result and made a def post()
in the class QuestionDetial() , and still the form shows up but when I type something and hit the button , it refresh the page and nothing saves . I have already saved a comment using admin and it appears in the question-detail page. used print to find the bug, but it seems the post() in class and the question_comment() not being recall. searched a lot but no answer. (BTW I get no error except the NoReverseMatch that I fixed)
You never save the model object (not by the form, nor by the view). Furthermore the name of the method is 'POST', not 'Post':
#api_view(['POST','GET'])
def question_comment(request, *args, **kwargs):
form = QuestionCommentForm()
print('finction comment started'*20)
if request.method == 'POST':
c_form = QuestionCommentForm(request.POST)
if c_form.is_valid():
c_form.instance.user = request.user
c_form.save()
context = {'c_form': c_form }
return render(request, 'blog/question_detail.html',context)
ok , the problem was that I had two form-tags in question_detail template and buy adding comment form in the end of the template had 3 , but the problem was when I pressed the submit comment button the terminal showed this message
"POST /create-like/blog/answer/18/ HTTP/1.1" 302 0
which was the form I had made for like function .(while my url was question-comment)
I accidentally forgot to close the like-form tag in template. so all I needed was putting a
</form>
after the form block .
thank's Willem Van Onsem. it solved my post method error.
and these were very helpful
Proper way to handle multiple forms on one page in Django
How can I build multiple submit buttons django form?
I am trying to set the initial value of a form field.
I have approached it the following ways:
forms.py:
class FilterGigsForm(forms.Form):
field = forms.IntegerField(label="", required=False, initial=5)
def __init__(self, *args, **kwargs):
instance = kwargs.get('instance', None)
kwargs.update(initial={
'field': 5,
})
super().__init__(*args, **kwargs)
self.fields['field'].initial = 5
views.py
class HomePage(FormMixin, TemplateView):
template_name = 'index.html'
model = MyModel
form_class = MyForm
def get(self, request, *args, **kwargs):
form = MyForm(self.request.GET, initial={'field': 5,})
...
return render(request, 'index.html', {"form": form, })
I have loaded the form field into my template as follows:
<div class="styling">
{{ form.field|as_crispy_field }}
</div>
However, none of these approaches are working and the initial value is not showing when I load/reload the page.
form = MyForm(initial={'field': 5,})
I am currently following Mozilla's Django tutorial (https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Forms). The tutorial mostly shows how to create form using functions. I am trying to make the same function view work by using a generic class view (FormView). I am able to make most of the code to work except for 2 things. First one is that I can't seem to be able to save the due date. And, second one, is that I don't know how to access the model fields in my template using template variables.
Here is my form model from the forms.py file.
class RenewBookModelForm(ModelForm):
def clean_due_back(self):
data = self.cleaned_data['due_back']
# Check if a date is not in the past.
if data < datetime.date.today():
raise ValidationError(ugettext_lazy(
'Invalid date - renewal in past'))
# Check if a date is in the allowed range (+4 weeks from today).
if data > datetime.date.today() + datetime.timedelta(weeks=4):
raise ValidationError(ugettext_lazy(
'Invalid date - renewal more than 4 weeks ahead'))
# Remember to always return the cleaned data.
return data
class Meta:
model = BookInstance
fields = ['due_back']
labels = {'due_back': ugettext_lazy('New renewal date')}
help_texts = {'due_back': ugettext_lazy(
'Enter a date between now and 4 weeks (default 3).')}
The form model implemented as a function:
#permission_required('catalog.can_mark_returned')
def renew_book_lirarian(request, pk):
book_instance = get_object_or_404(BookInstance, pk=pk)
# If this is a POST request then process the Form data
if request.method == 'POST':
# Create a form instance and populate it with data from the request (binding):
form = RenewBookModelForm(request.POST)
# Chech if the form is valid
if form.is_valid():
# process that data in form.cleaned_data as required (here we just write to the model due_back field)
book_instance.due_back = form.cleaned_data['due_back']
book_instance.save()
# redirect to a new URL
return HttpResponseRedirect(reverse('all-borrowed'))
# If this is a GET (or any other method) create the default form.
else:
proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3)
form = RenewBookModelForm(initial={'due_back': proposed_renewal_date})
context = {
'form': form,
'book_instance': book_instance
}
return render(request, 'catalog/book_renew_librarian.html', context=context)
This is my class-based view from my views.py file:
class RenewBookLibrarian(LoginRequiredMixin, PermissionRequiredMixin, generic.FormView):
"""Generic class-based view for forms."""
template_name = 'catalog/book_renew_librarian.html'
permission_required = 'catalog.can_mark_returned'
form_class = RenewBookModelForm
success_url = reverse_lazy('all-borrowed')
def get_initial(self):
proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3)
initial = {'due_back': proposed_renewal_date}
return initial
And finally this is my template file where I wish to access the model fields:
{% extends 'base_generic.html' %}
{% block content %}
<h1>Renew: {{ book_instance.book.title }}</h1>
<p>Borrower: {{ book_instance.borrower }}</p>
<p {% if book_instance.is_overdue %} class="text-danger" {% endif %}>Due date: {{ book_instance.due_back }}</p>
<form action="" method="POST">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="submit" value="submit">
</form>
{% endblock %}
The book_instance variable in the template is not working, hence I would like to know how I can display fields from my BookInstance model.
To add book_instance to the template context, you can override get_context_data.
In the FormView, instead of checking if form.is_valid(), you override the form_valid method (see the class based view docs for basic forms).
class RenewBookLibrarian(LoginRequiredMixin, PermissionRequiredMixin, generic.FormView):
...
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['book_instance'] = get_object_or_404(BookInstance, pk=self.kwargs['pk'])
def form_valid(self, form):
book_instance = get_object_or_404(BookInstance, pk=self.kwargs['pk'])
book_instance.due_date = form.cleaned_data['due_date']
book_instance.save()
return super().form_valid(form) # will redirect to the success url