I am trying to display the init value (State) in my form, but it keep showing the "-------" value.
I know the value is not empty because it saves the states into my database. It just wont show up in my form
My form class:
class AddressUpdateForm(BaseModelForm):
state = ProvinceModelChoiceField(required=False, queryset=CountryProvincePair.objects.all())
class Meta:
model = Address
fields = ('label', 'postal', 'zip', 'city', 'country', 'state', 'custombillto')
def __init__(self, *args, **kwargs):
super(AddressUpdateForm, self).__init__(*args, **kwargs)
print("THIS IS IT", self.instance.state)
self.fields['state'].instance = self.instance.state
My custom ModelChoiceField:
class ProvinceModelChoiceField(ModelChoiceField):
"""
A model choice field that accepts custom values for province class.
Should be used together with Chosen with extended functionality.
This model will accept everything that is not in its Query initial values
"""
def label_from_instance(self, obj):
return "%s, %s" % (obj.province, obj.country)
def to_python(self, value):
if value in self.empty_values:
return None
try:
key = self.to_field_name or 'pk'
value = self.queryset.get(**{key: value})
except (ValueError, TypeError, self.queryset.model.DoesNotExist):
# If the object does not exist, does not raise an error
pass
return value
BaseModelForm:
class BaseModelForm(ModelForm):
def __init__(self, *args, **kwargs):
super(BaseModelForm, self).__init__(*args, **kwargs)
for label, field_instance in self.fields.items():
if isinstance(field_instance, ModelChoiceField) or isinstance(field_instance, ChoiceField):
field_instance.widget.attrs['class'] = 'chosen'
field_instance.help_text = ''
if isinstance(field_instance, BaseTemporalField):
field_instance.widget.attrs['class'] = 'dp'
if isinstance(field_instance, NullBooleanField):
field_instance.widget.attrs['class'] = 'switch'
if isinstance(field_instance, BooleanField):
field_instance.widget.attrs['class'] = 'switch'
Related
I have a form with a required field customer_phone_number and despite passing a phone number in with the data the form isn't valid..
Submitting the form through a view works but not in the shell.
class OrderForm(forms.ModelForm):
class Meta:
model = models.Order
fields = (
'date',
'customer_phone_number',
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['customer_phone_number'].required = True
self.fields['customer_phone_number'].widget = PhoneNumberPrefixWidget()
data = {
'date': '2020-04-20',
'customer_phone_number': '+17168567800',
}
form = OrderForm(data=data)
form.is_valid()
print(form.errors) # {'customer_phone_number': ['This field is required.']}
The only thing I can think of is that I'm using a custom widget but I have no idea how to correct this error.
from phonenumber_field.phonenumber import PhoneNumber
from django.forms import Select, TextInput
from django.forms.widgets import MultiWidget
class PhonePrefixSelect(Select):
initial = '+1'
def __init__(self, attrs=None):
choices = [('+1', '+1')]
super().__init__(attrs, choices=sorted(choices, key=lambda item: item[1]))
def render(self, name, value, *args, **kwargs):
return super().render(
name, value or self.initial, *args, **kwargs)
class PhoneNumberPrefixWidget(MultiWidget):
"""
A Widget that splits phone number input into:
- a country select box for phone prefix
- an input for local phone number
"""
def __init__(self, attrs=None):
widgets = (
PhonePrefixSelect(attrs={
'class': 'form-control w-25 mr-2',
'tabindex': '-1'
}),
TextInput(attrs={
'class': 'form-control w-75'
}),
)
super().__init__(widgets, attrs)
def decompress(self, value):
if value:
if type(value) == PhoneNumber:
if value.country_code and value.national_number:
return ["+%d" % value.country_code, value.national_number]
else:
return value.split('.')
return [None, ""]
def value_from_datadict(self, data, files, name):
values = super().value_from_datadict(
data, files, name)
if all(values):
return '%s.%s' % tuple(values)
return ''
Solved by splitting customer_phone_number into two fields: customer_phone_number_0 and customer_phone_number_1
Currently Happening:
Dynamically generated form and form fields are being displayed.
Enter some data into the said fields, but self.get_all_cleaned_data() returns nothing.
Form returns to page 0 instead of submitting the form and using done()
What I want to happen:
- Data in fields to be retained and displayed when going back, or to the confirmation page
- Form to actually submit and use done() to process and save
The following the my forms.py
class OrderForm(forms.Form):
class Meta:
localized_fields = ('__all__',)
def __init__(self, *args, **kwargs):
self.fields = kwargs.pop('fields')
fields = self.fields
super(OrderForm, self).__init__(*args, **kwargs)
if not isinstance(fields, str):
for i in fields.fields.all():
widget = forms.TextInput()
_type = forms.CharField
if i.field_type == Field.TEXTAREA_FIELD:
widget = forms.Textarea
...
self.fields[i.name] = _type(**fields)
This is supposed to get Database created forms and field data and generate fields accordingly. For example:
Form A has fields:
Name (Regular Text Field)
Address (Textarea)
The above code will then generate fields for those.
The next block of code is from my views.py file
FORM_TEMPLATES = {
"0": 'order/details.html',
"1": 'order/details.html',
"2": 'order/details.html',
"3": 'order/details.html',
"4": 'order/details.html',
"confirm": 'order/confirm.html',
}
class Order(SessionWizardView):
form_list = [OrderForm]
def get_current_step_form(self, company, *args, **kwargs):
step_form = [Form.objects.all()]
step_form.append('Confirm')
return step_form
def get_context_data(self, form, **kwargs):
context = super(Order, self).get_context_data(form=form, **kwargs)
# Returns {}, but I want this to return all previous field values
context.update({
'all_data': self.get_all_cleaned_data(),
})
return context
def post(self, *args, **kwargs):
go_to_step = self.request.POST.get('wizard_goto_step', None)
form = self.get_form(data=self.request.POST)
current_index = self.get_step_index(self.steps.current)
goto_index = self.get_step_index(go_to_step)
if current_index > goto_index:
self.storage.set_step_data(self.steps.current,
self.process_step(form))
self.storage.set_step_files(self.steps.current,
self.process_step_files(form))
return super(Order, self).post(*args, **kwargs)
def get_form(self, step=None, data=None, files=None):
"""
Get the form and add to form_list
"""
form = super(Order, self).get_form(step, data, files)
company = ...
get_forms = self.get_current_step_form(company=company)
form_list_value = dict(self.form_list)['0']
while len(self.form_list.items()) < len(get_forms):
self.form_list.update({str(len(self.form_list.items())): form_list_value})
return form
def done(self, form_list, **kwargs):
return HttpResponse("View")
done() is a work in progress, but it doesn't even seem to reach that point, as it keeps going from (for example) Form 0-1-2-3-0-...
The confirm form will not have any field values form the previous pages and will only return {}
Any help would be appreciated,
Thanks
Is there a way to modify the data obtained from the model before inserting it in the form?
Here's my model:
class SomeData(models.Model):
Some_No = models.ForeignKey('SomeDef', on_delete=models.PROTECT)
Some_Val = models.DecimalField(max_digits=10, decimal_places=3, verbose_name=_('Some_Val'))
And here's my form:
#autostrip
class SomeForm(forms.ModelForm):
class Meta:
model = models.SomeData
fields = ('Some_Val', 'Some_No')
def __init__(self, *args, **kwargs):
super(SomeForm, self).__init__(*args, **kwargs)
self.fields['Some_No'].label = _(' ')
def clean_some(self):
some = None
some_id = self.cleaned_data['some']
some = models.SomeDef.objects.get(Some_No=some_id)
return some
def save(self, something, *args, **kwargs):
orig_commit = kwargs.get('commit', True)
kwargs['commit'] = False
ri = super(SomeForm, self).save(*args, **kwargs)
ri.Some_No = something
if orig_commit:
try:
ri.save()
except ValidationError as e:
raise ValidationError
return ri
The data saved inside of the models is a bit different from what I want to show in the forms when these are populated with data. However, I cannot figure out how to do it in a smart way.
Using the pre_save signal. Signals
You can set the initial value in the __init__ method:
def __init__(self, *args, **kwargs):
super(SomeForm, self).__init__(*args, **kwargs)
self.fields['Some_No'].initial = f(self.instance)
You can pass an initial data dictionary argument to your form, when you instantiate it into your view.
I would like not to display a field in form if I have a boolean field in database set to False.
Here is my code:
class CreateServer(ModelForm):
def __init__(self, g, *args, **kwargs):
super(CreateServer, self).__init__(*args, **kwargs)
if g.boolean_clients:
self.fields['clients'].queryset = Clients.objects.filter(game=g)
else:
# the fields['clients'] shouldn't be displayed in form
pass
...
class Meta:
model = Server
queryset = Server.objects.filter()
fields = ['hostname', 'clients', 'map']
So if g.boolean_clients is true, there must be the filter, but if g.boolean_clients is false I do not want to display this field in form.
Is there any way hot to do it?
I haven't tested this but try:
class CreateServer(ModelForm):
def __init__(self, g, *args, **kwargs):
super(CreateServer, self).__init__(*args, **kwargs)
if g.boolean_clients:
self.fields['clients'].queryset = Clients.objects.filter(game=g)
else:
self.fields.pop('clients')
class Meta:
model = Server
queryset = Server.objects.filter()
fields = ['hostname', 'clients', 'map']
I have a multivaluefield with a charfield and choicefield. I need to pass choices to the choicefield constructor, however when I try to pass it into my custom multivaluefield I get an error __init__() got an unexpected keyword argument 'choices'.
I know the rest of the code works because when I remove the choices keyword argument from __init__ and super, the multivaluefield displays correctly but without any choices.
This is how I setup my custom multivaluefield:
class InputAndChoice(object):
def __init__(self, text_val='', choice_val=''):
self.text_val=text_val
self.choice_val=choice_val
class InputAndChoiceWidget(widgets.MultiWidget):
def __init__(self, attrs=None):
widget = (widgets.TextInput(),
widgets.Select()
)
super(InputAndChoiceWidget, self).__init__(widget, attrs=attrs)
def decompress(self,value):
if value:
return [value.text_val, value.choice_val]
return [None, None]
class InputAndChoiceField(forms.MultiValueField):
widget = InputAndChoiceWidget
def __init__(self, required=True, widget=None, label=None, initial=None,
help_text=None, choices=None):
field = (
fields.CharField(),
fields.ChoiceField(choices=choices),
)
super(InputAndChoiceField, self).__init__(fields=field, widget=widget,
label=label, initial=initial, help_text=help_text, choices=choices)
And I call it like so:
input_and_choice = InputAndChoiceField(choices=[(1,'first'),(2,'second')])
So how do I pass the choices to my ChoiceField field?
Edit:
I've tried stefanw's suggestion but still no luck. I've used logging.debug to print out the contents of InputAndChoiceField at the end of the init and self.fields[1].choices contains the correct values as per above however it doesnt display any choices in the browser.
I ran into this exact same problem and solved it like this:
class InputAndChoiceWidget(widgets.MultiWidget):
def __init__(self,*args,**kwargs):
myChoices = kwargs.pop("choices")
widgets = (
widgets.TextInput(),
widgets.Select(choices=myChoices)
)
super(InputAndChoiceWidget, self).__init__(widgets,*args,**kwargs)
class InputAndChoiceField(forms.MultiValueField):
widget = InputAndChoiceWidget
def __init__(self,*args,**kwargs):
# you could also use some fn to return the choices;
# the point is, they get set dynamically
myChoices = kwargs.pop("choices",[("default","default choice")])
fields = (
fields.CharField(),
fields.ChoiceField(choices=myChoices),
)
super(InputAndChoiceField,self).__init__(fields,*args,**kwargs)
# here's where the choices get set:
self.widget = InputAndChoiceWidget(choices=myChoices)
Add a "choices" kwarg to the widget's constructor. Then explicitly call the constructor after the field is created.
ModelChoiceField is technically a ChoiceField, but it doesn't actually use any of the ChoiceField's implementations. So, here's how I use it.
class ChoiceInputMultiWidget(MultiWidget):
"""Kindly provide the choices dynamically"""
def __init__(self, attrs=None):
_widget = (
Select(attrs=attrs),
TextInput(attrs=attrs)
)
super().__init__(_widget, attrs)
class ModelChoiceInputField(MultiValueField):
widget = ChoiceInputMultiWidget
def __init__(self, *args, **kwargs):
_fields = (
ModelChoiceField(queryset=Type.objects.all()),
CharField()
)
super().__init__(_fields, *args, **kwargs)
# Use the auto-generated widget.choices by the ModelChoiceField
self.widget.widgets[0].choices = self.fields[0].widget.choices
Have a look at the source of __init__ of forms.MultiValueField:
def __init__(self, fields=(), *args, **kwargs):
super(MultiValueField, self).__init__(*args, **kwargs)
# Set 'required' to False on the individual fields, because the
# required validation will be handled by MultiValueField, not by those
# individual fields.
for f in fields:
f.required = False
self.fields = fields
So I would overwrite the __init__ probably like this:
def __init__(self, *args, **kwargs):
choices = kwargs.pop("choices",[])
super(InputAndChoiceField, self).__init__(*args, **kwargs)
self.fields = (
fields.CharField(),
fields.ChoiceField(choices=choices),
)
You might even want to do super(MultiValueField, self).__init__(*args, **kwargs) instead of super(InputAndChoiceField, self).__init__(*args, **kwargs) because you are setting the fields yourself instead of getting them via parameters.
passing the choices in the widget solved this for me
class InputAndChoiceWidget(widgets.MultiWidget):
def __init__(self, attrs=None):
choices = [('a', 1), ('b', 2)]
widget = (widgets.TextInput(),
widgets.Select(choices=choices)
)
super(InputAndChoiceWidget, self).__init__(widget, attrs=attrs)
class HTML5DateInput(DateInput):
input_type = 'date'
class CustomSelectRangeWidget(forms.MultiWidget):
def __init__(self, attrs=None, choices = ()):
widgets = (Select(attrs=attrs, choices=choices), HTML5DateInput(attrs=attrs), HTML5DateInput(attrs=attrs))
super(CustomSelectRangeWidget, self).__init__(widgets, attrs)
def decompress(self, value):
if value:
return [value.field, value.start, value.stop]
return [None, None, None]
def format_output(self, rendered_widgets):
return '-'.join(rendered_widgets)
class CustomSelectRangeField(forms.MultiValueField):
widget = CustomSelectRangeWidget
def __init__(self, *args, **kwargs):
if kwargs.has_key('choices') :
choices = kwargs.pop('choices')
else:
choices = ()
fields = (
forms.ChoiceField(choices=choices), #field with choices,
# so that clean can be passed
forms.DateField(),
forms.DateField(),
)
super(CustomSelectRangeField, self).__init__(fields=fields, *args, **kwargs)
#initialize widget with choices.
self.widget = CustomSelectRangeWidget(choices=choices)
def compress(self, data_list):
if data_list:
#check if datalist has 3 not null values
if len([v for v in data_list if v not in [None, '']]) == 3:
out_dict = {'field':data_list[0], 'start':data_list[1], 'stop':data_list[2]}
return out_dict
return None