I'm wondering how I can solve this issue in my form_valid() function :
form_valid() missing 1 required positional argument: 'form'
I'm using Django Class Based View and formsets. But, it could be possible to get IntegrityError and I added a try/except in order to save the formset when forms are valid, and redirect the template with an error message when I get this issue.
class AnimalCreateView(CreateView):
model = Animal
template_name = 'animal_form.html'
def get_context_data(self, **kwargs):
context = super(AnimalCreateView, self).get_context_data(**kwargs)
foo_queryset = Foo.objects.all()
context['FooFormSets'] = FooFormSet(self.request.POST or None, self.request.FILES or None,
prefix='foo', queryset=foo_queryset)
return context
def form_valid(self, request, form):
context = self.get_context_data()
formsets = context['FooFormSets']
if form.is_valid():
self.object = form.save()
try:
if formsets.is_valid():
formsets.instance = self.object
formsets.save(commit=False)
for element in formsets:
element.save(commit=False)
formsets.save()
except IntegrityError:
messages.error(self.request, _(f"Issue with foo"))
return render(request, self.template_name)
return super(AnimalCreateView, self).form_valid(form)
I would like to know what I have to do in my form_valid() function in order to solve my issue and redirect user on the same template form with error message.
Thank you
def form_valid(self, request, form): should be def form_valid(self, form):.
You can access request object through self.request.
def form_valid(self, form):
context = self.get_context_data()
formsets = context['FooFormSets']
if form.is_valid():
self.object = form.save()
try:
if formsets.is_valid():
formsets.instance = self.object
formsets.save(commit=False)
for element in formsets:
element.save(commit=False)
formsets.save()
except IntegrityError:
messages.error(self.request, _(f"Issue with foo"))
return render(self.request, self.template_name)
Additionally, you don't need to call if form.is_valid(): a second time as that's already been determined - hence executing form_valid.
Related
I have two views, PostCreateView and PostUpdateView. They both route through the same html template, post_form.html. I want to create a Copy button that only appears if I am accessing a record through PostUpdateView. Pressing the Copy button will create a new record pre-filled with all the data from record that I was just on.
PostCreateView code:
class PostCreateView(LoginRequiredMixin, FormView):
template_name = 'trucking/post_form.html'
form_class = RecordForm
def form_valid(self, form):
form.instance.author = self.request.user
obj = form.save(commit=False)
obj.save()
messages.success(self.request, f'RECORD: {obj.container_no} was saved')
return super().form_valid(form)
def get_success_url(self):
if self.request.POST.get('Save_Exit'):
return reverse('close')
elif self.request.POST.get('Save'):
return reverse('post-create')
else:
return reverse('database')
PostUpdateView code:
class PostUpdateView(LoginRequiredMixin, UpdateView):
form_class = RecordForm
model = Post
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
history = Post.objects.filter(id = self.object.id).first().history.all().reverse()
data['history'] = get_status_changes(history)
return data
# checks to make sure the user is logged in
def form_valid(self, form):
form.instance.author = self.request.user
obj = form.save(commit=False)
messages.success(self.request, f'RECORD: {obj.container_no} was updated')
return super().form_valid(form)
def get_success_url(self):
if self.request.POST.get('Save_Exit'):
return reverse('close')
# return render(self.request, 'trucking/email_close.html')
elif self.request.POST.get('Save'):
return reverse('post-update', kwargs={'pk': self.object.id})
else:
return reverse('database')
Based on this post I tried creating a view like so:
def copy(request):
post = Post.objects.get(pk = request.users.post.id)
my_form = RecordForm(instance = post)
return render(request, 'trucking/post_form.html', {'form': my_form})
However, I get an error saying 'WSGIRequest' object has no attribute 'users'. I think that's not the only issue with this approach.
Should it be a part of PostUpdateView, since it takes the form information from an existing record? Should it be part of PostCreateView since you take the preexisting info and fill out a new form with it? Should it be a new view altogether? Any help is appreciated
I am getting forms success message in my html template but forms data not saving. here is my code:
views.py:
class BlogDetailsAccount(FormMixin,DetailView):
model = Blog
template_name = 'blog/my-account-blog-details.html'
form_class = CommentFrom
def get_success_url(self):
return reverse('blog:my-account-blog-details', kwargs={'slug': self.object.slug})
def get_context_data(self, **kwargs):
#my context data........
return data
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
messages.add_message(self.request, messages.INFO, 'Your Comment pending for admin approval')
return self.form_valid(form)
else:
messages.add_message(self.request, messages.INFO, 'Somethings Wrong. Please try again')
return self.form_invalid(form)
def form_valid(self, form):
return super(BlogDetailsAccount, self).form_valid(form)
my models.py:
class BlogComment(models.model):
.......#my models fields....
.............
post_save.connect(BlogComment.user_comment, sender=BlogComment) #using signals
A FormMixin does not save the data to the database. In case you need to save the form, you should save it in the form_valid method:
A form_mixin does not save the data to the database.
class BlogDetailsAccount(FormMixin,DetailView):
# …
def post(self, request, *args, **kwargs):
form = self.get_form() # ← first create a form
self.object = self.get_object() # ← then specify the object
if form.is_valid():
messages.add_message(self.request, messages.INFO, 'Your Comment pending for admin approval')
return self.form_valid(form)
else:
messages.add_message(self.request, messages.INFO, 'Somethings Wrong. Please try again')
return self.form_invalid(form)
def form_valid(self, form):
form.save()
return super().form_valid(form)
You should also likely fill in relevant data like the post where the comment appears and
I've got one master detail relationship beteen Article and Section, and wish to allow a user to add sections on the same page before creating the Article.
It seems to work, but I'm also very new to Django, and wonder if I'm doing it right. There's a lot going on here, and I wonder if I'm overriding methods correctly, for example, and when I do, is the content correct?
A gold-standard way of doing it would be very appreciated!
class ArticleCreateView(LoginRequiredMixin,CreateView):
template_name = 'articles/article_add.html'
model = Article
form_class = ArticleForm
success_url = reverse_lazy('index')
SectionFormSet = inlineformset_factory(Article, Section, extra=2, can_delete=False, fields=('content',))
def get(self, request, *args, **kwargs):
print('get called on article create view')
self.object = None #what does this achieve?
return self.render_to_response(
self.get_context_data(form=self.get_form(),
section_form=self.SectionFormSet(),
))
def post(self, request, *args, **kwargs):
print('post called on article create view')
self.object = None
form = self.get_form()
section_form = self.SectionFormSet(self.request.POST)
if (form.is_valid() and section_form.is_valid()):
return self.form_valid(form, section_form)
else:
return self.form_invalid(form, section_form)
def form_valid(self, form, section_form):
form.instance.author = self.request.user
if section_form.is_valid():
print('section form is valid')
self.object = form.save()
section_form.instance = self.object
section_form.save()
return HttpResponseRedirect(self.success_url)
#return super().form_valid(form)
'''
return self.render_to_response( self.get_context_data(form=form,
section_form=section_form,
))
'''
def form_invalid(self, form, article_form):
return self.render_to_response(
self.get_context_data(form=form,
article_form=article_form))
I don't know if this is 100% correct, but it seems better than my previous code :
class ArticleCreateView(LoginRequiredMixin,CreateView):
template_name = 'articles/article_add.html'
form_class = ArticleForm
SectionFormSet = inlineformset_factory(Article, Section, extra=2, can_delete=False, fields=('content',))
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if self.request.POST:
context['section_form'] = self.SectionFormSet(self.request.POST,instance=self.object)
else:
context['section_form'] = self.SectionFormSet(instance=self.object)
return context
def save_sections(self):
try:
context = self.get_context_data()
section_form = context['section_form']
if section_form.is_valid():
#section_form.instance = self.object
section_form.save()
except Exception as e:
print('failed to save section: ' + str(e))
def form_valid(self, form):
form.instance.author = self.request.user
response = super().form_valid(form) #save article form
self.save_sections()
return response
def get_success_url(self):
return reverse_lazy('index')
here's the update view (for completion), pretty much identical except it gets the instance of the article in the formset factory
class ArticleUpdateView(LoginRequiredMixin,UserPassesTestMixin,UpdateView):
template_name = 'articles/ArticleUpdate.html'
form_class = ArticleForm
model = Article
SectionFormSet = inlineformset_factory(Article, Section, extra=0, can_delete=False, fields=('content',))
def test_func(self):
article = self.get_object()
if self.request.user == article.author or self.request.user.is_superuser :
return True
else:
return False
def get_context_data(self, **kwargs):
print('get context data called update view')
context = super().get_context_data(**kwargs)
if self.request.POST:
context['section_form'] = self.SectionFormSet(self.request.POST,instance=self.object)
else:
context['section_form'] = self.SectionFormSet(instance=self.object)
return context
def save_sections(self):
print('save sections called update view')
try:
context = self.get_context_data()
section_form = context['section_form']
if section_form.is_valid():
# section_form.instance = self.object #if im passing instance in the factory, do I need it here to?
section_form.save()
except Exception as e:
print('failed to save section: ' + str(e))
def form_valid(self, form):
print('form valid called update view')
form.instance.author = self.request.user
response = super().form_valid(form) #save article form
self.save_sections()
return response
def get_success_url(self):
return reverse_lazy('index')
I am trying to create an object with django DetailView. My code is like that.
class Detail(DetailView):
model = MyModel
template_name = 'mymodel_detail.html'
def get_context_data(self, **kwargs):
context = super(Detail, self).get_context_data(**kwargs)
context['form'] = DetailForm
return context
def post(self, request, *args, **kwargs):
form = DetailForm(request.POST, request.FILES)
if form.is_valid():
context['reply_form'] = DetailForm
self.object = super(Detail, self).get_object()
context['object'] = super(Detail, self).get_object()
return self.render_to_response(request=request, template=self.get_template_names(), context=context)
else:
context = context = super(Detail, self).get_context_data(**kwargs)
context['reply_form'] = form
self.object = super(Detail, self).get_object()
context['object'] = super(Detail, self).get_object()
return self.render_to_response(request=request, template=self.get_template_names(), context=context)
But here I am getting error that
'Detail' object has no attribute 'object'
I tried to assign object in context instance and with self as well. But nothing works.
What you are missing here is that you have to assign the object to the class or self before calling the get_context_data().
class Detail(DetailView):
model = MyModel
template_name = 'mymodel_detail.html'
def get_context_data(self, **kwargs):
context = super(Detail, self).get_context_data(**kwargs)
context['form'] = DetailForm
return context
def post(self, request, *args, **kwargs):
form = DetailForm(request.POST, request.FILES)
if form.is_valid():
# Write Your Logic here
self.object = self.get_object()
context = super(Detail, self).get_context_data(**kwargs)
context['form'] = DetailForm
return self.render_to_response(context=context)
else:
self.object = self.get_object()
context = super(Detail, self).get_context_data(**kwargs)
context['form'] = form
return self.render_to_response( context=context)
and in render_to_response() Just pass context. No other arguments.
Hope it will work for you.
This is how I implemented the code from Safrazs answer to make a reply option on my question model. I know this is an old question, but I hope that someone will find this useful.
class QuestionDetailView(generic.DetailView):
model = Question
template_name = 'forum/question.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = ReplyForm
return context
def post(self, request, *args, **kwargs):
form = ReplyForm(request.POST)
if form.is_valid():
reply = form.save(commit=False)
reply.creator = request.user
reply.question = self.get_object()
reply.save()
self.object = self.get_object()
context = context = super().get_context_data(**kwargs)
context['form'] = ReplyForm
return self.render_to_response(context=context)
else:
self.object = self.get_object()
context = super().get_context_data(**kwargs)
context['form'] = form
return self.render_to_response(context=context)
You are inheriting from the wrong generic view. You need to inherit from CreateView, like this:
class CreateModel(CreateView):
model = MyModel
template_name = 'mymodel_detail.html'
form_class = DetailForm
success_url = reverse('/thanks')
def form_valid(self, form):
# this method is called when the form
# is successfully validated
# if you need to do something with
# the database object, this is the place
# do not use it to redirect to a success page
# use the success_url for that
return super(CreateModel, self).form_valid(form)
You are calling super on the wrong class: they should be Detail, not MessageDetail. Also, you don't need the form code. Instead, use one of the generic editting views (CreateView, DeleteView, FormView, UpdateView). The DetailView is only for display purposes really. More detail on the generic views can be found at http://ccbv.co.uk/
I have a model PSG which has a relation to another model SubPSG. PSGs can have several SubPSGs. I'm using inline formsets to create SubPSGs as I create the PSGs. The CreateView works okay but the UpdateView is not working as expected. The UpdateView is as follows:
class PSGUpdate(GroupRequiredMixin, UpdateView):
model = PSG
form_class = PSGForm
template_name = "management/psg_update_form.html"
success_url = reverse_lazy('dashboard')
group_required = [u"admin", "manager"]
def get_context_data(self, **kwargs):
context = super(PSGUpdate, self).get_context_data(**kwargs)
subpsgs = SubPSG.objects.filter(psg=self.object).values()
sub_formset = inlineformset_factory(PSG, SubPSG, fields=('name',), can_delete=True,
extra=len(subpsgs), formset=BaseSubPSGFormset,)
context['formset'] = sub_formset(instance=self.object, initial=subpsgs, prefix='psg')
return context
def post(self, request, *args, **kwargs):
self.object = self.get_object()
subpsgs = SubPSG.objects.filter(psg=self.object).values()
psg_form = PSGForm(request.POST, instance=self.object)
sub_formset = inlineformset_factory(PSG, SubPSG, fields=('name',), can_delete=True, extra=len(subpsgs), formset=BaseSubPSGFormset)
formset = sub_formset(self.request.POST, instance=self.object, prefix='psg')
if all([psg_form.is_valid(), formset.is_valid()]):
return self.form_valid(psg_form, formset)
else:
return self.form_invalid(psg_form, formset)
def form_valid(self, form, formset):
if all([formset.is_valid() and form.is_valid()]):
self.object = form.save()
instances = formset.save(commit=False)
for form in instances:
form.psg = self.object
form.save()
return HttpResponseRedirect(self.get_success_url())
else:
return super(PSGUpdate, self).form_valid(form)
When I try updating a SubPSG formset, a new object is created instead of the edit taking place. Moreover, duplicates of the other formsets are also created.
What I'm I doing wrong??