Set value of field for Django ModelForm in CreateView - python

I need to set the value of a BooleanField from my Django model via the CreateView for my ModelForm. But for some reason, setting it in form_valid() isn't working.
Here's my model:
class Feedback(models.Model):
was_satisifed = models.BooleanField(
help_text='Returns true if the user exits the process early.',
default=False)
Here's my view:
class FeedbackActionMixin(object):
model = Feedback
form_class = FeedbackForm
def form_valid(self, form):
instance = form.save(commit=False)
instance.was_satisfied = True
return super(FeedbackActionMixin, self).form_valid(form)
The form submits, but the "was_satisfied" value is left at the default False. What am I missing?

If memory serves, form_valid is called after the data has already been posted. You might try overriding the post method on your mixin:
class FeedbackActionMixin(object):
model = Feedback
form_class = FeedbackForm
def post(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
if form.is_valid():
form.was_satisfied = True
return self.form_valid(form)
else:
return self.form_invalid(form)

Here's what I ended up doing, though in forms.py not views.py:
class SuccessfulFeedbackForm(FeedbackFormMixin, forms.ModelForm):
def __init__(self, *args, **kwargs):
super(SuccessfulFeedbackForm, self).__init__(*args, **kwargs)
self.fields['was_satisifed'].initial = True
class UnsuccessfulFeedbackForm(FeedbackFormMixin, forms.ModelForm):
def __init__(self, *args, **kwargs):
super(UnsuccessfulFeedbackForm, self).__init__(*args, **kwargs)
self.fields['was_satisifed'].initial = False

Related

django user update form not updating

I have a detailed user view that has a button for user updates. The user update form is inside a modal, for that, I am using a FormView ModelForm and a TbUser. I don't get how the form validation works but the fields are correct. When I update something for a user, I get an error, TbUser with username already exists, which means the code does not update the user but tries to add a new one. Also, I want to redirect to user-detail page after submit.
views.py
class UserUpdateView(LoginRequiredMixin, SuccessMessageMixin, FormView):
form_class = UserUpdateForm
template_name = 'users/modals/user_update_modal.html'
success_message = "User updated successfully."
def get_form_kwargs(self):
kw = super().get_form_kwargs()
kw['request'] = self.request
return kw
def form_valid(self, form):
obj = form.save(commit=False)
print(obj.username)
print('valid')
TbUser.objects.filter(id=self.request.user.id).update(username=obj.username, real_name=obj.real_name,
email=obj.email, cellphone=obj.cellphone,
department=obj.department, role=obj.role)
def form_invalid(self, form):
messages.error(self.request, form.errors)
# Where to redirect here? I want to
def get_success_url(self):
return reverse('user-detail', kwargs={'pk': self.formclass})
forms.py
class UserUpdateForm(forms.ModelForm):
email = forms.EmailField()
def __init__(self, request, *args, **kwargs):
super().__init__(*args, **kwargs)
self.request = request
if request.user.customer:
self.fields['department'].queryset = TbDepartment.objects.filter(
customer=request.user.customer)
self.fields['role'].queryset = TbRole.objects.filter(
customer=request.user.customer)
class Meta:
model = TbUser
fields = ['username', 'real_name', 'email',
'cellphone', 'department', 'role']
urls.py
urlpatterns = [
path('users-list/', views.UsersListView.as_view(), name='users-list'),
path('user-detail/<str:pk>/',
views.UserDetailView.as_view(), name='user-detail'),
path('tb-user-update-form/<str:pk>/update/',
views.UserUpdateView.as_view(), name='tb-user-update-form'),
]
You need to override the get_object method to let it point to the object you want to update.
A FormView will construct a form *without looking for an object, you can use an UpdateView [Django-doc] to fetch the object with get_object, and then inject this in the form to update that object:
from django.views.generic import UpdateView
class UserUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
form_class = UserUpdateForm
template_name = 'users/modals/user_update_modal.html'
success_message = "User updated successfully."
def get_form_kwargs(self):
kw = super().get_form_kwargs()
kw['request'] = self.request
return kw
def get_object(self, *args, **kwargs):
return self.request.user
def form_invalid(self, form):
messages.error(self.request, form.errors)
return super().form_invalid(form)
def get_success_url(self):
return reverse('user-detail', kwargs={'pk': self.object.pk })

Saving in django forms using FormView

I'm creating a form that will change the state of reserve book, I have this
class LibraryReserveForm(CrispyFormMixin, forms.Form):
def __init__(self, *args, **kwargs):
self.manager = kwargs.pop('manager')
super(LibraryReserveForm, self).__init__(*args, **kwargs)
def save(self):
self.instance.reserve_status = 'approved'
self.instance.save()
return self.manager
models.py
class ReservedBooks(TimeStampedModel):
BOOK_RESERVE_STATUS = Choices(
('for_approval', "For Approval"),
('approve', "Approved"),
('cancelled', "Cancelled"),
('rejected', "Rejected")
)
reserve_status = models.CharField(
_('Status'),
max_length=32,
choices=BOOK_RESERVE_STATUS,
default='for_approval'
)
...
view
class LibraryReserveView(
ProfileTypeRequiredMixin,
MultiplePermissionsRequiredMixin,
FormView,
):
model = ReservedBooks
template_name = 'library/reserved_list.html'
form_class = LibraryReserveForm
def get_form_kwargs(self):
kwargs = super(LibraryReserveView, self).get_form_kwargs()
kwargs.update({
'manager': self.request.user.manager,
})
return kwargs
urls
url(
r'^reserve/(?P<pk>\d+)/$',
views.LibraryReserveView.as_view(),
name='reserved'
),
everytime I submit the button I print something in the save() method of the forms but its not printing something therefore that method is not called. How do you called the save method ? Thanks
A FormView does not handle saving the object. It simply calls form_valid that will redirect to the success_url. But an UpdateView adds boilerplate code to pass the instance to the form, and will save the form.
You work with a Form, but a Form has no .instance attribute. A ModelForm has, so it might be better to use a ModelForm here:
class LibraryReserveForm(CrispyFormMixin, forms.ModelForm):
def __init__(self, *args, **kwargs):
self.manager = kwargs.pop('manager')
super(LibraryReserveForm, self).__init__(*args, **kwargs)
def save(self, *args, **kwargs):
self.instance.reserve_status = 'approved'
return super().save(*args, **kwargs)
Then we can make use of an UpdateView:
from django.views.generic import UpdateView
class LibraryReserveView(
ProfileTypeRequiredMixin,
MultiplePermissionsRequiredMixin,
UpdateView
):
model = ReservedBooks
template_name = 'library/reserved_list.html'
form_class = LibraryReserveForm
success_url = …
def get_form_kwargs(self):
kwargs = super(LibraryReserveView, self).get_form_kwargs()
kwargs.update(
manager=self.request.user.manager
)
return kwargs
You still need to specify the sucess_url here: the URL to which a successful POST request will redirect to implement the Post/Redirect/Get pattern [wiki].

Django: Different views for POST and GET - Form validation errors

I have a DemandDetailView(DetailView) and BidCreationView(CreateView).
On DemandDetailView page, there is a form (for creating Bids) which posts data to BidCreationView.
I can't figure out what to do in case form is invalid. I would like to render DemandDetailView again with form errors and preserve corresponding URL.
class DemandDetailView(DetailView):
model = Demand
template_name = 'demands/detail.html'
def dispatch(self, request, *args, **kwargs):
self.bid_creation_form = BidCreationForm(request.POST or None, request.FILES or None,request=request)
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['bid_creation_form']=self.bid_creation_form
return context
class BidCreationView(CreateView):
http_method_names = ['post']
model = Bid
form_class = BidCreationForm
def get_success_url(self):
return reverse_lazy("demands:demands")
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs.update({'request': self.request})
return kwargs
def form_valid(self, form):
form.instance.demand_id = self.kwargs.pop('demand_id')
return super().form_valid(form)
Do you have any ideas? My only idea is to use session which isn't probably the best way.
You could use is_valid() method from Form Objects. Something like:
class DemandDetailView(DetailView):
model = Demand
template_name = 'demands/detail.html'
def dispatch(self, request, *args, **kwargs):
form = BidCreationForm(request.POST or None, request.FILES or None,request=request)
if form.is_valid():
self.bid_creation_form = form
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['bid_creation_form']=self.bid_creation_form
return context
Option #2 (Personal Choice):
forms.py
from django import forms
from .models import Bid
class BidCreationForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(BidCreationForm, self).__init__(*args, **kwargs)
for field in self.fields.values():
field.widget.attrs = {"class": "form-control"}
class Meta:
model = Bid
fields = ('user', 'demands', 'amount', 'transaction')
Take a look at the Meta class within the Form. Its explicitly calling the Bid Model and the fields attribute is referring Bid fields from the Model Instance. Now you could call this form in any view without calling another view. If you want to add logic to this form, like calculating total amount or something like that then should do it within the form also. Write code once, Dont Repeat yourself.

Python edit form data prior to django validation

I'm fairly new to python and have been searching for awhile to find how I can edit form data BEFORE all the standard python form/field validators do their magic.
I have a model form with an IntegerField which I'd like to remove the "$" and commas from (using some sort of custom validation), then let the normal to_python() validate() etc do their thing.
My code is below - any help would be much appreciated!
forms.py
class BuyerSettingsForm(forms.ModelForm):
total_offer_limit = forms.IntegerField(required=False, max_value=10000000, min_value=0)
def __init__(self, request, *args, **kwargs):
super(BuyerSettingsForm, self).__init__(*args, **kwargs)
class Meta:
model = Buyer
fields = ['total_offer_limit']
def save(self, commit=True):
profile = super(BuyerSettingsForm, self).save(commit=commit)
profile.total_offer_limit = self.cleaned_data['total_offer_limit']
profile.save()
return profile
views.py
class SettingsPreferences(LoginRequiredMixin, BuyerAccessRequiredMixin, BuyerAdminAccessRequiredMixin, UpdateView):
template_name = 'invoicely/buyer/settings/buyer_settings.html'
form_class = BuyerSettingsForm
success_url = reverse_lazy('settings_preferences')
def get_object(self, *args, **kwargs):
return self.request.user.profile.buyer
def get_initial(self):
ctx = super(SettingsPreferences, self).get_initial()
ctx.update({
'total_offer_limit': self.object.total_offer_limit,
})
return ctx
def get_form_kwargs(self):
kwargs = super(SettingsPreferences, self).get_form_kwargs()
kwargs['request'] = self.request
return kwargs
def form_valid(self, form):
self.object = form.save()
messages.add_message(self.request, messages.SUCCESS, "Settings successfully updated")
return super(SettingsPreferences, self).form_valid(form)
If you are already overloaded get_form_kwargs you can do this. This is data which your form will be initialized with. So we can edit it before its initialization.
class SettingsPreferences(LoginRequiredMixin, BuyerAccessRequiredMixin, BuyerAdminAccessRequiredMixin, UpdateView):
...
def get_form_kwargs(self):
kwargs = super(SettingsPreferences, self).get_form_kwargs()
kwargs = copy.deepcopy(kwargs)
kwargs['request'] = self.request
if self.request.method in ('POST', 'PUT'):
# here put your data editing code
kwargs['data']['total_offer_limit'] = int(kwargs['data']['total_offer_limit'].strip().replace('$', ''))
return kwargs

Django ModelForm within DetailView

I have a DetailView which displays a Post. I now want to add the ability to create a Comment for a Post. For that I need a CommentForm, within the DetailView, so that I can create comments while being on the same page with a Post.
Is this possible, or should I be looking for another approach, like doing the form handling 'manually'?
class Comment(models.Model):
body = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
author_name = models.CharField(max_length=255)
parent_post = models.ForeignKey('Post',related_name='comments')
class PostDetailView(BlogMixin,DetailView):
""" A view for displaying a single post """
template_name = 'post.html'
model = Post
#Add some code for the CommentForm here?
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
exclude = ("parent_post","created_at")
def create_view(request, **kwargs):
if request.method == "POST":
parent_fk = request.args['parent_fk'] #Im hoping to find how this will work soon
form = CommentForm(request.POST)
if form.is_valid():
new_comment = form.save(commit=False)
new_comment.parent_post = parent_fk
new_comment.save()
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
** Alternative **
I have been trying to apply the solution - A better alternative - but I get
Exception Value: __init__() takes exactly 1 argument (3 given)
Exception Location: .../sitepackages/django/core/handlers/base.py in get_response, line 112
and have not been able to trace it yet.
class PostView(BlogMixin,DetailView):
""" A view for displaying a single post """
template_name = 'post.html'
model = Post
def get_context_data(self, **kwargs):
context = super(PostView, self).get_context_data(**kwargs)
context['form'] = CommentForm()
return context
class PostDetailView(View):
def get(self, request, *args, **kwargs):
view = PostView.as_view()
return view(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
view = PostComment.as_view()
return view(request, *args, **kwargs)
class PostComment( SingleObjectMixin , FormView):
template_name = 'post.html'
form_class = CommentForm
model = Post
def post(self, request, *args, **kwargs):
self.object = self.get_object()
return super(PostComment, self).post(request, *args, **kwargs)
def get_success_url(self):
return reverse('post-detail', kwargs={'pk': self.object.pk})
class BlogMixin(object):
"""
Basic mixin for all the views. Update the context with additional
information that is required across the whole site, typically
to render base.html properly
"""
def get_context_data(self, *args, **kwargs):
context = super(BlogMixin, self).get_context_data(*args, **kwargs)
blog = Blog.get_unique()
context.update({
'blog': blog,
'active_user': users.get_current_user(),
'is_admin': users.is_current_user_admin()
})
return context
urls.py:
url(r'^post/(?P[\d]+)/$', views.PostDetailView., name="post-detail"),
If you want to use your first method, you can make the FK a hidden field. In your view, you can save the FK before committing the comment to the database. Like this:
if form.is_valid():
comment = form.save(commit=False)
comment.parent_post = parent_post
comment.save()
Edit: If you want to fetch the comments, then you can use filter by post to get a QuerySet of the comments.
Why dont you send your form in the context in detail view:
class YourDetailView(DetailView):
#Your stuff here
def get_context_date(self, **kwargs):
context = super(YOurDetailView, self).get_context_data(**kwargs)
context['form'] = YourForm
return context
PS. Look for the parameters in get_context_date..
In the end I could not make it work with the redirect, but the following is working:
class PostDetailView(BlogMixin,CreateView):
""" A view for displaying a single post """
template_name = 'post.html'
model = Comment
fields = ['body','author_name']
def get_context_data(self, **kwargs):
context = super(PostDetailView, self).get_context_data(**kwargs)
context['post'] = Post.objects.get(pk=self.kwargs['pk'])
return context
def form_valid(self, form):
# self.object = form.save()
obj = form.save(commit=False)
obj.parent_post = Post.objects.get(pk=self.kwargs['pk'])
obj.save()
return redirect('post-detail', self.kwargs['pk'])

Categories