Dynamically populating choicefield in Django - python

I can't initialize the Choicefield form inside the views.py. I tried passing the option variable in the __init__ function but I got an error:
__init__() takes at least 2 arguments (1 given)` coming from the `form = super(SomeeWizard, self).get_form(step, data, files)
forms.py
class SomeForm(forms.Form):
def __init__(self, choice, *args, **kwargs):
super(SomeForm, self).__init__(*args, **kwargs)
self.fields['choices'] = forms.ChoiceField(choices=[ (o.id, str(o)) for o in choice])
views.py
class SomeWizard(SessionWizardView):
def get_form(self, step=None, data=None, files=None):
form = super(SomeWizard, self).get_form(step, data, files)
if step == "step2":
option = self.get_cleaned_data_for_step("step1")['choice']
choice = Choice.objects.filter(question__text__exact=option)
form = SomeForm(choice)
return form
def get_template_names(self):
return [TEMPLATES[self.steps.current]]
def done(self, form_list, **kargs):
return render_to_response('done.html')
EDIT
I tried Hasan solution and Django Form Wizard is passing {'files': None, 'prefix': 'step2', 'initial': {}, 'data': None} into the **kwarg in the __init__ function of SomeForm.
I printed the content in the **kwarg and I got:
{'files': None, 'prefix': 'step2', 'initial': {}, 'data': None}
{'choices': [<Choice: Blue>, <Choice: Red>]}

try this change(I comment changed line):
forms.py:
class SomeForm(forms.Form):
def __init__(self, *args, **kwargs): #this line changed
choice = kwargs.pop('choice', None) #this line added
self.fields['choices'] = forms.ChoiceField(choices=[ (o.id, str(o)) for o in choice])
super(SomeForm, self).__init__(*args, **kwargs)
views.py:
class SomeWizard(SessionWizardView):
def get_form(self, step=None, data=None, files=None):
form = super(SomeWizard, self).get_form(step, data, files)
if step == "step2":
option = self.get_cleaned_data_for_step("step1")['choice']
choice = Choice.objects.filter(question__text__exact=option)
form = SomeForm(choice=choice) #this line changed
return form
def get_template_names(self):
return [TEMPLATES[self.steps.current]]
def done(self, form_list, **kargs):
return render_to_response('done.html')

FYI I had to init the form with the data attribute for this to work. For example:
class SomeWizard(SessionWizardView):
def get_form(self, step=None, data=None, files=None):
form = super(SomeWizard, self).get_form(step, data, files)
# determine the step if not given
if step is None:
step = self.steps.current
if step == "2":
option = self.get_cleaned_data_for_step("1")['choice']
choice = Choice.objects.filter(question__text__exact=option)
## Pass the data when initing the form, which is the POST
## data if the got_form function called during a post
## or the self.storage.get_step_data(form_key) if the form wizard
## is validating this form again in the render_done methodform
form = SomeForm(choice=choice, data=data)
return form
def get_template_names(self):
return [TEMPLATES[self.steps.current]]
def done(self, form_list, **kargs):
return render_to_response('done.html')
Otherwise when the form was submitted it just returned my back to this first form. For a full explanation see here. I am using django 1.8 though.

Related

Why is this django form not valid?

Why is this form not validating? It is not even calling the clean() method.
forms.py:
class SingleSampleForm(forms.Form):
sample_id = forms.CharField(label='Sample ID:')
class Meta:
fields = ('sample_id',)
def __init__(self, *args, **kwargs):
super(SingleSampleForm, self).__init__()
self.helper = FormHelper()
self.helper.layout = Layout(
Field('sample_id',
css_class="search-form-label",),
Submit('submit', 'Search sample', css_class='upload-btn')
)
self.helper.form_method = 'POST'
def clean(self):
print('CLEAN')
sample_id = self.cleaned_data['sample_id']
if sample_id:
return sample_id
raise ValidationError('This field is required')
views.py:
class SampleView(View):
sample_form = SingleSampleForm
def get(self, request, *args, **kwargs):
sample_form = self.sample_form()
self.context = {'sample_form': sample_form,}
return render(request,
'results/single_sample_search.html',
self.context)
def post(self, request, *args, **kwargs):
self.sample_form = self.sample_form(request.POST)
if self.sample_form.is_valid():
print('Valid')
else:
print('not valid')
self.context = {
'sample_form': self.sample_form,
}
return render(request,
'results/single_sample_search.html',
self.context)
I don't understand why it is not even calling the clean() method. I have another form which is almost identical which validates. When I do print dir(self.sample_form) after I have passed the request.POST dict it states that validation=unknown. Why is this? How do I check the reason it is not validating?
You need to pass *args and **kwargs when you call super():
def __init__(self, *args, **kwargs):
super(SingleSampleForm, self).__init__(*args, **kwargs)
At the moment, calling __init__ without any *args or **kwargs is equivalent to calling with data=None. The form is unbound, so will never be valid.

