I have been subclassing multiwidget to create an HTML5 range slider synchronised with a NumberInput field using this code:
class SplitRangeNumberWidget(MultiWidget):
def __init__(self):
widgets = (
forms.NumberInput(attrs={'type':'range',
'onchange':'this.nextElementSibling.value=this.value',
'oninput':'this.nextElementSibling.value=this.value',
'step':'any',
'min':0,
'max':1}),
forms.NumberInput(attrs={'step':'any',
'onchange':'this.previousElementSibling.value=this.value',
'oninput':'this.previousElementSibling.value=this.value',})
)
super(SplitRangeNumberWidget, self).__init__(widgets)
def decompress(self, value):
if value:
return [value, value]
return [None, None]
When I instanciate it and use it in a Form such as:
class ParameterForm(forms.ModelForm):
class Meta:
model = Parameter
fields = ['name','value','min_value','max_value']
widgets = {'value':SplitRangeNumberWidget()}
, the widget works fine: changing the slider or the numberinput does change the other field. However, when doing a POST, the form does not validate and I get this error in form.errors (for 3 parameters):
[{'value': ['Enter a number.']}, {'value': ['Enter a number.']},
{'value': ['Enter a number.']}]
The widgets alone do work well and form is correctly bound. But not in a multiwidget. What am I doing wrong? I added
def value_from_datadict(self, data, files, name):
num = [widget.value_from_datadict(data, files, name + '_%s' % i)
for i, widget in enumerate(self.widgets)]
return [float(num[0]), float(num[1])]
but this still does not work.
Thanks for your help.
I found the solution: I needed to implement
def value_from_datadict(self, data, files, name):
value_list = [widget.value_from_datadict(data, files, name + '_%s' % i)
for i, widget in enumerate(self.widgets)]
try:
value = value_list[0]
except ValueError:
return None
else:
return value
To paraphrase the documentation: The default implementation of value_from_datadict() returns a list of values corresponding to each Widget. This is appropriate when using a MultiWidget with a MultiValueField, but since we want to use this widget with a TextField which takes a single value, we have overridden this method to combine the data of all the subwidgets into a value.
Related
I looked through every single similar question on stackoverflow and tried nearly everything.
It seems easy to do: I just want to allow , as decimal separator for a FloatField in the Django admin interface. At the moment, it depends on the localization, but I always want to allow it. It would even be ok for me, if it's just a TextInput, but I need , to work. Setting DECIMAL_SEPARATOR in settings.py does not work.
My question is similar to this 6 year old, unanswered one: How to only override DECIMAL_SEPARATOR in django while keeping localization intact?
I managed to use the TextInput widget for FloatFields like this:
class ExampleAdminForm(forms.ModelForm):
class Meta:
model = Example
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for key, value in self.fields.items():
if isinstance(value, FloatField):
self.fields[key].widget = TextInput()
The widget works, but an input like 1,23 leads to an error message Enter a number. I'd have to implement my own, custom FloatField that overrides the sanitizing in to_python to allow for differenct decimal separators, but then I'd still need a different widget. This seems awfully complicated, is there a better way?
If not, how can I change the frontend validation in the widget to ignore localization and use my own regex pattern?
Patch the to_python method, or use a custom form field class that overrides that method.
One-liner answer:
self.fields[key].to_python = lambda v: self.fields[key].__class__.to_python(self.fields[key], '.'.join(v.rsplit(',', 1)) if len(v.rsplit(',', 1)[-1]) < 3 else v)
As a wrapper function:
self.fields[key].to_python = allow_comma_decimal_separator(self.fields[key].to_python)
def allow_comma_decimal_separator(old_to_python):
def to_python(value):
if ',' in value:
lvalue, decimals = value.rsplit(',', 1)
if len(decimals) < 3:
value = '.'.join((lvalue, decimals))
return old_to_python(value)
return to_python
i.e. If a comma is in the value, and the substring after the rightmost comma has a length of less than 3 (so we assume it's not a thousand separator), then we replace that comma with a period by joining the substring before and after with a period.
For explicit fields using a reusable form field class
This would be considered less "hacky".
class AllowCommaDecimalSeparatorFloatField(forms.FloatField):
def to_python(self, value):
if ',' in value:
lvalue, decimals = value.rsplit(',', 1)
if len(decimals) < 3:
value = '.'.join((lvalue, decimals))
return super().to_python(value)
In your form's Meta class:
field_classes = {
'myfield': AllowCommaDecimalSeparatorFloatField,
}
For all FloatFields
To affect all FloatField instances (not instantiated yet), place this at the top of your module:
old_to_python = forms.FloatField.to_python
def to_python(self, value):
if ',' in value:
lvalue, decimals = value.rsplit(',', 1)
if len(decimals) < 3:
value = '.'.join((lvalue, decimals))
return old_to_python(self, value)
forms.FloatField.to_python = to_python
I'm trying to compute a value to be displayed in a tree view, the problem is that my private function never gets executed and not setting the value for my computed field.
I've simplified the following code:
class ProjectProject(models.Model):
_inherit = "project.project"
assigned = fields.Char(string='Assigned multi', compute='_roles_assigned', store=False)
#api.multi
#api.depends('task_ids')
def _roles_assigned(self):
#do dome calculations
assigned = ' test of 1' #'0 / {total}'.format(total=total)
return assigned
as you see in the image the value is always blank
When we display computed field in tree view, it will have multiple records set. So we have to set value for each record set.
Try with following code:
#api.multi
def _roles_assigned(self):
#do dome calculations
for record in self:
assigned = ' test of 1' #'0 / {total}'.format(total=total)
record.assigned = assigned
You have to iterate over the records and assign some value to it, check the code below.
#api.multi
def _roles_assigned(self):
for rec in self:
rec.assigned = 'assign your value here'
If have a date picker form that filters a set of models (Sonde) and populates a ModelChoicesField. This works correctly in terms of date choice in my app, but on my canvas I constantly get the error:
Select a valid choice. That choice is not one of the available choices.
I do the init, to filter the available instances of Sonde and populate the choices of the ModelChoiceField.
From my forms.py
class date_choice(forms.Form):
avSonden = forms.ModelChoiceField(queryset = Sonde.objects.none())
def __init__(self, *args, **kwargs):
currentUserID = kwargs.pop('currentUserID', None)
super(date_choice, self).__init__(*args, **kwargs)
if currentUserID:
self.fields['avSonden'].queryset = Sonde.objects.filter(owned_by__Kundennummer = currentUserID).values_list("Serial",flat=True).distinct()
start = forms.DateField(input_formats=['%Y-%m-%d'])
end = forms.DateField(input_formats=['%Y-%m-%d'])
I had to force the clean() to ignore my change from PK to other identifier:
def clean_status(self):
#valid if a value has been selected
if self["avSonden"].value()!="":
del self._errors["avSonden"]
return self["avSonden"].value()
So I have a ModelForm with a select field. In this select field there are about 100 entries. Of these entries I have a number of entries like "Organization Colorado - Denver", which I would like to have at the top of the list, such that all the entries with "Organization Colorado" are at the top of the list, and everything else is sorted in lexicographical order.
I've tried making two separate querysets (this seems like a bad idea, but manageable with only 100 or so entries). There seems to be a lot of ways of combining these two query sets, but without maintaining the order (which is the point). I've tried this:
class CreateContactForm(ModelForm):
...
def __init__(self, *args, **kwargs):
super(CreateContactForm, self).__init__(*args, **kwargs)
p = models.ConstantContactList.objects.filter(
name__startswith=settings.PREF_ORGANIZATION_PREFIX
)
np = models.ConstantContactList.objects.filter(
name__regex=r'^(?!{})'.format(settings.PREF_ORGANIZATION_PREFIX)
).order_by('-name')
self.fields['cc_lists'].queryset = list(p) + list(np)
This doesn't work, although it might, if there was some way to convert that list back into a queryset, or if there is a way to go around the queryset maybe? I'm not sure. Can anyone provide a clue as to what I should do?
I would recommend against trying to order the querysets and just handle sorting in the rendering layer (templates or forms). This way if you want to localize your code you won't have to change your queries.
Assuming you use a forms.Select widget. You may want to inherit from this widget and override the render_menu logic in order to construct it yourself and handle the ordering yourself. You'll have access to the rendered or unrendered options, so it shouldn't be an issues from that point on.
OK. I've come up with a solution that works here, and I'm providing it to anyone else who has the same need.
from django.forms.widgets import Select
import re
class CustomOrderingWidget(Select):
def __init__(self, priority_regex, sort='+', attrs=None):
super(CustomOrderingWidget, self).__init__(attrs)
self.regex = re.compile(priority_regex)
self.sort = sort
self.template_name = 'django/forms/widgets/select.html'
def render(self, name, value, attrs=None, renderer=None):
context = self.get_context(name, value, attrs)
optgroups = context.get('widget').get('optgroups')
firsts, others = [], []
for grp in optgroups:
if self.regex.search(grp[1][0].get('label')):
firsts.append(grp)
else:
others.append(grp)
if self.sort == '+':
kfn = lambda x: x[1][0].get('label')
context['widget']['optgroups'] = sorted(firsts, key=kfn) +\
sorted(others, key=kfn)
elif self.sort == '-':
kfn = lambda x: x[1][0].get('label')
context['widget']['optgroups'] =\
sorted(firsts, key=kfn, reverse=True) +\
sorted(others, key=kfn, reverse=True)
else:
context['widget']['optgroups'] = firsts + others
return self._render(self.template_name, context, renderer)
Then you can plug it into a ModelForm like this...
import settings # YOUR personal stuffz!
class CreateContactForm(ModelForm):
...
class Meta:
...
widgets = {
# Just an example, make your own regex string!
'cc_lists': CustomOrderingWidget("^{0}".format(
settings.PREF_ORGANIZATION_PREFIX
))
}
I have a form with checkboxes, the form functioning well, in my view i can use request.POST.getlist('list') to retrieve the list of values.
At the moment am trying to do some form validation inside the clean method and when i try to use self.cleaned_data['list'] I get the last value. I cannot retrieve the list of items.
Any idea how i could do that?
forms.py
class SelectList_Form(forms.Form):
list = forms.CharField(required=False)
def clean(self):
super(SelectList_Form, self).clean()
cleaned_data = self.cleaned_data
try:
# TODO: list validation
if cleaned_data['list'].__len__() is 0:
raise forms.ValidationError(_('Must select at least one of the lists below'),)
if cleaned_data['list'].__len__() > 1:
try:
# In here when i print list it only shows me the last value. It doesn't show me the list of values when the box is checked
print cleaned_data['list']
except Main.DoesNotExist:
raise Http404
except forms.ValidationError:
raise
class Posting_Wizard(FormWizard):
def render_template(self, request, form, previous_fields, step, context=None):
if step == 0:
obj = MainI18n.objects.filter(main__is_active=True, language=request.LANGUAGE_CODE).\
exclude(main__parent=None).order_by('main__parent').select_related(depth=1)
category_choices=dict(['%s,%s' % (i.main.slug, i.main.parent.slug), '%s - %s' % (i.main.parent,i.label)] for i in obj)
form.fields['categories'] = forms.CharField(widget=forms.RadioSelect(choices=category_choices.items()))
if step == 1:
category = request.POST.get('0-categories')
pobj = Main.objects.filter(slug=category.split(',')[1], parent=None).get()
cobj = Main.objects.filter(slug=category.split(',')[0], parent=pobj.id).get()
lobj = ListI18n.objects.filter(list__is_active=True, language=request.LANGUAGE_CODE, list__main__slug=category.split(',')[0], list__main__parent=pobj.id).select_related()
list_choices = dict([i.id, i.title] for i in lobj)
if cobj.mainproperties.relation == 'M':
# Here i generate the checkboxes
form.fields['list']=forms.CharField(widget=forms.CheckboxSelectMultiple(choices=list_choices.items()),label="Pick the list",)
else:
form.fields['list']=forms.CharField(widget=forms.RadioSelect(choices=list_choices.items()),label="Pick the list",)
return super(Posting_Wizard, self).render_template(request, form, previous_fields, step, context)
def done(self, request, form_list):
return HttpResponseRedirect(reverse('accounts-registration-wizard-done'))
def get_template(self, step):
return 'listing/post/wizard/wizard_%s.html' % step
First, there are a number of basic Python errors here. There is almost never a need to access the double-underscore functions - they are internal implementation details. Always use the normal len() function instead. And, never use is for comparisons: it's for identity, so should only be used with things you know have the same identity, which basically just means None. So your code should read:
if len(cleaned_data['list']) == 0:
etc.
Now, secondly, I don't understand why you think there could ever be more than one 'element' in list. You've defined it as a CharField, which is a single field containing many characters. Your len is testing the number of characters entered into that field, not the number of fields, however you think you've defined them.