I would like to insert a header in a Django form. I have the following fields:
Name
Price
Optional - Size
Optional - Color
But instead of having the "Optional - " in every optional item, I would like a header, so the form looks like this:
Name
Example
Optional fields:
Size
Color
(This is pseudocode but should be easy to illustrate my point)
I know i can render each individual form field in the HTML, but I would like to still use the {{ form.as_p }} (so I don't have to change the markup for every new field I want to add). Is there any way to define a Django property for the form so I can preserve the form.as_p and still have this additional header (and more headers for more sections of fields)?
Thank you
I made it with JQuery.
It is very simple.
$('#id_size').parent().parent().before('<h1>optional</h1>');
Well, for lack of a better option, I solved this problem by rendering the form's individual fields instead of the whole form.
Then I only have to do:
<div class="fieldWrapper">
(...Field 1...)
</div>
<h1>My Own Header</h1>
<div class="fieldWrapper">
(...Field 2...)
</div>
I think Django hasn't such functionality but django-uni-form has :) This is an example from uni-form documentation with small changes:
from uni_form.helper import FormHelper
from uni_form.layout import Layout, Fieldset
class ExampleForm(forms.Form):
[...]
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.layout = Layout(
Fieldset(
'name',
'price'
),
Fieldset(
'Optional',
'size',
'color'
),
ButtonHolder(
Submit('submit', 'Submit', css_class='button white')
)
)
return super(ExampleForm, self).__init__(*args, **kwargs)
Quite an outdated post, but I spent several hours in finding a solution, so I would like to share it. Hope it's useful.
In your apps directory create a file named widgets.py, with following content, to extend the basic Widget class from Django:
# widgets.py
from django.forms.widgets import Widget
from django.utils.translation import ugettext_lazy as _
from django.template.loader import render_to_string
class HeaderWidget(Widget):
def __init__(self, attrs=None, label=None, tag='h1'):
self.label = label
self.tag = tag
super(HeaderWidget, self).__init__(attrs)
def render(self, name, value, attrs=None):
# change blocks/ according to your template path. In this case was templates/block
widget = render_to_string("blocks/header-form-field.html", {
'tag' : self.tag,
'label' : self.label,
})
return widget
Then create a file customfileds.py . Here we extend the Field Class with mostly nothing. The important thing is to avoid field validation, ase we have no input.
from django import forms
from django.forms.fields import Field
from . import widgets
class HeaderField(Field):
widget = widgets.HeaderWidget
# This is to avoid validation
validators = []
def __init__(self, **kwargs):
super().__init__(**kwargs)
def clean(self, value=''):
"""
It seem the clean method in mandatory when creating a Field class.
Jaust return value without validation
"""
return value
Then create the html template. Put it in a path that matches your
widget = render_to_string("blocks/header-form-field.html"
#header-form-field.html
<{{ tag }} class="your-custom-class">{{ label }}</{{ tag }}>
Now you can add to your forms any HeaderField you need, like that:
class MyForm(GenericForm):
header1 = customfileds.HeaderField(label=_(""),required=False,label_suffix='',
widget=widgets.HeaderWidget(label=_("My custom Label text here"),tag="h3"))
field 1 = whatever input field you have...
Here you can find same code in a gita page
https://gitlab.com/MaadiX/header-field-for-django-forms/blob/master/README.md
Related
Background
I have two models, Runs and Orders. One run will complete many orders, so I have a Many-to-one relation between my orders and runs, represented as a foreignkey on my orders.
I want to build a UI to create a run. It should be a form in which someone selects orders to run. I'd like to display a list of checkboxes alongside information about each order. I'm using django crispy forms right now.
views.py
class createRunView(LoginRequiredMixin, CreateView):
model = Run
form_class = CreateRunForm
template_name = 'runs/create_run.html'
forms.py
class CreateRunForm(forms.ModelForm):
class Meta:
model = Run
fields = ['orders',]
orders = forms.ModelMultipleChoiceField(queryset=Order.objects.filter(is_active=True, is_loaded=False))
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_method = 'post'
self.helper.layout = Layout(
Field('orders', template="runs/list_orders.html"),
Submit('save', 'Create Run'),
Button('cancel', 'Cancel'),
)
Questions
I'm not sure what locals are available to me in the list_orders.html template. It seems like there's {{ field }} and maybe form.visible_fields but if I dig to deeply into either I get a TypeError: 'SubWidget' object is not iterable, which is barely documented online.
The above suggests I might still be getting a widget in the template, despite the fact that Field('orders', template="runs/list_orders.html"), should prevent that, per the crispy docs:
Field: Extremely useful layout object. You can use it to set attributes in a field or render a specific field with a custom template. This way you avoid having to explicitly override the field’s widget and pass an ugly attrs dictionary:
I've seen this answer which suggests using label_from_instance. However I'm not sure how to stuff a bunch of html into label_from_instance. Instead of having a different label, I really want to have a template which generates a bunch of html which shows details about the entire order object, so I'm not sure this approach will work.
The answers in this question mostly confused me, but the accepted answer didn't work, it seems. (maybe a django version issue, or a crispy forms issue?)
TL;DR
How do I render templates with data from each model in ModelMultipleChoiceField?
Widgets control how fields are rendered in HTML forms. The Select widget (and its variants) have two attributes template_name and option_template_name. The option_template_name supplies the name of a template to use for the select options. You can subclass a select widget to override these attributes. Using a subclass, like CheckboxSelectMultiple, is probably a good place to start because by default it will not render options in a <select> element, so your styling will be easier.
By default the CheckboxSelectMultiple option_template_name is 'django/forms/widgets/checkbox_option.html'.
You can supply your own template that will render the details of the orders how you want. IE in your forms.py
class MyCheckboxSelectMultiple(forms.CheckboxSelectMultiple):
option_template_name = 'myapp/detail_options.html'
class CreateRunForm(forms.ModelForm):
...
orders = ModelMultipleChoiceField(..., widget=MyCheckboxSelectMultiple)
Suppose that myapp/detail_options.html contained the following
{# include the default behavior #}
{% include "django/forms/widgets/input_option.html" %}
{# add your own additional div for each option #}
<div style="background-color: blue">
<h2>Additional info</h2>
</div>
You would see that blue div after each label/input. Something like this
Now, the trick will be how you get the object available to the widget namespace. By default, only certain attributes are present on a widget, as returned by the widget's get_context method.
You can use your own subclass of MultipleModelChoiceField and override label_from_instance to accomplish this. The value returned by label_from_instance is ultimately made available to the widgets as the label attribute, which is used for the visible text in your model form, when it renders {{ widget.label }}.
Simply override label_from_instance to return the entire object then use this subclass for your field.
class MyModelMultipleChoiceField(forms.ModelMultipleChoiceField):
def label_from_instance(self, obj):
return obj
class CreateRunForm(forms.ModelForm):
...
orders = MyModelMultipleChoiceField(..., widget=MyCheckboxSelectMultiple)
So now in the myapp/detail_options template you can use widget.label to access the object directly and format your own content as you please. For example, the following option template could be used
{% include "django/forms/widgets/input_option.html" %}
{% with order=widget.label %}
<div style="background-color: blue">
<h2>Order info</h2>
<p style="color: red">Order Active: {{ order.is_active }}</p>
<p style="color: red">Order Loaded: {{ order.is_loaded }}</p>
</div>
{% endwith %}
And it would produce the following effect.
This also will not disrupt the default behavior of the widget label text wherever widget.label is used. Note that in the above image the label texts (e.g. Order object (1)) are the same as before we applied the change to label_from_instance. This is because the default in template rendering is to use str(obj) when presented with a model object; the same thing that would have previously been done by the default label_from_instance.
TL;DR
Make your own subclasses of ModelMultiplechoiceField to have label_from_instance return the object.
Make your own SelectMultiple widget subclass to specify a custom option_template_name.
The template specified will be used to render each option, where widget.label will be each object.
I want to reset the value of some fields in my django form to None, inside the __init__ method of my form.
This is what I have so far:
class MyFormForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ['field1', ...]
field1 = forms.IntegerField(max_value=100)
def __init__(self, *args, **kwargs):
values_changed = kwargs.pop('values_changed', False)
super(MyFormForm, self).__init__(*args, **kwargs)
self.data = self.data.copy()
if not values_changed:
for field in self.fields:
self.data[field] = None
Unfortunately, when the form is displayed in my template, the value that has been POSTed is still in it. How do I get rid of the value, so that the change takes effect in the template, which renders the form?
Things that don't work:
Setting the initial parameter. Since there is a value present, it will be ignored
Using cleaned values. The form is not cleaned at this stage, since it is not valid. Because of this cleaned_data does not exist
I'm accessing the values like this:
{{ form.field1.value|default_if_none:"Please enter." }}
edit: I just tried
{{ form.data.field1|default_if_none:"Please enter." }}
no change.
I'm a moron. There were two forms, form and form_2.
I used the wrong variable name. My bad. It works like it should with the code above.
I have a little question regarding Forms / Views which don't use a Model object. I seem to have it set up almost the way it should, but I can't seem to figure out how to pass data around to initialise the fields in my edit form.
What I have to do is get data from a REST server which was developed using Delphi. So this django thingie won't be using the normal django ORM model thing. Currently I have it working so my app displays a list of departmets which it got using a REST call to the server. Each department has it's ID as a hyperlink.
My next step / thing I would like to do is display a form in which the user can edit some values for the selected department. Logically everything seems to be hooked up together the way it should (as far as I can see). Sadly ... for whatever reason ... I can't seem to pass along information about the clicked ID or even the selected object in my list to the detail view.
Would anyone be able to help me out ? This is what I have so far :
The urls.py :
# DelphiClient/urls.py
from django.conf.urls import patterns
from django.conf.urls import url
from . import views
urlpatterns = patterns("",
url(
regex=r"^Departments$",
view=views.DelphiDepartmentsListView.as_view(),
name="Departments"
),
url(
regex=r'^Department/(?P<pk>\d+)/$',
view=views.DepartmentFormView.as_view(),
name='department_update'
),
)
The views.py :
# DelphiClient/views.py
...
from .client import DelphiClient
from .forms import DepartmentForm
class DelphiDepartmentsListView(TemplateView):
template_name = 'DelphiDepartmentList.html'
def get_context_data(self, **kwargs):
client = DelphiClient()
departments = client.get_department()
context = super(DelphiDepartmentsListView, self).get_context_data(**kwargs)
context['departments'] = departments
#client.update_department(1, 'Update From Django')
return context
class DepartmentFormView(FormView):
template_name = 'DepartmentUpdate.html'
form_class = DepartmentForm
success_url = '/DelphiClient/Departments'
def get_initial(self, **kwargs):
"""
Returns the initial data to use for forms on this view.
"""
initial = super(DepartmentFormView, self).get_initial(**kwargs)
# How can I get the ID passed along from the list view
# so I can get the correct object from my REST server and
# pass it along in the Initial ???
return initial
def form_valid(self, form):
# This method is called when valid form data has been POSTed.
# It should return an HttpResponse.
print "form.data {0}".format(form.data)
client = DelphiClient()
client.update_department(form.data["flddepartmentId"],form.data["flddepartmenet"])
return super(DepartmentFormView, self).form_valid(form)
The forms.py :
# DelphiClient/forms.py
from django import forms
from .client import DelphiClient
class DepartmentForm(forms.Form):
# How can I fill in the values for these fields using an object passed in
# thhrough Initial or the context?
flddepartmentId = forms.IntegerField(label="Department ID") #, value=1)
flddepartmenet = forms.CharField(label="New Description", max_length=100)
def update_department(self, *args, **kwargs):
#print "update_department"
#print self.data
#print self.data["flddepartmenet"]
client = DelphiClient()
client.update_department(self.data["flddepartmentId"],self.data["flddepartmenet"])
And the template for the form :
<h1>Update Department</h1>
<p>Update Department? {{ department.flddepartmentid }}</p>
<p>Something : {{ something }}</p>
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<p><label for="id_flddepartmentId">Department ID:</label> <input id="id_flddepartmentId" name="flddepartmentId" type="number" value="1"></p>
<p><label for="id_flddepartmenet">New Description:</label> <input id="id_flddepartmenet" maxlength="100" name="flddepartmenet" type="text"></p>
<input type="submit" value="OK">
</form>
As you can see ... I'm close ... but no cigar yet :-) Since I'm completely new to Python / Django and have been learning on the go, I have no idea what I'm doing wrong or where I should look.
If anyone would be able to help or point me in the right direction it would be really appreciated.
The positional and name-based arguments are stored in self.args and self.kwargs respectively (see the docs on name based filtering). Therefore you can access the pk with self.kwargs['pk'].
I'm not sure that you should include flddepartmentId as an editable field in the form. It means that users could go to /Department/1/, but then enter flddepartmentId=2 when they submit the form. It might be better to remove the field from the form, then use the value from the URL when calling update_department.
client.update_department(self.kwargs['pk'],self.data["flddepartmenet"])
If you are sure that you want to include flddepartmentId in your form, then your get_initial method should look as follows:
def get_initial(self, **kwargs):
"""
Returns the initial data to use for forms on this view.
"""
initial = super(DepartmentFormView, self).get_initial(**kwargs)
initial['flddepartmentId'] = self.kwargs['pk']
return initial
is there a way i could send a form's (css) class from python?
For example:
class Company(Form):
companyName = TextField('Company Name', [validators.Length(min=3, max = 60)])
This renders a simple text field, but i want that text field to have the css class of .companyName, is that possible directly from python?
I know that i can put a id="companyName" directly from python, but not class.
Help.
Update:
I tried class_="companyName" and it did not work, i got:
__init__() got an unexpected keyword argument '_class'
Alternatively you can add the class in your template like this for jinja2:
{{ form.name(size=20, class_='input-small') }}
WTForms does not allow you to set display options (such as class name) in the field initialization. However, there are several ways to get around this:
If all of your fields should include a class name as well as an ID then just pass in each field's short_name to it when you render it:
<dl>
{% for field in form %}
<dt>{{field.label}}</dt>
<dd>{{field(class_=field.short_name)}}</dd>
{% endfor %}
</dl>
Create a custom widget mixin that provides the class name:
from wtforms.fields import StringField
from wtforms.widgets import TextInput
class ClassedWidgetMixin(object):
"""Adds the field's name as a class
when subclassed with any WTForms Field type.
Has not been tested - may not work."""
def __init__(self, *args, **kwargs):
super(ClassedWidgetMixin, self).__init__(*args, **kwargs)
def __call__(self, field, **kwargs):
c = kwargs.pop('class', '') or kwargs.pop('class_', '')
kwargs['class'] = u'%s %s' % (field.short_name, c)
return super(ClassedWidgetMixin, self).__call__(field, **kwargs)
# An example
class ClassedTextInput(ClassedWidgetMixin, TextInput):
pass
class Company(Form):
company_name = StringField('Company Name', widget=ClassedTextInput)
Use render_kw if using WTForms >= 2.1 :
submit = SubmitField(u'Block Submit Buttom', render_kw={"class": "btn btn-primary btn-block"})
In your template, try it
{{ form.companyName( **{'class': 'companyName'} ) }}
I am trying to add a special template property to one of the form fields which I will than use to render the form in template tag. Here is the code:
form = ProfileForm()
for field in form:
if field.name == 'email':
field.template = 'email_field.html'
This way, original form variable is not modified. Is there a way to achive my goal?
I'm going to assume you might want to build a html5 email field:
from django import forms
from django.forms.widgets import Widget
from django.utils.safestring import mark_safe
class Html5Email(Widget):
def render(self, name, value, attrs=None):
return mark_safe(u'<input name="custom-email" type="email" />')
class YourForm(forms.Form):
html5_email = forms.CharField(widget=Html5Email())
I came up with the above by glancing at the Django source code. Since I haven't personally use the above in an actual project, the code will probably need to be fleshed out.