Django CBV - set form class based on permissions?

I have created two forms in forms.py one form has less fields than the other.
what I would like to now do is get the current users permissions and set the form class of the CBV based on those perms.
below is my current view:
class EditCircuit(UpdateView):
model = Circuits
# if user_passes_test(lambda u: u.has_perm('config.edit_circuit')))
form_class = CircuitForm
# else
# form_class = CircuitFormRestricted
template_name = "sites/circuit_form.html"
#method_decorator(user_passes_test(lambda u: u.has_perm('config.edit_circuit')))
def dispatch(self, *args, **kwargs):
self.site_id = self.kwargs['site_id']
self.site = get_object_or_404(SiteData, pk=self.site_id)
return super(EditCircuit, self).dispatch(*args, **kwargs)
def get_success_url(self, **kwargs):
return reverse_lazy("sites:site_detail_circuits", args=(self.site_id,))
def form_valid(self, form):
form.instance.site_data = self.object.site_data
return super(EditCircuit, self).form_valid(form)
def get_form_kwargs(self, *args, **kwargs):
kwargs = super().get_form_kwargs()
return kwargs
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['SiteID']=self.site_id
context['SiteName']=self.site.location
context['FormType']='Edit'
context['active_circuits']='class="active"'
return context
You can override the get_form_class method.
def get_form_class(self):
if self.request.user.has_perm('config.edit_circuit'):
return CircuitForm
return CircuitFormRestricted
Also, it looks as though you don't need the get_form_kwargs definition, as it's not doing anything at the moment.
you can create your own mixin like this
class AuthorOnlyMixin(object):
def has_permissions(self):
return self.get_object().created_by == self.request.user
then use it like this
class EditViewClass(AuthorOnlyMixin, EditView):
def get_form_class(self):
if self.has_permissions():
return FormWithPermission
else:
return FormWithoutPermission

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

Passing request object from view to form in Django

I'm trying to create an account edit page which visually contains a single form (ie single submit button) but the fields are part of two (or more) different models. I've pieced together a solution from several SO answers and the form loads fine on GET requests but I would now like to conditionally hide/display the terms of service checkbox field based on what url is being accessed. Specifically on registration the TOS should be displayed while it should not on the account edit page. Simplified code looks like so:
# views.py
class _RequestPassingFormView(FormView):
http_method_names = ['get', 'post', 'head', 'options', 'trace']
def get(self, request, *args, **kwargs):
# Pass request to get_form_class and get_form for per-request
# form control.
form_class = self.get_form_class(request)
form = self.get_form(form_class)
return self.render_to_response(self.get_context_data(form=form))
def post(self, request, *args, **kwargs):
# Pass request to get_form_class and get_form for per-request
# form control.
form_class = self.get_form_class(request)
form = self.get_form(form_class)
if form.is_valid():
# Pass request to form_valid.
return self.form_valid(request, form)
else:
return self.form_invalid(form)
def get_form_class(self, request=None):
return super(_RequestPassingFormView, self).get_form_class()
def get_form_kwargs(self, request=None, form_class=None):
return super(_RequestPassingFormView, self).get_form_kwargs()
def get_initial(self, request=None):
return super(_RequestPassingFormView, self).get_initial()
def get_success_url(self, request=None, user=None):
# We need to be able to use the request and the new user when
# constructing success_url.
return super(_RequestPassingFormView, self).get_success_url()
def form_valid(self, form, request=None):
return super(_RequestPassingFormView, self).form_valid(form)
def form_invalid(self, form, request=None):
return super(_RequestPassingFormView, self).form_invalid(form)
class AccountEditView(_RequestPassingFormView):
form_class = AccountEditForm
template_name = 'account_edit.html'
def form_valid(self, request, form):
success_url = self.get_success_url(request, new_user)
try:
to, args, kwargs = success_url
return redirect(to, *args, **kwargs)
except ValueError:
return redirect(success_url)
def get_success_url(selfself,request, user):
return '/account'
#forms.py
class CombinedFormBase(forms.Form):
form_classes = []
def __init__(self, *args, **kwargs):
super(CombinedFormBase, self).__init__(*args, **kwargs)
for f in self.form_classes:
name = f.__name__.lower()
setattr(self, name, f(*args, **kwargs))
form = getattr(self, name)
self.fields.update(form.fields)
self.initial.update(form.initial)
def is_valid(self):
isValid = True
for f in self.form_classes:
name = f.__name__.lower()
form = getattr(self, name)
if not form.is_valid():
isValid = False
# is_valid will trigger clean method
# so it should be called after all other forms is_valid are called
# otherwise clean_data will be empty
if not super(CombinedFormBase, self).is_valid() :
isValid = False
for f in self.form_classes:
name = f.__name__.lower()
form = getattr(self, name)
self.errors.update(form.errors)
return isValid
def clean(self):
cleaned_data = super(CombinedFormBase, self).clean()
for f in self.form_classes:
name = f.__name__.lower()
form = getattr(self, name)
cleaned_data.update(form.cleaned_data)
return cleaned_data
class RegistrationForm(forms.Form):
required_css_class = 'required'
email = forms.EmailField(label=_('E-mail'))
password1 = forms.CharField(widget=forms.PasswordInput,
label=_('Password'))
password2 = forms.CharField(widget=forms.PasswordInput,
label=_('Password (again)'))
"""
Conditionlly display TOS checkbox based on context
"""
def __init__(self, *args, **kwargs):
"""
add in a field for terms of service here if viewing
the registration form
"""
super(RegistrationForm, self).__init__(*args, **kwargs)
class AccountProfileForm(forms.Form):
required_css_class = 'required'
company = forms.CharField(label=('Company Name'))
class AccountEditForm(CombinedFormBase):
form_classes = [RegistrationForm, AccountProfileForm]
This is my first django project so it's possible that this is a completely wrong direction also. If so a hint towards a simpler solution would be appreciated.
As you are using class based view, I think you can try like this:
view:
class _RequestPassingFormView(FormView):
http_method_names = ['get', 'post', 'head', 'options', 'trace']
def get(self, request, *args, **kwargs):
form_class = self.get_form_class()
self.object = None
form = form_class(request_data=request)
return self.render_to_response(self.get_context_data(form=form))
Or like this:
class _RequestPassingFormView(FormView):
http_method_names = ['get', 'post', 'head', 'options', 'trace']
#No need to override the get method in this view.
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs.update(request_data=self.request)
return kwargs
And form is like this:
class RegistrationForm(forms.Form):
#form fields
def __init__(self, *args, request_data=None, **kwargs):
super().__init__(*args, **kwargs)
print(request_data)
# do other operations
I'm gonna keep it super simple. If you need further details, please refer here:
def my_view(request):
if request.method == 'POST':
# Sending your request info as kwarg.
form = myform(request.POST, user=request.user)
class MyForm(forms.Form):
def __init__(self, *args, **kwargs):
# Recieving it.
self.user = (kwargs.pop('user', None))
super(MyForm, self).__init__(*args, **kwargs)
You may want to consider just having two separate forms. If the only difference is the one on the registration page has a terms of service checkbox, then that form class could inherit from the other class and have the extra form field.

