Django modelform widget attributes replaced or disregarded - django

I've been scratching my head over this for the last little while. I have been able to change the modelfield's field queryset and widget attributes, well somewhat!
class InvoiceItemForm(ModelForm):
UOM = forms.ChoiceField (choices = site_defaults.UOM)
class meta:
model = InvoiceItem
fields = ['name', 'costcode', 'rate', 'quantity',]
labels = {'name': 'Item', 'rate': 'Cost Per Unit', 'quantity': 'Base Quantity'}
widgets = {'UOM': forms.Select(choices = site_defaults.UOM )}
def __init__(self, current_user, current_project, *args, **kwargs):
''' Rendering custom ModelForm '''
super(InvoiceItemForm, self).__init__(*args, **kwargs)
the_title = None
the_instance = kwargs.get('instance', None)
if the_instance:
the_costcode = the_instance.costcode
if the_costcode:
the_title = the_costcode.title
self.fields['costcode'].queryset = CostCode.objects.filter(project = current_project, item = 0)
self.fields['costcode'].widget = forms.TextInput(attrs={'class': 'site-flex-select-large', 'value': the_title})
When this is rendered, the costcode field takes the right instance. Also, the class is shown as site-flex-select-large, but the title is shown as the instance.id and not the_title which is the instance.title (a text field is displayed with value of 192 instead of the title of the invoice item).
Why is Django ignoring some changes and accepting some other changes to the field?
I'm not sure if it is a relevant detail or not, but the modelform is used in an inlineformset:
expenses_forms = self.InvoiceItem_InlineFormSet(instance = the_invoice, prefix='expenses', form_kwargs={'current_user': user, 'current_project': project})

A fields widget is not the place that you should be setting initial values for fields. You should set this in the "initial" kwarg to the form's __init__ method, you can pass it to the call to super. You then can set the costcode widget in the Meta
class InvoiceItemForm(ModelForm):
UOM = forms.ChoiceField (choices = site_defaults.UOM)
class Meta:
model = InvoiceItem
fields = ['name', 'costcode', 'rate', 'quantity',]
labels = {'name': 'Item', 'rate': 'Cost Per Unit', 'quantity': 'Base Quantity'}
widgets = {
'UOM': forms.Select(choices = site_defaults.UOM ),
'costcode': forms.TextInput(attrs={'class': 'site-flex-select-large'})
}
def __init__(self, current_user, current_project, *args, **kwargs):
the_instance = kwargs.get('instance', None)
if the_instance:
the_costcode = the_instance.costcode
if the_costcode:
initial = kwargs.get('initial', {})
initial['costcode'] = the_costcode.title
kwargs['initial'] = initial
super(InvoiceItemForm, self).__init__(*args, **kwargs)
EDIT: like Willem says, the costcode field is a TextInput so it does not make sense to set a queryset attribute on it unless you change it to a select

The value is not taken from the attrs, it is taken from the value of that field. You can set the .initial attribute of the field, like:
def __init__(self, current_user, current_project, *args, **kwargs):
''' Rendering custom ModelForm '''
super(InvoiceItemForm, self).__init__(*args, **kwargs)
the_title = None
the_instance = kwargs.get('instance', None)
if the_instance:
the_costcode = the_instance.costcode
if the_costcode:
the_title = the_costcode.title
self.fields['costcode'].queryset = CostCode.objects.filter(project=current_project, item=0)
self.fields['costcode'].initial = the_title
self.fields['costcode'].widget = forms.TextInput(attrs={'class': 'site-flex-select-large'})
That being said, by using a TextInput, it will, as far as I know, just ignore the queryset, and it will not properly validate the data. I think you better use a Select widget [Django-doc] here, and then use some CSS/JavaScript to make it searchable through text.

Related

Django - remove/hide labels for checkboxes

