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
Related
I am trying to build a custom MultiValue field in django that consists of two widgets: RadioSelect and TextInput: if a user chooses 'Other' then they can insert the value there.
Everything works, with one weird exception: the labels for radio buttons are not shown (see picture). Values are rendered ok, but the labels are just not there. What I am doing wrong?
fields.py
from .widgets import OtherSelectorWidget
class OtherModelField(models.CharField):
def __init__(self, *args, **kwargs):
self.inner_choices = kwargs.pop('choices', None)
super().__init__(*args, **kwargs)
def formfield(self, **kwargs):
return OtherFormField(choices=self.inner_choices, **kwargs)
class OtherFormField(MultiValueField):
def __init__(self, **kwargs):
self.choices = kwargs.pop('choices')
self.widget = OtherSelectorWidget(choices=self.choices)
fields = (CharField(), CharField(),)
super().__init__(fields=fields, require_all_fields=False, **kwargs)
def compress(self, data_list):
return str(data_list)
widgets.py
from datetime import date
from django.forms import widgets
class OtherSelectorWidget(widgets.MultiWidget):
def __init__(self, choices=None, attrs=None):
self.choices = choices
_widgets = (
widgets.RadioSelect(choices=choices),
widgets.TextInput(attrs=attrs),
)
super().__init__(_widgets, attrs)
def decompress(self, value):
if value:
return [value[0], value[1]]
return [None, None, ]
def format_output(self, rendered_widgets):
return ''.join(rendered_widgets)
def value_from_datadict(self, data, files, name):
datelist = [
widget.value_from_datadict(data, files, name + '_%s' % i)
for i, widget in enumerate(self.widgets)]
radio_data = self.widgets[0].value_from_datadict(data, files, name + '_0')
text_data = self.widgets[1].value_from_datadict(data, files, name + '_1')
try:
D = [radio_data, text_data]
except ValueError:
return ''
else:
return D
it seems to be a glitch in Django. Here is a link to a ticket: https://code.djangoproject.com/ticket/29200
I dealt with it by adding wrap_label to widget's context:
class OtherSelectorWidget(widgets.MultiWidget):
def get_context(self, name, value, attrs):
con = super().get_context(name, value, attrs)
con['wrap_label'] = True
return con
Then everything is rendered properly
I am using model form and I'm trying to bypass validation of one particular field.
I have ascertained that I need use the clean() method to bypass the field. however when im printing out the cleaned data the assigned_subnets field is not in the dictionary, it is missing
actions:
I create the assigned subnet field manually in forms.py. Then using jquery I alter that form field and add more select options to it post all the options. The additional values posted were not part of the original field choices, hence the error.
output from print:
{'site_data': None, 'switches': None, 'hostname': 'STR--RTR-01', 'template': <ConfigTemplates: STR-RTR-01>, 'model': <DeviceModel: Cisco - 4431>, 'install_date': datetime.date(2016, 5, 26), 'ospf_area': None, 'snmp_data': <SNMPData: XXXX>, 'available_subnets': <QuerySet []>}
forms.py
class DeviceForm(forms.ModelForm):
class Meta:
model = DeviceData
fields = ['site_data', 'switches', 'hostname', 'template', 'model', 'install_date','ospf_area','snmp_data']
def clean(self):
super(DeviceForm, self).clean()
print(self.cleaned_data)
if self.cleaned_data.get('assigned_subnets') in self._errors:
del self._errors['assigned_subnets']
return self.cleaned_data
def __init__(self, *args, **kwargs):
site_id = kwargs.pop('site_id', None)
device_id = kwargs.pop('device_id', None)
self.is_add = kwargs.pop("is_add", False)
super(DeviceForm, self).__init__(*args, **kwargs)
devicesubnet = Subnets.objects.filter(devicesubnets__device_id=device_id)
sitesubnet = Subnets.objects.filter(sitesubnets__site_id=site_id)
common_subnets = list(set(devicesubnet) & set(sitesubnet))
subnet_id_list = []
for s in common_subnets: subnet_id_list.append(s.id)
available_subnet_data = sitesubnet.exclude(id__in=subnet_id_list)
assigned_choices = []
devicesubnet_data = DeviceSubnets.objects.filter(device_id=device_id)
for choice in devicesubnet_data:
assigned_choices.append((choice.subnet.id,choice.subnet.subnet))
self.fields['available_subnets'] = forms.ModelMultipleChoiceField(
label='Available Subnets',
queryset=available_subnet_data,
widget = forms.SelectMultiple(
attrs = {'class': 'form-control', 'size' : '15'}
)
)
self.fields['assigned_subnets'] = forms.MultipleChoiceField(
label='Assigned Subnets',
choices=assigned_choices,
widget = forms.SelectMultiple(
attrs = {'class': 'form-control', 'size' : '15'}
)
)
self.fields['available_subnets'].required = False
self.fields['assigned_subnets'].required = False
self.helper = FormHelper(self)
self.helper.form_id = 'device_form'
self.helper.form_method = 'POST'
...
views.py
class EditDevice(UpdateView):
model = DeviceData
form_class = DeviceForm
template_name = "config/device_form.html"
#method_decorator(user_passes_test(lambda u: u.has_perm('config.edit_device')))
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(EditDevice, self).dispatch(*args, **kwargs)
def get_success_url(self, **kwargs):
return reverse_lazy("config:device_details", args=(self.site_id,))
def form_valid(self, form):
form.instance.site_data = self.site
assigned_subnets = form.cleaned_data['assigned_subnets']
print(assigned_subnets)
return super(EditDevice, self).form_valid(form)
def get_form_kwargs(self, *args, **kwargs):
kwargs = super().get_form_kwargs()
kwargs['site_id'] = self.site_id
kwargs['device_id'] = self.object.pk
return kwargs
...
EDIT:
what im trying to do is like in the image below. I have a list of available subnets and a list of chosen (assigned) subnets.
I don think this form widget exists outside of Django admin? so Ive created the two fields manually and used jquery to move a subnet from available to assigned.
then I get the assigned subnets and update the DB. however I get the errors when I alter the assigned field
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
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'
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