I have a problem with saving a form in my CreateView, I found
this solution and it worked for me:
class ScheduleDocumentView(CreateView):
def post(self, request, pk, *args, **kwargs):
form = ScheduleDocumentForm(request.POST, request.FILES)
if form.is_valid():
form.instance.relates_to = Schedule.objects.get(pk=pk)
form.save()
return redirect('planning:schedule-detail', pk=pk)
However my goal is to save a form using form_valid() and get_success_url() without a template in CreateView. I tried something like this(doesn't work):
class ScheduleDocumentView(CreateView):
model = ScheduleDocument
form_class = ScheduleDocumentForm
def form_valid(self, form):
form.instance.relates_to = Schedule.objects.get(pk=pk)
form.save()
return redirect('planning:schedule-detail', pk=pk)
def get_success_url(self):
return reverse('planning:schedule-detail', kwargs={'pk': pk})
It requires a template, is there any other way to handle my post request in DetailView, process it in separate CreateView and redirect it to my DetailView page?
Here's my template for DetailView:
<form enctype="multipart/form-data" action="{% url 'planning:upload-document' schedule.pk %}" method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="button button--secondary">Attach</button>
Urls:
path('schedules/<int:pk>/', ScheduleDetailView.as_view(), name='schedule-detail'),
path('schedules/<int:pk>/upload-document/', ScheduleDocumentView.as_view(), name='upload-document'),
I came across this solution:
class ScheduleDocumentView(CreateView):
model = ScheduleDocument
form_class = ScheduleDocumentForm
def form_valid(self, form):
form.instance.schedule = Schedule.objects.get(pk=self.kwargs['pk'])
return super().form_valid(form)
def get_success_url(self):
return reverse('planning:schedule-detail', kwargs={'pk': self.kwargs['pk']})
template_name is required Django Docs:
The full name of a template to use as defined by a string. Not
defining a template_name will raise a
django.core.exceptions.ImproperlyConfigured exception.
Or in your case Django would cause the default template_name to be 'yourapp/scheduledocument_create_form.html'.
Therefore you get the error TemplateDoesNotExist.
You can get the pk value from self.kwargs(Django Docs).
You can simple create the blank.html template.
class ScheduleDocumentView(CreateView):
http_method_names = ['post']
template_name = 'blank.html' # or use this 'schedule_detail.html'
model = ScheduleDocument
form_class = ScheduleDocumentForm
def form_valid(self, form):
form.instance.relates_to = Schedule.objects.get(pk=self.kwargs.get("pk"))
return super().form_valid(form)
def get_success_url(self):
return reverse('planning:schedule-detail', kwargs={'pk': self.kwargs.get("pk")})
Or use A simple view:
def create_schedule_document(request, pk):
if request.method == 'POST':
form = ScheduleDocumentForm(request.POST, request.FILES)
if form.is_valid():
obj = form.save(commit=False)
obj.relates_to = Schedule.objects.get(pk=pk)
obj.save()
else:
form = ApplyAnonymousForm()
return redirect('planning:schedule-detail', pk=pk)
Related
I think I don't understand something fundamental here, but every single tutorial on the topic proposes a solution using either a function or a generic class, and both of them work for me, but I can't figure out how to deal with the issue using just View. So to illustrate where I am at, I am building a very simple blog and want to update data on a single post based on it's id. So what I have is:
models.py
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
author = models.CharField(max_length=100)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post-detail', kwargs={'pk': self.pk})
forms.py
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = '__all__'
urls.py
urlpatterns = [
path('post/<int:pk>/update/', PostUpdateView.as_view(), name='post-update')
]
update_post.html
<form method="post">
{% csrf_token %}
{{ isolated_post.as_p }}
<input type="submit" value="Update">
</form>
and finally views.py
class PostUpdateView(View):
form_class = PostForm
initial = {'key': 'value'}
template_name = "blog/update_post.html"
def get(self, request, pk):
isolated_post = Post.objects.get(pk=pk)
form = self.form_class(instance=isolated_post)
return render(request, self.template_name, {'form': form})
def post(self, request, pk, form):
updated_post = self.form_class(request.POST, instance=form)
if updated_post.is_valid():
updated_post.save()
return HttpResponseRedirect("/post/" + f'{pk}/')
return render(request, self.template_name, {'updated_post': updated_post})
I've tried a lot of things, this time it says that form has not been passed to the post function. but the most I can achieve is the empty form, which throws out an error after I press "update". I think I just do not get the logic of "post" well enough, or how to populate the form with the one i use get function on.
Your view
class PostUpdateView(View):`
form_class = PostForm
template_name = "blog/update_post.html"
def setup(self, request, *args, **kwargs):
self.post_instance = get_object_or_404(Post, pk=kwargs['post_id'])
return super().setup(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
post = self.post_instance
form = self.form_class(instance=post)
return render(request, self.template_name, {'form': form})
def post(self, request, *args, **kwargs):
post = self.post_instance
form = self.form_class(request.POST, instance=post)
if form.is_valid():
new_post = form.save(commit=False)
new_post.save()
return redirect('your dynamic url', post.id)
I’m trying to create a dashboard for the staff users to fill in and edit some information regarding their users. The form works and saves successfully, but when I submit it for a second time, it creates a new object. It won’t replace the previous:
This is my views.py file:
class ScientificInfoView(FormMixin, DetailView):
model = ScientificInfo
template_name = 'reg/scientific-info.html'
form_class = ScientificInfoForm
def get_success_url(self):
return reverse('scientific-info', kwargs={'pk': self.object.pk})
def get_context_date(self, **kwargs):
context = super(ScientificInfoView, self).get_context_data(**kwargs)
context['form'] = ScientificInfoForm()
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 form_valid(self, form):
form.save()
return super(ScientificInfoView, self).form_valid(form)
And my template:
<form method="POST" enctype="multipart/form-data" action="{% url 'scientific-info' pk=object.id %}">
{% csrf_token %}
{{form}}
<button type="submit">submit</button>
</form>
File urls.py:
path('surveys/scientific/<pk>', login_required(views.ScientificInfoView.as_view()), name='scientific-info')
I’m pretty sure that the action part in my form is causing the issue, but how can I solve it?
Use this:
def get_success_url(self):
pk = self.kwargs["pk"]
return reverse("scientific-info", kwargs={"pk": pk})
Or
class ScientificInfoView(FormMixin, DetailView):
model = ScientificInfo
template_name = 'reg/scientific-info.html'
form_class = ScientificInfoForm
def get_success_url(self):
return reverse("scientific-info", args=[pk]) # You can replace pk
I am trying to make a form that is dynamically selected by a DetailView object.
I want to click the DetailView link and be taken to a form whose primary key is the same as the primary key from my detail view. When I attempt to do this I get an error. How can I do this? Is their a prebuilt library that will assist me?
My Model:
'''
class MemberStudent(models.Model):
instructor = models.ForeignKey(Teachers, on_delete=models.CASCADE)
name = models.CharField(max_length=50, default="Doe")
age = models.IntegerField(default=99)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('student_detail', args=[str(self.id)])
class BehaviorGrade(models.Model):
SEVERITY = [
('Bad','Bad Behavior'),
('Good','Good Behavior'),
('Danger','Dangerous'),
]
LETTER_GRADE = [
('A','A'),
('B','B'),
('F','F'),
]
studentName = models.ForeignKey(MemberStudent, on_delete=models.CASCADE) #need a link to student name
eventGrade = models.CharField(max_length=1, choices=LETTER_GRADE)
eventSeverity = models.CharField(max_length=15, choices=SEVERITY)
eventTitle = models.CharField(max_length=15)
eventDescription = models.TextField()
def __str__(self):
return self.eventTitle
'''
My Views:
'''
class StudentDetailView(FormMixin,DetailView):
model = MemberStudent
template_name = 'student_detail.html'
form_class = BehaviorForm
def get_success_url(self):
return reverse('student_detail', kwargs={'pk':self.object.pk})
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
form.save()
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
return super().form_valid(form)
'''
my forms.py:
'''
class BehaviorForm(ModelForm):
class Meta:
model = BehaviorGrade
fields = ['eventGrade',
'eventSeverity','eventTitle',
'eventDescription']
'''
my url:
'''
path('students/<int:pk>', StudentDetailView.as_view(), name='student_detail'),
'''
my htmltemplate(its a detailview with a form below on the same page):
'''
{{ object.id }}
{{ object.name }}
{{ object.age }}
{{ object.instructor }}
<form action="{% url 'student_detail' object.id %}" method="POST">
{{ form }}
{% csrf_token %}
<input type="submit" value="REVIEW">
</form>
'''
error message when form submitted:
"IntegrityError at /student/students/1
NOT NULL constraint failed: Students_behaviorgrade.studentName_id"
The form also is sent without the PK I have requested in my form code.
the django error log shows the PK is never sent
here is the log message on a test of dummy data:
params [None, 'A', 'Bad', 'asdf', 'asdf']
That's because you have to manually set the member student id on the behavior garde.
You can do it like this in the post function of your view :
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
behavior_grade = form.save(commit=False)
behavior_grade.studentName = self.object
return self.form_valid(form)
else:
return self.form_invalid(form)
You can also take a look at this method to use the form_valid function to do it :
## Include the instance object before saving
def form_valid(self, form):
form.instance.studentName = self.object
return super().form_valid(form)
The solution was to to call the primary key in the model method. Then to save to the correct model object I called .pk on my object. I'm cannot elegantly explain this solution however upon testing several different scenarios it works.
'''
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
behavior_grade = form.save(commit=False)
behavior_grade.studentName = self.object #passes unamed form field
behavior_grade.studentName.pk = form.save(commit=True) #calls key
return self.form_valid(form)
else:
return self.form_invalid(form)
'''
I'm having trouble with adding comments to class based views,
forms.py:
class RequestForm(ModelForm):
class Meta:
model = Request
exclude = ('slug',)
class RequestCommentForm(ModelForm):
class Meta:
model = RequestComment
fields = ['body' ]
models.py:
class Request(models.Model):
title = models.CharField(max_length=250 )
date = models.DateTimeField('Request date', default=timezone.now, editable=False )
department = models.CharField(max_length=2, choices=DEPARTMENT)
support_request = models.TextField('Request', max_length=2500, blank=True)
owner = models.ForeignKey(User,)
slug = models.SlugField(blank=True, editable=False)
def __unicode__(self):
return self.title
views.py:
class RequestDetailView(ModelFormMixin, DetailView):
model = Request
form_class = RequestCommentForm
def get_success_url(self):
return reverse('request-detail', kwargs={'pk': self.object.pk})
def get_context_data(self, **kwargs):
context = super(RequestDetailView, self).get_context_data(**kwargs)
context['form'] = self.get_form()
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 form_valid(self, form):
form.instance.author = self.request.user
form.instance.request = Request.objects.get(pk=self.object.pk)
form.instance.created = timezone.now
form.save()
Also tried this part with:
self.object = form.save(commit=False)
self.object.author = self.request.user
self.object.request = Request.objects.get(pk=self.object.pk)
self.object.created = timezone.now
self.object.save()
return super(RequestDetailView, self).form_valid(form)
template: request_detail.html comments section .....
{% load bootstrap %}
<form action="{% url 'request-detail' object.id %}" method="post"> {% csrf_token %}
<ul class="form-group">
{{ form|bootstrap }}
</ul>
<input class="btn btn-primary" type="submit" value="Submit" />
</form>
.....
Page renders correctly, but when I submit, no-go with saving comment.
Debug toolbar shows that sql queries are updating the request model, instead of request comment.
Can't figure out how to add simple comment form from different model to detail page.
Any help would be appreciated.
Also, if there is more elegant way off adding comments form to class based view, would love to see it. My google-fu didn't help me to find anything.
I know I'm answering this too late, but maybe someone with the same question, in the future, may benefit.
Class RequestDetailView(DetailView):
model = Request
template_name = 'detail.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context ['comment'] = RequestComment.objects. all()
context['form'] = RequestCommentForm()
return context
Class CommentCreateView(CreateView):
model = RequestComment
form_class = RequestCommentForm
def get_success_URL(self):
return reverse ('request: detail', kwargs = {'slug':self.object.post.slug})
def form_valid(self, form):
post = get_object_or_404(Request, slug = self.kwargs ['slug'])
Form.instance.post = Request
return super().form_valid(form)
The URL
path('<slug:slug>/add_comment/', CommentCreateView.as_view(), name = add_comment')
And finally the HTML
<form action="{% url 'request:add_comment' request.slug %}" enctype = "multiparty/form-data" method = "post">
</form>
And that's it. I answered using my phone so there's some typos etc. This is my first answer here, I searched stack overflow for answers I'll post links later. I assumed RequestComment foreignkey field is post and creating models with name 'request' is not encouraged in Django.
Wish I could find a way to do this with CBV, but nope.. function view works great...
class RequestDetailView(ModelFormMixin, DetailView):
model = Request
form_class = RequestCommentForm
def get_success_url(self):
return reverse('request-detail', kwargs={'pk': self.object.pk})
def get_context_data(self, **kwargs):
context = super(RequestDetailView, self).get_context_data(**kwargs)
context['form'] = self.get_form()
return context
def comments(self):
return RequestComment.objects.filter(request=Request.objects.get(pk=self.object.pk))
def RequestCommentAddView(request, pk):
form = RequestCommentForm(request.POST or None)
if form.is_valid() and pk:
form.instance.author = request.user
form.instance.request = Request.objects.get(pk=pk)
form.save()
return HttpResponseRedirect(reverse('request-detail', kwargs={'pk': pk}))
return HttpResponseRedirect(reverse('request-detail', kwargs={'pk': pk}))
I have two forms which I want to display and use on index page of the website.
Index page is a HomeView which in it's context contains two forms - ContactForm and SubscribtionForm.
My goal is to use these two forms on single page. So far it is impossible because each time validation fails it redirects me to view which is responsible for form validation.
Fragment of index page html code:
<form id="contact-form" class="contact form-horizontal" action="{% url 'contact' %}" method="post" novalidate>
<form id="newsletter-form" class="newsletter_form" action="{% url 'subscribe' %}" method="post" novalidate>
How do I do it in django that when I submit it will use other views to validate and save the data but responses will be displayed on Index page? Should I use javascript/ajax calls to solve this issue? Or there is another way?
Edit:
class SubscriberFormView(FormView):
form_class = SubscriberForm
template_name = 'home/subscriber_form.html'
def get_success_url(self):
return reverse('home')
def form_valid(self, form):
form.save()
return redirect(self.get_success_url())
class ContactFormView(FormView):
form_class = ContactForm
template_name = "home/contact_form.html"
def form_valid(self, form):
instance = form.save()
instance.send_confirmation()
instance.send_notification()
return redirect(self.get_success_url())
Alternative solution with prefixes:
class HomeView(FormMixin, TemplateView):
template_name = 'index.html'
form_class = ContactForm
form_class_sub = SubscriberForm
def get_success_url(self):
return reverse('home')
def get_forms(self):
contact_kwargs = self.get_form_kwargs().copy()
contact_kwargs['prefix'] = 'contact'
sub_kwargs = self.get_form_kwargs().copy()
sub_kwargs['prefix'] = 'sub'
return {
'contact_form': self.form_class(**contact_kwargs),
'subscription_form': self.form_class_sub(**sub_kwargs),
}
def post(self, request, *args, **kwargs):
forms = self.get_forms()
if 'contact-submit' in request.POST:
form = forms['contact_form']
form_name = 'contact_form'
elif 'newsletter-submit' in request.POST:
form = forms['subscription_form']
form_name = 'subscription_form'
else:
raise Http404
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form, form_name)
def form_invalid(self, form, form_name):
return self.render_to_response(self.get_context_data())
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
if 'contact_form' not in data:
data['contact_form'] = self.get_forms()['contact_form']
if 'subscription_form' not in data:
data['subscription_form'] = self.get_forms()['subscription_form']
return data
def form_valid(self, form):
instance = form.save()
return super().form_valid(form)
Function based view (index):
def index(request):
contact_form = ContactForm(request.POST or None, prefix='contact')
subscription_form = SubscriberForm(request.POST or None, prefix='sub')
if request.method == "POST":
if 'contact-submit' in request.POST:
form = contact_form
elif 'sub-submit' in request.POST:
form = subscription_form
else:
raise Http404
if form.is_valid():
form.save()
return TemplateResponse(request, template="index.html", context={
'contact_form': contact_form,
'subscription_form': subscription_form,
})
Final version:
def index(request):
contact_form = ContactForm(None, prefix='contact')
subscription_form = SubscriberForm(None, prefix='sub')
if request.method == "POST":
if 'contact-submit' in request.POST:
contact_form = ContactForm(request.POST, prefix='contact')
if contact_form.is_valid():
contact_form.save()
elif 'sub-submit' in request.POST:
subscription_form = SubscriberForm(request.POST, prefix='sub')
if contact_form.is_valid():
contact_form.save()
else:
raise Http404
return TemplateResponse(request, template="index.html", context={
'contact_form': contact_form,
'subscription_form': subscription_form,
})
You should not use different view do validate data. The forms should be validated in the same view where they've been rendered.
To be able to use multiple forms in one view, simply use form prefix:
https://docs.djangoproject.com/en/1.8/ref/forms/api/#prefixes-for-forms