I would have thought this would be pretty simple but I have plugged away at this for some time and I can't figure it out.
I have a multiple choice checkbox in a model form which is in a formset. All I want to do is remove the labels from the checkboxes. I can easily remove the label from the field but I can't figure out how to remove the labels from the checkboxes.
I am trying to convert the display of the form to a grid.
From this:
To this:
Here's my form code:
class ResourceEditAddForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.portfolio = kwargs.pop('portfolio')
super(ResourceEditAddForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_show_labels = False
self.fields["portfolio"].initial = self.portfolio
self.fields["portfolio"].required = False
self.fields["skills"].queryset = self.portfolio.getSkills()
class Meta:
model = Resource
fields = ['portfolio',
'name',
'skills',
'pct_availability',
'cost_per_day',
'email',
'timezone',
'update_request_time',
'calendar',
'start_date',
'end_date']
widgets = {
'portfolio': forms.HiddenInput(),
'start_date': DateInput(),
'end_date': DateInput(),
'update_request_time': TimeInput(),
'skills': forms.CheckboxSelectMultiple(),
}
ResourceEditAddFormSet = modelformset_factory(
Resource,
form=ResourceEditAddForm,
extra=0
)
I could build a manual form to achieve this but I want to keep using model forms as there's a few fields other than skills which are managed fine by the form.
If anyone can tell me how to hide the labels "animation", "art", etc. next to the checkboxes in forms, css or whatever that would be much appreciated.
You can simply hide your label from models.py. Labels on the form come from verbose_name attributes:
class Resource(models.Model):
skills = models...(...,verbose_name="")
or
try this under ResourceEditAddForm's __init__ function:
def __init__(self, *args, **kwargs):
...
self.fields['skills '].label = ''
...
I appear to have resolved this by slightly customising the widget:
skills_widget = forms.CheckboxSelectMultiple
skills_widget.option_template_name = "django/forms/widgets/input.html"
model = Resource
fields = ['portfolio',
'name',
'skills',
'end_date']
widgets = {
'portfolio': forms.HiddenInput(),
'skills': skills_widget
},
So this replaces the full template for the checkbox (which wraps the input in a label) with just the input part.

Django ModelForm widget and non-widget field ordering

I have a django (1.11.9) ModelForm for events with times (start and end). When I display it to the user in a template I want last_field to be the last field in the form at the bottom:
class EventForm(forms.ModelForm):
dt = '%Y-%m-%d %H:%M'
start = forms.DateTimeField(
label="Start time (in event's local time)",
required=False,
input_formats=[dt]
)
end = forms.DateTimeField(
label="End time (in event's local time)",
required=False,
input_formats=[dt]
)
class Meta:
model = Event
fields = [
'name', 'flexibility', 'timezone',
'date_start', 'date_end', 'last_field',
]
def __init__(self, *args, **kwargs):
super(EventForm, self).__init__(*args, **kwargs)
self.fields['date_start'].widget = forms.HiddenInput()
self.fields['date_end'].widget = forms.HiddenInput()
i = kwargs.get('instance')
tz = pytz.timezone(i.timezone) if i and i.timezone else None
if i and i.flexibility == Event.FLEXIBILITY_ONTIME and tz:
self.fields['start'].initial = i.date_start.astimezone(tz).strftime(self.dt) if i.date_start else None
self.fields['end'].initial = i.date_end.astimezone(tz).strftime(self.dt) if i.date_end else None
else:
self.fields['start'].initial = None
self.fields['end'].initial = None
however, even explicitly setting 'last_field' at the end of the fields = [] array (per docs https://docs.djangoproject.com/en/1.11/topics/forms/modelforms/#changing-the-order-of-fields), the start and end widget fields in the form render below it out of the set field order.
is there a way to override the widgets to force the ordering of the fields? thanks

Django - field missing from cleaned data

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

django can't relabel empty label in TypedChoiceField

I can't seem to relabel the empty label in a form's ModelChoiceField.
I have tried. (note I am using django crispy forms for the form layout - shouldn't interfere with this)
Forms.py
class PaymentForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['payment_day'].empty_label = 'test something'
self.helper = FormHelper(self)
self.helper.label_class = 'sr-only'
self.helper.form_tag = False
self.helper.layout = Layout(
......
PrependedText('payment-day', '<i class="fa fa-calendar"></i>', placeholder="What"),
)
class Meta:
model = DirectDebit
fields = [
......
'payment_day',
]
models.py
class DirectDebit(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
......
DAYS_OF_MONTH_CHOICES = [(i, i) for i in range(32)]
payment_day = models.IntegerField(choices=DAYS_OF_MONTH_CHOICES)
time_stamp = models.DateField(auto_now=True)
By the time you set empty_label it may be too late, as the field's choices have already been calculated. Try reassigning the queryset to itself, to see whether that triggers the choices to be recalculated.
super().__init__(*args, **kwargs)
self.fields['payment_day'].empty_label = 'test something'
self.fields['payment_day'].queryset = self.fields['payment_day'].queryset
Solved with:
self.fields['payment_day'].choices = [('', '---- Please select your payment day ----')] + list(
self.fields['payment_day'].choices[1:])

Choices lost when overriding Django Select with RadioSelect

Given the django model below, is it possible to automatically update a subset of the fields to be RadioSelect instead of the default Select?
class ExampleModel(models.Model):
field1_radios = models.CharField(max_length=1, choices=DEFAULT_CHOICES)
field2_radios = models.CharField(max_length=1, choices=DEFAULT_CHOICES)
field3_radios = models.CharField(max_length=1, choices=DEFAULT_CHOICES)
field4_radios = models.CharField(max_length=1, choices=DEFAULT_CHOICES)
field5 = models.CharField(max_length=50)
...
I can achieve this manually with:
class ExampleForm(forms.ModelForm):
class Meta:
model = ExampleModel
widgets = {
'field1_radios': forms.RadioSelect(),
'field2_radios': forms.RadioSelect(),
'field3_radios': forms.RadioSelect(),
'field4_radios': forms.RadioSelect()}
When I try to automate the creation of the widgets the choices are lost (and the radio buttons are not rendered):
def custom_callback(f, *args, **kwargs):
if f.name.endswith('_radios'):
formfield = f.formfield()
formfield.widget = forms.RadioSelect()
return formfield
else:
return f.formfield()
class ExampleForm(forms.ModelForm):
formfield_callback = custom_callback
class Meta:
model = ExampleModel
ahh, it looks like I was missing the final step of re-specifying the original choices, this is not done automatically:
def custom_callback(f, *args, **kwargs):
if f.name.endswith('_radios'):
formfield = f.formfield()
formfield.widget = forms.RadioSelect(choices=formfield.choices)
return formfield
else:
return f.formfield()
I haven't seen formfield_callback used very often, and I don't think it's documented.
Another approach would be to override formfield_for_choice_field.
class MyModelAdmin(admin.ModelAdmin):
...
def formfield_for_choice_field(self, db_field, request, **kwargs):
"""Use a radio select instead of a select box"""
kwargs['widget'] = forms.RadioSelect
if 'choices' not in kwargs:
# this was required to prevent displaying the empty label ---------
kwargs['choices'] = db_field.get_choices(include_blank=False)
return super(MyModelAdmin, self).formfield_for_choice_field(db_field, request, **kwargs)