I have created a custom SelectTimeDateWidget that used the default SelectDateWidget and a custom SelectTimeWidget
class SelectDateTimeWidget(forms.MultiWidget):
supports_microseconds = False
def __init__(self, attrs=None, date_format=None, time_format=None):
widgets = (SelectDateWidget(empty_label=( "Year", "Month", "Day")),
SelectTimeWidget(use_seconds=False))
super(SelectDateTimeWidget, self).__init__(widgets, attrs)
def decompress(self, value):
if value:
value = to_current_timezone(value)
return [value.date(), value.time().replace(microsecond=0)]
return [None, None]
The widgets all work fine but because they are places next to one another in the form template it looks really clustered and not very user friendly. So I need a way to put a break tag and maybe a label tag in between them. I cant find anything on the web(Maybe I'm not wording it right)
This is the code within the form template and both widgets are in that one tag so i'm struggling to think of a way to break them up onto different lines, Any ideas?
<label> Start Time/Date:</label>
{{form.start}},
You can just overwrite format_output method from MultiWidget. It takes one argument, rendered_widgets, which is just a list of rendered sub-widgets. Default implementation just joins them, but you can do with them whatever you want. Example:
def format_output(self, rendered_widgets):
return '<br>'.join(rendered_widgets)
Or, if you want to write something between widgets in template, add this method to your MultiWidget implementation:
def subwidgets(self, name, value, attrs=None):
if self.is_localized:
for widget in self.widgets:
widget.is_localized = self.is_localized
# value is a list of values, each corresponding to a widget
# in self.widgets.
if not isinstance(value, list):
value = self.decompress(value)
output = []
final_attrs = self.build_attrs(attrs)
id_ = final_attrs.get('id')
for i, widget in enumerate(self.widgets):
try:
widget_value = value[i]
except IndexError:
widget_value = None
if id_:
final_attrs = dict(final_attrs, id='%s_%s' % (id_, i))
output.append(widget.render(name + '_%s' % i, widget_value, final_attrs))
return output
And you should be able to use {{ form.start.0 }}, {{ form.start.1 }} and so on in your template.
Related
To workaround issues with Taggit, I'm trying to add quotes around values in the tag field before they're transferred into a model. This is what I have so far but it's not working. What am I doing wrong?
class TagField(models.CharField):
description = "Simplifies entering tags w/ taggit"
def __init__(self, *args, **kwargs):
super(TagField, self).__init__(self, *args, **kwargs)
# Adds quotes to the value if there are no commas
def to_python(self, value):
if ',' in value:
return value
else:
return '"' + value + '"'
class CaseForm(forms.ModelForm):
class Meta:
model = Case
fields = ['title', 'file', 'tags']
labels = {
'file': 'Link to File',
'tags': 'Categories'
}
widgets = {
'tags': TagField()
}
You are subclassing models.CharField, instead you should subclass forms.CharField, you're specifying for widget attribute in the form but you're trying to create a form field subclass.
The reason this is not working is you are defining a custom model field and then trying to specify it as a widget in the form. If you indeed want a custom widget, you need to actually provide a widget instance, not a model field instance.
But to get the behavior you want, instead you need to declare the field at the Model level as an instance of your custom field class.
Try something like -
from django.db import models
class TagField(models.CharField):
description = "Simplifies entering tags w/ taggit"
def __init__(self, *args, **kwargs):
super(TagField, self).__init__(*args, **kwargs)
# Adds quotes to the value if there are no commas
def to_python(self, value):
if any( x in value for x in (',', '"') ):
return value
else:
return "\"%s\"" % value
class ModelWithTag(models.Model):
tag = TagField(max_length = 100)
The to_python method is also called by Model.clean(), which is called during form validation, so I think this will provide the behavior you need.
Note, I also check for the presence of a double-quote in your condition in the to_python method, otherwise the quotes will continue to "stack up" every time save() is called.
I have a model lets say MyModel which has a foreign key to another model say Tag.
class MyModel(models.Model):
id=models.AutoField(primary_key=True)
name=models.CharField(max_length=200)
tag = models.ManyToMany(Tag)
I have approximately 50,000 instances of MyModel and each MyModel can have 100 Tags.
If I use the above model I will get 5,000,000 entries in a table, mymodel_tag but I can use all the features of ORM.
However If I write custom methods and treat above field as an array of integers and write custom code to retrieve/save ids of tags associated with MyModel, I will have just 50K entries but I will have to write custom code for retrieval etc.
a) I want to know the pros and cons of both the approaches!
b) If I have to have to use the custom array approach how can i do it efficiently.
Umm..
tag = models.ManyToManyField(Tag)
?
With a foreign key, MyModel can only be associated with one and only one Tag. I'm honestly not even sure how you were able to give each one 100 Tags without have to duplicate each MyModel 100 times. If you were doing that, no wonder you're not liking the results.
ManyToManyField creates a join table that will consist only of an id (integer) reference to MyModel and an id (integer) reference to Tag. That is the most compact you'll ever get with this type of relationship, and it's the best practice, anyways.
Although I totally agree with what chrisdpratt says but unfortunately, I have been forced to do it otherwise. Here is one way I found of doing that at http://djangosnippets.org/snippets/1200/:
from django.db import models
from django import forms
class MultiSelectFormField(forms.MultipleChoiceField):
widget = forms.CheckboxSelectMultiple
def __init__(self, *args, **kwargs):
self.max_choices = kwargs.pop('max_choices', 0)
super(MultiSelectFormField, self).__init__(*args, **kwargs)
def clean(self, value):
if not value and self.required:
raise forms.ValidationError(self.error_messages['required'])
if value and self.max_choices and len(value) > self.max_choices:
raise forms.ValidationError('You must select a maximum of %s choice%s.'
% (apnumber(self.max_choices), pluralize(self.max_choices)))
return value
class MultiSelectField(models.Field):
__metaclass__ = models.SubfieldBase
def get_internal_type(self):
return "CharField"
def get_choices_default(self):
return self.get_choices(include_blank=False)
def _get_FIELD_display(self, field):
value = getattr(self, field.attname)
choicedict = dict(field.choices)
def formfield(self, **kwargs):
# don't call super, as that overrides default widget if it has choices
defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name),
'help_text': self.help_text, 'choices':self.choices}
if self.has_default():
defaults['initial'] = self.get_default()
defaults.update(kwargs)
return MultiSelectFormField(**defaults)
def get_db_prep_value(self, value):
if isinstance(value, basestring):
return value
elif isinstance(value, list):
return ",".join(value)
def to_python(self, value):
if isinstance(value, list):
return value
return value.split(",")
def contribute_to_class(self, cls, name):
super(MultiSelectField, self).contribute_to_class(cls, name)
if self.choices:
func = lambda self, fieldname = name, choicedict = dict(self.choices):",".join([choicedict.get(value,value) for value in getattr(self,fieldname)])
setattr(cls, 'get_%s_display' % self.name, func)
I know that if I need a custom "selector" for a field in django-admin I need to create a custom widget.
But what if the widget have to produce two values, for example X and Y coordinates, how can I fill them in two different fields from the model?
You can look at the implementation of the date-time field, that renders as 2 fields in the admin.
Going top-down,
the admin uses
class AdminSplitDateTime(forms.SplitDateTimeWidget):
"""
A SplitDateTime Widget that has some admin-specific styling.
"""
def __init__(self, attrs=None):
widgets = [AdminDateWidget, AdminTimeWidget]
# Note that we're calling MultiWidget, not SplitDateTimeWidget, because
# we want to define widgets.
forms.MultiWidget.__init__(self, widgets, attrs)
def format_output(self, rendered_widgets):
return mark_safe(u'<p class="datetime">%s %s<br />%s %s</p>' % \
(_('Date:'), rendered_widgets[0], _('Time:'), rendered_widgets[1]))
which in turn uses SplitDateTimeWidget:
class SplitDateTimeWidget(MultiWidget):
"""
A Widget that splits datetime input into two <input type="text"> boxes.
"""
date_format = DateInput.format
time_format = TimeInput.format
def __init__(self, attrs=None, date_format=None, time_format=None):
if date_format:
self.date_format = date_format
if time_format:
self.time_format = time_format
widgets = (DateInput(attrs=attrs, format=self.date_format),
TimeInput(attrs=attrs, format=self.time_format))
super(SplitDateTimeWidget, self).__init__(widgets, attrs)
def decompress(self, value):
if value:
return [value.date(), value.time().replace(microsecond=0)]
return [None, None]
Which in turn extends the MultiWidget defined in django.forms.widgets which you should also extend. It has many useful methods which you can override.
class MultiWidget(Widget):
"""
A widget that is composed of multiple widgets.
Its render() method is different than other widgets', because it has to
figure out how to split a single value for display in multiple widgets.
The ``value`` argument can be one of two things:
* A list.
* A normal value (e.g., a string) that has been "compressed" from
a list of values.
In the second case -- i.e., if the value is NOT a list -- render() will
first "decompress" the value into a list before rendering it. It does so by
calling the decompress() method, which MultiWidget subclasses must
implement. This method takes a single "compressed" value and returns a
list.
When render() does its HTML rendering, each value in the list is rendered
with the corresponding widget -- the first value is rendered in the first
widget, the second value is rendered in the second widget, etc.
Subclasses may implement format_output(), which takes the list of rendered
widgets and returns a string of HTML that formats them any way you'd like.
You'll probably want to use this class with MultiValueField.
"""
def __init__(self, widgets, attrs=None):
self.widgets = [isinstance(w, type) and w() or w for w in widgets]
super(MultiWidget, self).__init__(attrs)
def render(self, name, value, attrs=None):
# value is a list of values, each corresponding to a widget
# in self.widgets.
if not isinstance(value, list):
value = self.decompress(value)
output = []
final_attrs = self.build_attrs(attrs)
id_ = final_attrs.get('id', None)
for i, widget in enumerate(self.widgets):
try:
widget_value = value[i]
except IndexError:
widget_value = None
if id_:
final_attrs = dict(final_attrs, id='%s_%s' % (id_, i))
output.append(widget.render(name + '_%s' % i, widget_value, final_attrs))
return mark_safe(self.format_output(output))
def id_for_label(self, id_):
# See the comment for RadioSelect.id_for_label()
if id_:
id_ += '_0'
return id_
id_for_label = classmethod(id_for_label)
def value_from_datadict(self, data, files, name):
return [widget.value_from_datadict(data, files, name + '_%s' % i) for i, widget in enumerate(self.widgets)]
def _has_changed(self, initial, data):
if initial is None:
initial = [u'' for x in range(0, len(data))]
else:
if not isinstance(initial, list):
initial = self.decompress(initial)
for widget, initial, data in zip(self.widgets, initial, data):
if widget._has_changed(initial, data):
return True
return False
def format_output(self, rendered_widgets):
"""
Given a list of rendered widgets (as strings), returns a Unicode string
representing the HTML for the whole lot.
This hook allows you to format the HTML design of the widgets, if
needed.
"""
return u''.join(rendered_widgets)
def decompress(self, value):
"""
Returns a list of decompressed values for the given compressed value.
The given value can be assumed to be valid, but not necessarily
non-empty.
"""
raise NotImplementedError('Subclasses must implement this method.')
def _get_media(self):
"Media for a multiwidget is the combination of all media of the subwidgets"
media = Media()
for w in self.widgets:
media = media + w.media
return media
media = property(_get_media)
def __deepcopy__(self, memo):
obj = super(MultiWidget, self).__deepcopy__(memo)
obj.widgets = copy.deepcopy(self.widgets)
return obj
Jannis Leidel released a widget quite a long time ago. django-coordinatesfield
As far as I remember, it took the coordinates from a map and passed it a single field and some javascript cut it into 2 coordinates for 2 fields.
Combined with a custom form it should work quite well
Here's an example for a ModelForm:
http://www.adamalton.co.uk/blog/displaying-django-genericforeignkey-as-single-form-field/
Add an extra form field to the form (for your single widget) and exclude the two 'real' fields, then override the init and save methods to do the extra logic that makes it work.
Also, same question:
How to get a single widget to set 2 fields in Django?
You can make the widget render two (hidden) html inputs, whose names relate to the model's fields that need to be filled and assign the necessary values via javascript to them!
Updated with my final solution, below.
I wrote a custom Django form widget to create a range query. It renders two input fields to define the min and max values for a query.
With well-crafted forms and widgets, fields can be filled with the values from the last query like so:
form = my_form(request.GET)
However, I cannot figure out a way to fill the values of those fields in my custom widget. Here is the widget code:
class MinMax(Widget):
input_type = None # Subclasses must define this.
def _format_value(self, value):
if self.is_localized:
return formats.localize_input(value)
return value
def render(self, name, value, attrs=None):
if value is None:
value = ''
final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
if value != '':
# Only add the 'value' attribute if a value is non-empty.
final_attrs['value'] = force_unicode(self._format_value(value))
return mark_safe(u'<input type="text" name="min-%s" /> to
<input type="text" name="max-%s" />' % (name, name) )
Probably because of the custom input field names, the values are not accessible. Is there a way to route them, or a way to rewrite the widget to include this useful functionality? One non-widget solution I can think of is some simple jquery logic, but that's less than optimal.
Here's the code I ended up using:
class MinMax(MultiWidget):
def __init__(self, attrs=None):
""" pass all these parameters to their respective widget constructors..."""
widgets = (forms.TextInput(attrs=attrs), forms.TextInput(attrs=attrs) )
super(MinMax, self).__init__(widgets, attrs)
def decompress(self, value):
return value or ''
def value_from_datadict(self, data, files, name):
value = ''
for key, value in data.items():
value += value
def format_output(self, rendered_widgets):
"""
Given a list of rendered widgets (as strings), it inserts stuff
between them.
Returns a Unicode string representing the HTML for the whole lot.
"""
rendered_widgets.insert(-1, ' to ')
return u''.join(rendered_widgets)
Note that these fields are returned as fieldname_0, fieldname_1 (and so on if you add additional widgets).
I am using Django everyday now for three month and it is really great. Fast web application development.
I have still one thing that I cannot do exactly how I want to.
It is the SelectField and SelectMultiple Field.
I want to be able to put some args to an option of a Select.
I finally success with the optgroup :
class EquipmentField(forms.ModelChoiceField):
def __init__(self, queryset, **kwargs):
super(forms.ModelChoiceField, self).__init__(**kwargs)
self.queryset = queryset
self.to_field_name=None
group = None
list = []
self.choices = []
for equipment in queryset:
if not group:
group = equipment.type
if group != equipment.type:
self.choices.append((group.name, list))
group = equipment.type
list = []
else:
list.append((equipment.id, equipment.name))
But for another ModelForm, I have to change the background color of every option, using the color property of the model.
Do you know how I can do that ?
Thank you.
What you need to do, is to change the output which is controlled by the widget. Default is the select widget, so you can subclass it. It looks like this:
class Select(Widget):
def __init__(self, attrs=None, choices=()):
super(Select, self).__init__(attrs)
# choices can be any iterable, but we may need to render this widget
# multiple times. Thus, collapse it into a list so it can be consumed
# more than once.
self.choices = list(choices)
def render(self, name, value, attrs=None, choices=()):
if value is None: value = ''
final_attrs = self.build_attrs(attrs, name=name)
output = [u'<select%s>' % flatatt(final_attrs)]
options = self.render_options(choices, [value])
if options:
output.append(options)
output.append('</select>')
return mark_safe(u'\n'.join(output))
def render_options(self, choices, selected_choices):
def render_option(option_value, option_label):
option_value = force_unicode(option_value)
selected_html = (option_value in selected_choices) and u' selected="selected"' or ''
return u'<option value="%s"%s>%s</option>' % (
escape(option_value), selected_html,
conditional_escape(force_unicode(option_label)))
# Normalize to strings.
selected_choices = set([force_unicode(v) for v in selected_choices])
output = []
for option_value, option_label in chain(self.choices, choices):
if isinstance(option_label, (list, tuple)):
output.append(u'<optgroup label="%s">' % escape(force_unicode(option_value)))
for option in option_label:
output.append(render_option(*option))
output.append(u'</optgroup>')
else:
output.append(render_option(option_value, option_label))
return u'\n'.join(output)
It's a lot of code. But what you need to do, is to make your own widget with an altered render method. It's the render method that determines the html that is created. In this case, it's the render_options method you need to change. Here you could include some check to determine when to add a class, which you could style.
Another thing, in your code above it doesn't look like you append the last group choices. Also you might want to add an order_by() to the queryset, as you need it to be ordered by the type. You could do that in the init method, so you don't have to do it all over when you use the form field.
render_option has been removed from Django 1.11 onwards. This is what I did to achieve this. A little bit of digging and this seems straightforward and neat. Works with Django 2.0+
class CustomSelect(forms.Select):
def __init__(self, attrs=None, choices=()):
self.custom_attrs = {}
super().__init__(attrs, choices)
def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
index = str(index) if subindex is None else "%s_%s" % (index, subindex)
if attrs is None:
attrs = {}
option_attrs = self.build_attrs(self.attrs, attrs) if self.option_inherits_attrs else {}
if selected:
option_attrs.update(self.checked_attribute)
if 'id' in option_attrs:
option_attrs['id'] = self.id_for_label(option_attrs['id'], index)
# setting the attributes here for the option
if len(self.custom_attrs) > 0:
if value in self.custom_attrs:
custom_attr = self.custom_attrs[value]
for k, v in custom_attr.items():
option_attrs.update({k: v})
return {
'name': name,
'value': value,
'label': label,
'selected': selected,
'index': index,
'attrs': option_attrs,
'type': self.input_type,
'template_name': self.option_template_name,
}
class MyModelChoiceField(ModelChoiceField):
# custom method to label the option field
def label_from_instance(self, obj):
# since the object is accessible here you can set the extra attributes
if hasattr(obj, 'type'):
self.widget.custom_attrs.update({obj.pk: {'type': obj.type}})
return obj.get_display_name()
The form:
class BookingForm(forms.ModelForm):
customer = MyModelChoiceField(required=True,
queryset=Customer.objects.filter(is_active=True).order_by('name'),
widget=CustomSelect(attrs={'class': 'chosen-select'}))
The output which I needed is as:
<select name="customer" class="chosen-select" required="" id="id_customer">
<option value="" selected="">---------</option>
<option value="242" type="CNT">AEC Transcolutions Private Limited</option>
<option value="243" type="CNT">BBC FREIGHT CARRIER</option>
<option value="244" type="CNT">Blue Dart Express Limited</option>
I run into this question many times when searching by
'how to customize/populate Django SelectField options'
The answer provided by Dimitris Kougioumtzis is quite easy
Hope it could help somebody like me.
# forms.py
from django.forms import ModelForm, ChoiceField
from .models import MyChoices
class ProjectForm(ModelForm):
choice = ChoiceField(choices=[
(choice.pk, choice) for choice in MyChoices.objects.all()])
# admin.py
class ProjectAdmin(BaseAdmin):
form = ProjectForm
....
You should not mess with form fields for adding some custom attributes to the rendered html tag. But you should subclass and add a these to the Widget.
From the docs: customizing-widget-instances
You can submit attrs dictionary to the form Widgets, that render as attributes on the output form widgets.
class CommentForm(forms.Form):
name = forms.CharField(
widget=forms.TextInput(attrs={'class':'special'}))
url = forms.URLField()
comment = forms.CharField(
widget=forms.TextInput(attrs={'size':'40'}))
Django will then include the extra attributes in the rendered output:
>>> f = CommentForm(auto_id=False)
>>> f.as_table()
<tr><th>Name:</th><td><input type="text" name="name" class="special"/></td></tr>
<tr><th>Url:</th><td><input type="text" name="url"/></td></tr>
<tr><th>Comment:</th><td><input type="text" name="comment" size="40"/></td></tr>
http://code.djangoproject.com/browser/django/trunk/django/newforms/widgets.py?rev=7083
As seen under the class Select(Widget):, there is no way to add the style attribute to an option tag. To this, you will have to subclass this widget and add such functionality.
The class Select(Widget): definition only adds style attribute to the main select tag.