django cant use global variable in class based view

my view is like this
class RecordView(View):
record_form = RecordForm
record_files = {}
templare = 'acquisition.html'
def get(self, request, *args, **kwargs):
fil = urllib.urlopen('/home/student/wwww.jpg')
self.record_files = {'small_cover': SimpleUploadedFile('hehe.jpg', fil.read())}
rr_form = self.record_form()
return render(request, self.template_name, {'rr_form': rr_form,
})
def post(self, request, *args, **kwargs):
record = RecordForm(request.POST, self.record_files)
record.save()
HttpResponseRedirect('/')
Here i have populated self.record_files in get method.. but after i post data i see self.record_files as empty dictionary. I get confused here. What can i do to do so.
The state of your view instance is not maintained between a get and a post, so setting record_files on the instance will not keep it for the next request. You would need to put that logic in the dispatch method, or store information in the user's session.
class RecordView(View):
record_form = RecordForm
record_files = {}
templare = 'acquisition.html'
# dispatch is called before deciding whether to use get() or post()
# so any instance-level properties that require the request can go here.
# This could even go in __init__().
def dispatch(self, request, *args, **kwargs):
fil = urllib.urlopen('/home/student/wwww.jpg')
self.record_files = {'small_cover': SimpleUploadedFile('hehe.jpg', fil.read())}
return super(RecordView, self).dispatch(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
rr_form = self.record_form()
return render(request, self.template_name, {'rr_form': rr_form,
})
# self.record_files will be available in a get, or a post (or any valid
# method for that matter).
def post(self, request, *args, **kwargs):
record = RecordForm(request.POST, self.record_files)
record.save()
return HttpResponseRedirect('/')
That is exactly how it suppose to work. Whenever you have a GET request, it will call method get, and if it is a POST request, naturally it will call post method, but not the get method. So here is how you can solve your problem:
from django.views.generic import TemplateView
class RecordView(TemplateView):
record_form = RecordForm
record_files = {}
template_name = 'acquisition.html'
def get_context_data(self, **context):
fil = urllib.urlopen('/home/student/wwww.jpg')
self.record_files = {
'small_cover': SimpleUploadedFile('hehe.jpg', fil.read())
}
context.update({
'record_files': self.record_files,
'rr_form': self.record_form()
})
fil.close()
return super(RecordView, self).get_context_data(**context)
def post(self, request, *args, **kwargs):
context = self.get_context_data()
record_form = self.record_form(request.POST, self.record_files)
if record_form.is_valid():
record_form.save()
## or do a redirect instead, like you had before:
# return HttpResponseRedirect('/')
context['rr_form'] = record_form
return self.render_to_response(context)

Categories