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].
Related
I created a CBV of which I want to remove one or more fields, depending on the user. The idea is a jobsite and if the logged in user is a recruiter, than the employer field should be included, otherwise it should be excluded.
forms.py
class JobCreationForm(forms.ModelForm):
class Meta:
model = Job
# exclude = ['posted', 'provider', 'ext_id']
fields = ('title',
'job_desc',
'agency_name',
'employer',
'contact_name',
)
views.py
class JobCreateView(LoginRequiredMixin, CreateView):
template_name = 'job/job.html'
form_class = JobCreationForm
success_url = '/'
def get_context_data(self, **kwargs):
context = super(JobCreateView, self).get_context_data(**kwargs)
# import the Customers of this Company
self.fields["agency_name"].remove()
recruiter = self.request.user
self.fields["contact_name"].queryset = Profile.objects.filter(user_id = self.request.user)
# if the user is a recruiter, delete the employer field.
if Company.objects.filter(user_id = self.request.user).values('is_recruiter') == False:
pass
# self.fields.remove("employer")
del self.fields["employer"]
return context
The current error is NoneType' object has no attribute '__getitem__'.
My question: how can I remove a field from the form based on logic? I tried these versions:
self.fields["employer"].delete()
self.fields.remove("employer")
del self.fields["employer"]
Any tips?
The correct way to implement this (modify the fields of the form depending on user) is to do it on your form's __init__ method. However in order for the form to access the current user you need to pass the user to it from your view. To do this you'll use the get_form_kwargs method. Thus, start by adding the following method to your view:
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs.update({'user': self.request.user})
return kwargs
And now, you can add an __init__ to your form like this:
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
if Company.objects.filter(user_id = self.user).is_recruiter == False:
self.fields.pop("employer")
self.fields.pop('owned_by')
Notice that you first initialize the form (using super.__init__) and then you can modify the fields to your heart's content.
There are few ways to go about it.
I find having 2 separate forms RecruiterEmployeeForm and EmployeeForm may be neater.
class RecruiterEmployeeForm(forms.ModelForm):
model = Job
fields = ('title',
'job_desc',
'agency_name',
'employer',
'contact_name',
)
class EmployeeForm(forms.ModelForm):
model = Job
fields = ('title',
'job_desc',
'agency_name',
'contact_name',
)
Then you can override ger_form_class for the CBV
def get_form_class(self):
if self.request.user.is_recruiter():
return RecruiterEmployeeForm
else:
return EmployeeForm
To send extra kwargs to use generic view method get_form_kwargs and to get extra kwargs override __init__ of form and pop the extra kwargs.
forms.py
class JobCreationForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super(JobCreationForm, self).__init__(*args, **kwargs)
if Company.objects.filter(user_id = self.user).is_recruiter == False:
self.fields.pop("employer")
class Meta:
model = Job
# exclude = ['posted', 'provider', 'ext_id']
fields = ('title', 'job_desc', 'agency_name', 'employer', 'contact_name')
views.py
class JobCreateView(LoginRequiredMixin, CreateView):
template_name = 'job/job.html'
form_class = JobCreationForm
success_url = '/'
def get_form_kwargs(self):
kwargs = super(JobCreateView, self).get_form_kwargs()
kwargs.update({'user': self.request.user})
return kwargs
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.
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
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
I have the following view below. I would like to use 'email' from the get_context_data method to pre-populate a form field in my UserAuthenticationForm form, how is this possible?
View:
class CampaignLoginView(LoginView):
model = Campaign
form_class = UserAuthenticationForm
def get_context_data(self, *args, **kwargs):
context = super(CampaignLoginView, self).get_context_data(*args, **kwargs)
user = AppUser.objects.get(pk=1)
context['email'] = user.email
Form:
class UserAuthenticationForm(AuthenticationForm):
def __init__(self, *args, **kwargs):
super(UserAuthenticationForm, self).__init__(*args, **kwargs)
Or is there is a way 'I should be doing it'?
What you want to do is pass initial data to the form. Assuming that LoginView is a class-based view that includes the FormMixin, this is best done in the get_form_kwargs method:
def get_form_kwargs(self):
kwargs = super(CampaignLoginView, self).get_form_kwargs()
kwargs['initial']['email'] = self.request.user.email
return kwargs