Wtforms-alchemy field order - python

I'm using wtforms-alchemy in my Tornado application to render SQLAlchemy models to HTML forms like this:
class UserProfileForm(ModelForm):
class Meta:
model = models.User
only = ['username', 'first_name', 'last_name']
It works just fine, but the fields on the form are in order last_name, username, first_name, which could be kinda confusing to user. Is there a way to set specific order of generated form's fields?

I did what this post suggested, and its been working flawlessly for me.
Briefly, add a class keyword that specifies the correct ordering, then update _unbound_fields from iter with fields in correct order.

You should put rendering of form in template.
like
{{ form.username }}
{{ form.first_name }}
{{ form.last_name }}

Solution https://stackoverflow.com/a/18475322/892040 did not work for me when inheriting from ModelForm. However, this did work:
class ItemReview(ModelForm):
class Meta:
model = Item
_field_order = ['field1', 'field2']
def __init__(self, *args, **kwargs):
super(ItemReview, self).__init__(*args, **kwargs)
field_order = getattr(ItemReview, '_field_order')
visited = []
if field_order:
new_fields = OrderedDict()
for field_name in field_order:
if field_name in self._fields:
new_fields[field_name] = self._fields[field_name]
visited.append(field_name)
for field_name in self._fields:
if field_name in visited:
continue
new_fields[field_name] = self._fields[field_name]
self._fields = new_fields

Related

Django Form Dynamic Fields looping over each field from POST and creating records

I'm looking for some advice where to go from here. I've been working on making a Form, which dynamically generates its fields.
The form is working and generating everything correctly. However, I am having issues with how to save the actual form data. I'm looking for each field to save as a new item in a model.
The View Class from view.py
class MaintenanceCheckListForm(LoginRequiredMixin, FormView):
login_url = '/accounts/login'
template_name = 'maintenance/checklist.html'
form_class = MaintenanceCheckListForm
success_url = reverse_lazy('m-checklist')
def form_valid(self, form):
form.cleaned_data
for key, values in form:
MaintenanceCheckList.objects.create(
item = key,
is_compliant = values
)
return super().form_valid(form)
The Form from forms.py
class MaintenanceCheckListForm(forms.Form):
def __init__(self, *args, **kwargs):
super(MaintenanceCheckListForm, self).__init__(*args, **kwargs)
items = Maintenance_Item.objects.all()
CHOICES = (
('P','Compliant'),
('F','Non-Compliant'),
)
for item in items:
self.fields[str(item.name)] = forms.ChoiceField(
label=item.name,
choices=CHOICES,
widget=forms.RadioSelect,
initial='F',
)
The Model, from models.py
class MaintenanceCheckList(CommonInfo):
CHOICES = (
('P','Compliant'),
('F','Non-Compliant'),
)
id = models.AutoField(primary_key=True)
item = models.CharField(max_length=100)
is_compliant = models.CharField(max_length=20, choices= CHOICES)
I am having trouble accessing the data from the Form when it POST's. I've done some troubleshooting where I have set the values statically in the '''form_valid''' and it appears to generate the correct amounts of entires in the model. However the trouble begins when I attempt to insert the values from the POST.
I receieve the below error, which I believe it is trying to dump all the keys and values into a single item instead of looping over each key, value and creating the item.
DataError at /maintenance/checklist
value too long for type character varying(100)
Request Method: POST
Request URL: http://t1.localhost:8000/maintenance/checklist
Django Version: 3.1.6
Exception Type: DataError
Exception Value:
value too long for type character varying(100)
I'm fairly new to the world of Django (4 weeks and counting so far, and maybe 12 weeks into python). So any assistance would be amazing!
I believe you have somewhat gone on a tangent. There's a simpler solution of using Model formsets for what you want.
First if you want a custom form make that:
from django import forms
class MaintenanceCheckListComplianceForm(forms.ModelForm):
item = forms.CharField(widget = forms.HiddenInput())
is_compliant = forms.ChoiceField(
choices=MaintenanceCheckList.CHOICES,
widget=forms.RadioSelect,
initial='F',
)
class Meta:
model = MaintenanceCheckList
fields = ('item', 'is_compliant')
Next use it along with modelformset_factory in your views:
from django.forms import modelformset_factory
class MaintenanceCheckListFormView(LoginRequiredMixin, FormView): # Changed view name was a bit misleading
login_url = '/accounts/login'
template_name = 'maintenance/checklist.html'
success_url = reverse_lazy('m-checklist')
def form_valid(self, form):
instances = form.save()
return super().form_valid(form)
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['queryset'] = MaintenanceCheckList.objects.none()
kwargs['initial'] = [{'item': obj['name'], 'is_compliant': 'F'} for obj in Maintenance_Item.objects.all().values('name')]
return kwargs
def get_form(self, form_class=None):
kwargs = self.get_form_kwargs()
extra = len(kwargs['initial'])
form_class = modelformset_factory(MaintenanceCheckList, form=MaintenanceCheckListComplianceForm, extra=extra)
return form_class(**kwargs)
Now in your template:
<form method="post">
{{ form }}
</form>
Or manually render it:
<form method="post">
{{ form.management_form }}
{% for sub_form in form %}
Item: {{ sub_form.item.value }}
{{ sub_form }}
{% endfor %}
</form>
Note: The above usage is a bit weird due to the naming of the formset variable as form by the FormView you should look into improving that a bit.
Note: Looking at the implementation it feels a bit weird to do this. I would advice you to redesign your models a bit. Perhaps a foreign key between your models? It basically feels like you have duplicate data with this implementation.

Creating a form out of relation based key, values in django

I have a django based application where I want to create a form out of key, value pairs from a model. The `Child' model consists of the following rows of data:
(<parent 1>, 'component 1', 'dummy content 1'),
(<parent 1>, 'component 2', 'dummy content 2'),
Here is are my models:
# models.py
class Parent(models.Model):
class Meta:
verbose_name = 'Parent'
db_table = "parent"
title = models.CharField(max_length=28)
def __str__(self):
return self.title
class Child(models.Model):
class Meta:
verbose_name = 'Child'
db_table = "child"
parent = models.ForeignKey(Parent, on_delete=models.CASCADE)
key = models.CharField(max_length=20)
value = models.TextField()
def __str__(self):
return self.parent
Following is the direct model to form mapping I am currently using for my other forms to keep it straight forward and simple
# forms.py
class MyForm(ModelForm):
class Meta:
model = Child
fields = () # fields go here
Then I pass this form to my view. The view page_view takes pk of the parent, gets the parent and passes it to the form. The form is then passed on to the template parent_view.html via the view.
# views.py
#login_required
def page_view(request, parent_pk):
parent = get_object_or_404(Parent, pk=pk)
my_form = MyForm(request.POST, instance=parent)
return render(request, 'parent_view.html', {
'parent': parent,
'my_form': my_form,
})
In the template I render the form like this:
<!-- page_view.html -->
{{ my_form }}
However, I would also like to write the html for this manually to add any design changes locally. I would like the forms.py MyForm to construct a form from the model by collecting key, value pairs for the provided parent.
So it should render it like this:
<form action=''>
<label for='component_1'>component 1</label>
<textarea name='component_1' type='text'>dummy content 1</textarea>
<label for='component_2'>component 2</label>
<textarea name='component_2' type='text'>dummy content 2</textarea>
</form>
But I can't seem to get my head around how to handle that in the `MyForm'. I have looked around a couple of solutions over stackoverflow but none of them point me in the right direction for this problem. If anyone has any ideas I would highly appreciate. Thanks in advance.
If there are multiple Child instances, then a single form will not be of much use, you will have to use a formset (a model formset to be precise).
As per the docs,
A formset is a layer of abstraction to work with multiple forms on the same page
# forms.py
# You can provide a text area widget for the field that you want to be displayed as a text area
class MyForm(ModelForm):
class Meta:
model = Child
fields = () # fields go here
widgets = {
'field_name': forms.Textarea(attrs={'cols': 80, 'rows': 3}),
}
ChildFormset = forms.modelformset_factory(Child, ChildForm, exclude=[], extra=0)
Then in your views, you can pass a queryset of all the objects that you want in your form
# views.py
from .forms import ChildFormset
#login_required
def page_view(request, parent_pk):
parent = get_object_or_404(Parent, pk=pk)
child_queryset = parent.child_set.all()
if request.method == 'GET':
child_formset = ChildFormset(queryset=child_queryset)
return render(request, 'parent_view.html', {
'parent': parent,
'my_formset': child_formset,
})
else:
child_formset = ChildFormset(request.POST, queryset=child_queryset)
if child_formset.is_valid():
for form in child_formset:
form.save()
# ... Do whatever else you want to do with the data
In your templates, you will then have to traverse through all the form objects in the formset. Then you can display them in whatever way you want to.
# parent_view.html
{{ child_formset.management_form }}
{% for form in child_formset %}
<div class="hidden">{{ form.id }}</div>
{% for field in form.visible_fields %}
{{ field }}
{% endfor %}
{% endfor %}
NOTE: The Foreign Key field will be displayed as a drop down for the user to select a parent object from the list of parent objects.

Succinct way to handle Form Data on Django using the save() method

In a Bookstore application, I have a form to add a Book written by an Author and a Shelf which belongs to a Bookstore. A book needs to be associated to a Shelf.
class AddBookForm(forms.Form):
def save(self, request):
#Let's say I put an excrutiating number of fields...say 13.
book_name = self.cleaned_data.get('book_name', None)
book_author_pk = self.cleaned_data.get('book_author_pk', None)
book_genre = self.cleaned_data.get('genre', None)
book_price = self.cleaned_data.get('price', None)
book_summary = self.cleaned_data.get('book_summary', None)
bookshop_location = self.cleaned_data.get('bookshop_location', None)
bookshop_shelf_number = self.cleaned_data.get('bookshop_shelf_number', None)
stock = self.cleaned_data.get('stock', None)
promotional_price = self.cleaned_data.get('promotional_price', None)
author_object = Author.objects.get(pk=book_author_pk)
book = Book(
name = book_name,
author = author_object,
genre = genre,
summary = book_summary
)
book.save(force_insert=True)
shelf = Shelf(
bookshop_location = bookshop_location,
shelf_number = bookshop_shelf_number,
stock = stock,
promotional_price = promotional_price
)
shelf.save(force_insert=True, force_update=False)
My question is: Is there actually a more succinct way to write this? I'm pretty sure that there is but I'm missing it somewhere.
Give this a try
class AddFruitForm(forms.Form):
def save(self, request):
# list all your fields here
fields_expected = ['fruit_name', 'fruit_color', ...]
# this should give you the dict with all the fields equal to "None"
fields = dict.fromkeys(fields_expected)
# this relace the None value with the good data from cleaned_data
fields.update(self.cleaned_data)
# assign all the in the dict to the model, key=value
fruit = Fruit(**fields)
fruit.save(force_insert=True, force_update=False)
However if your model can accept None for those value you shouldn't have to provide to the model explicitly as above, you can do this instead and let the model to handle the default value.
class AddFruitForm(forms.Form):
def save(self, request):
fields = self.cleaned_data
fields['my_custom_field'] = 'some data not from the field'
fruit = Fruit(**fields)
fruit.save(force_insert=True, force_update=False)
If your data is associated with models, it's best to use ModelForm. You could have as many form as you want in the front end and submit them altogether. The code is really straight forward:
# models.py
class BookForm(forms.ModelForm):
class meta:
model = Book
class Shelf(forms.ModelForm):
class meta:
model = Shelf
# views.py
def addbook(request):
book_form = BookForm(request.POST or None)
shelf_form = SelfForm(request.POST or None)
if book_form.is_valid() and shelf_form.is_valid():
book = book_form.save()
shelf = shelf_form.save()
return redirect('some-list-view')
return render(request,
'addbook.html',
{'book_form': book_form, 'shelf_form': shelf_form})
# addbook.html
<form action="/addbook/" method="post">
{% csrf_token %}
{{ book_form }}
{{ shelf_form }}
<input type="submit" value="Submit" />
</form>

Django form missing a field

I have a model and a modelform to change some settings. The form is displayed allright, with the correct values, but when I submit the form one field is missing in the request.POST dict.
The model:
class NodeSettings(models.Model):
nodetype = models.CharField(max_length=8, editable=False)
nodeserial = models.IntegerField(editable=False)
upper_limit = models.FloatField(null=True, blank=True,
help_text="Values above this limit will be of different color.")
graph_time = models.IntegerField(null=True, blank=True,
help_text="The `width' of the graph, in minutes.")
tick_time = models.IntegerField(null=True, blank=True,
help_text="Number of minutes between `ticks' in the graph.")
graph_height = models.IntegerField(null=True, blank=True,
help_text="The top value of the graphs Y-axis.")
class Meta:
unique_together = ("nodetype", "nodeserial")
The view class (I'm using Django 1.3 with class-based views):
class EditNodeView(TemplateView):
template_name = 'live/editnode.html'
class NodeSettingsForm(forms.ModelForm):
class Meta:
model = NodeSettings
# Some stuff cut out
def post(self, request, *args, **kwargs):
nodetype = request.POST['nodetype']
nodeserial = request.POST['nodeserial']
# 'logger' is a Django logger instance defined in the settings
logger.debug('nodetype = %r' % nodetype)
logger.debug('nodeserial = %r' % nodeserial)
try:
instance = NodeSettings.objects.get(nodetype=nodetype, nodeserial=nodeserial)
logger.debug('have existing instance')
except NodeSettings.DoesNotExist:
instance = NodeSettings(nodetype=nodetype, nodeserial=nodeserial)
logger.debug('creating new instance')
logger.debug('instance.tick_time = %r' % instance.tick_time)
try:
logger.debug('POST[tick_time] = %r' % request.POST['tick_time'])
except Exception, e:
logger.debug('error: %r' % e)
form = EditNodeView.NodeSettingsForm(request.POST, instance=instance)
if form.is_valid():
from django.http import HttpResponse
form.save()
return HttpResponse()
else:
return super(EditNodeView, self).get(request, *args, **kwargs)
The relevant portion of the template:
<form action="{{ url }}edit_node/" method="POST">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="submit" value="Ok" />
</form>
Here is the debug output in the console when running the debug server:
2011-04-12 16:18:05,972 DEBUG nodetype = u'V10'
2011-04-12 16:18:05,972 DEBUG nodeserial = u'4711'
2011-04-12 16:18:06,038 DEBUG have existing instance
2011-04-12 16:18:06,038 DEBUG instance.tick_time = 5
2011-04-12 16:18:06,039 DEBUG error: MultiValueDictKeyError("Key 'tick_time' not found in <QueryDict: {u'nodetype': [u'V10'], u'graph_time': [u'5'], u'upper_limit': [u''], u'nodeserial': [u'4711'], u'csrfmiddlewaretoken': [u'fb11c9660ed5f51bcf0fa39f71e01c92'], u'graph_height': [u'25']}>",)
As you can see, the field tick_time is nowhere in the QueryDict from request.POST.
It should be noted that the field is in the web-browser, and when looking at the HTML source it looks just like the other fields in the form.
Anyone have any hints on what can be wrong?
Since you are using generic view, isn't it best to extend ProcessFormView instead of TemplateView?
EDIT
I've tried your code with TemplateView:
class EditNodeView(TemplateView):
Do you have a get_context_data to push the form?
def get_context_data(self, **kwargs):
instance = NodeSettings.objects.get(pk=kwargs['node_id'])
form = EditNodeView.NodeSettingsForm(instance=instance)
context = super(EditNodeView, self).get_context_data(**kwargs)
context['form'] = form
return context
The best way for editing existing objects is to get by primary key, I have the following in the urls.py:
url(r'^edit-node/(?P<node_id>\d+)/$', EditNodeView.as_view(), name='edit-node'),
I fetch the instance by primary key, might want to do some checking above like throwing 404 if not present.
In your models, you have nodetype and nodeserial as editable=False, how do you display or create those items if they are set to not editable? I've set them to True for testing purposes.
In the template, I have changed the first line to:
<form action="" method="POST">
I'm aware that there are a lot of changes but the above can view and edit your model properly. You could set the nodetype and nodeserial to be read-only at the form level to prevent people from editing it.

Django forms, inheritance and order of form fields

I'm using Django forms in my website and would like to control the order of the fields.
Here's how I define my forms:
class edit_form(forms.Form):
summary = forms.CharField()
description = forms.CharField(widget=forms.TextArea)
class create_form(edit_form):
name = forms.CharField()
The name is immutable and should only be listed when the entity is created. I use inheritance to add consistency and DRY principles. What happens which is not erroneous, in fact totally expected, is that the name field is listed last in the view/html but I'd like the name field to be on top of summary and description. I do realize that I could easily fix it by copying summary and description into create_form and loose the inheritance but I'd like to know if this is possible.
Why? Imagine you've got 100 fields in edit_form and have to add 10 fields on the top in create_form - copying and maintaining the two forms wouldn't look so sexy then. (This is not my case, I'm just making up an example)
So, how can I override this behavior?
Edit:
Apparently there's no proper way to do this without going through nasty hacks (fiddling with .field attribute). The .field attribute is a SortedDict (one of Django's internal datastructures) which doesn't provide any way to reorder key:value pairs. It does how-ever provide a way to insert items at a given index but that would move the items from the class members and into the constructor. This method would work, but make the code less readable. The only other way I see fit is to modify the framework itself which is less-than-optimal in most situations.
In short the code would become something like this:
class edit_form(forms.Form):
summary = forms.CharField()
description = forms.CharField(widget=forms.TextArea)
class create_form(edit_form):
def __init__(self,*args,**kwargs):
forms.Form.__init__(self,*args,**kwargs)
self.fields.insert(0,'name',forms.CharField())
That shut me up :)
From Django 1.9+
Django 1.9 adds a new Form attribute, field_order, allowing to order the field regardless their order of declaration in the class.
class MyForm(forms.Form):
summary = forms.CharField()
description = forms.CharField(widget=forms.TextArea)
author = forms.CharField()
notes = form.CharField()
field_order = ['author', 'summary']
Missing fields in field_order keep their order in the class and are appended after the ones specified in the list. The example above will produce the fields in this order: ['author', 'summary', 'description', 'notes']
See the documentation: https://docs.djangoproject.com/en/stable/ref/forms/api/#notes-on-field-ordering
Up to Django 1.6
I had this same problem and I found another technique for reordering fields in the Django CookBook:
class EditForm(forms.Form):
summary = forms.CharField()
description = forms.CharField(widget=forms.TextArea)
class CreateForm(EditForm):
name = forms.CharField()
def __init__(self, *args, **kwargs):
super(CreateForm, self).__init__(*args, **kwargs)
self.fields.keyOrder = ['name', 'summary', 'description']
From Django 1.9: https://docs.djangoproject.com/en/1.10/ref/forms/api/#notes-on-field-ordering
Original answer: Django 1.9 will support this by default on the form with field_order:
class MyForm(forms.Form):
...
field_order = ['field_1', 'field_2']
...
https://github.com/django/django/commit/28986da4ca167ae257abcaf7caea230eca2bcd80
I used the solution posted by Selene but found that it removed all fields which weren't assigned to keyOrder. The form that I'm subclassing has a lot of fields so this didn't work very well for me. I coded up this function to solve the problem using akaihola's answer, but if you want it to work like Selene's all you need to do is set throw_away to True.
def order_fields(form, field_list, throw_away=False):
"""
Accepts a form and a list of dictionary keys which map to the
form's fields. After running the form's fields list will begin
with the fields in field_list. If throw_away is set to true only
the fields in the field_list will remain in the form.
example use:
field_list = ['first_name', 'last_name']
order_fields(self, field_list)
"""
if throw_away:
form.fields.keyOrder = field_list
else:
for field in field_list[::-1]:
form.fields.insert(0, field, form.fields.pop(field))
This is how I'm using it in my own code:
class NestableCommentForm(ExtendedCommentSecurityForm):
# TODO: Have min and max length be determined through settings.
comment = forms.CharField(widget=forms.Textarea, max_length=100)
parent_id = forms.IntegerField(widget=forms.HiddenInput, required=False)
def __init__(self, *args, **kwargs):
super(NestableCommentForm, self).__init__(*args, **kwargs)
order_fields(self, ['comment', 'captcha'])
It appears that at some point the underlying structure of field order was changed from a django specific SordedDict to a python standard OrderedDict
Thus, in 1.7 I had to do the following:
from collections import OrderedDict
class MyForm(forms.Form):
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
original_fields = self.fields
new_order = OrderedDict()
for key in ['first', 'second', ... 'last']:
new_order[key] = original_fields[key]
self.fields = new_order
I'm sure someone could golf that into two or three lines, but for S.O. purposes I think clearly showing how it works is better than cleaver.
You could also create a decorator to order fields (inspired by Joshua's solution):
def order_fields(*field_list):
def decorator(form):
original_init = form.__init__
def init(self, *args, **kwargs):
original_init(self, *args, **kwargs)
for field in field_list[::-1]:
self.fields.insert(0, field, self.fields.pop(field))
form.__init__ = init
return form
return decorator
This will ensure that all the fields passed to the decorator come first.
You can use it like this:
#order_fields('name')
class CreateForm(EditForm):
name = forms.CharField()
The accepted answer's approach makes use of an internal Django forms API that was changed in Django 1.7. The project team's opinion is that it should never have been used in the first place. I now use this function to reorder my forms. This code makes use of an OrderedDict:
def reorder_fields(fields, order):
"""Reorder form fields by order, removing items not in order.
>>> reorder_fields(
... OrderedDict([('a', 1), ('b', 2), ('c', 3)]),
... ['b', 'c', 'a'])
OrderedDict([('b', 2), ('c', 3), ('a', 1)])
"""
for key, v in fields.items():
if key not in order:
del fields[key]
return OrderedDict(sorted(fields.items(), key=lambda k: order.index(k[0])))
Which I use in classes like this:
class ChangeOpenQuestionForm(ChangeMultipleChoiceForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
key_order = ['title',
'question',
'answer',
'correct_answer',
'incorrect_answer']
self.fields = reorder_fields(self.fields, key_order)
For recent versions of Django (>=1.9), see the other answers' Form.field_order
See the notes in this SO question on the way Django's internals keep track of field order; the answers include suggestions on how to "reorder" fields to your liking (in the end it boils down to messing with the .fields attribute).
Alternate methods for changing the field order:
Pop-and-insert:
self.fields.insert(0, 'name', self.fields.pop('name'))
Pop-and-append:
self.fields['summary'] = self.fields.pop('summary')
self.fields['description'] = self.fields.pop('description')
Pop-and-append-all:
for key in ('name', 'summary', 'description'):
self.fields[key] = self.fields.pop(key)
Ordered-copy:
self.fields = SortedDict( [ (key, self.fields[key])
for key in ('name', 'summary' ,'description') ] )
But Selene's approach from the Django CookBook still feels clearest of all.
Based on an answer by #akaihola and updated to work with latest Django 1.5 as self.fields.insert is being depreciated.
from easycontactus.forms import *
from django import forms
class CustomEasyContactUsForm(EasyContactUsForm):
### form settings and configuration
CAPTHCA_PHRASE = 'igolf'
### native methods
def __init__(self, *args, **kwargs):
super(CustomEasyContactUsForm, self).__init__(*args, **kwargs)
# re-order placement of added attachment field
self.fields.keyOrder.insert(self.fields.keyOrder.index('captcha'),
self.fields.keyOrder.pop(self.fields.keyOrder.index('attachment'))
)
### field defintitions
attachment = forms.FileField()
In the above we are extending an EasyContactUsForm base class as it is defined in django-easycontactus package.
I built a form 'ExRegistrationForm' inherited from the 'RegistrationForm' from Django-Registration-Redux. I faced two issues, one of which was reordering the fields on the html output page once the new form had been created.
I solved them as follows:
1. ISSUE 1: Remove Username from the Registration Form: In my_app.forms.py
class ExRegistrationForm(RegistrationForm):
#Below 2 lines extend the RegistrationForm with 2 new fields firstname & lastname
first_name = forms.CharField(label=(u'First Name'))
last_name = forms.CharField(label=(u'Last Name'))
#Below 3 lines removes the username from the fields shown in the output of the this form
def __init__(self, *args, **kwargs):
super(ExRegistrationForm, self).__init__(*args, **kwargs)
self.fields.pop('username')
2. ISSUE 2: Make FirstName and LastName appear on top: In templates/registration/registration_form.html
You can individually display the fields in the order that you want. This would help in case the number of fields are less, but not if you have a large number of fields where it becomes practically impossible to actually write them in the form.
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<form method="post" action=".">
{% csrf_token %}
#The Default html is: {{ form.as_p }} , which can be broken down into individual elements as below for re-ordering.
<p>First Name: {{ form.first_name }}</p>
<p>Last Name: {{ form.last_name }}</p>
<p>Email: {{ form.email }}</p>
<p>Password: {{ form.password1 }}</p>
<p>Confirm Password: {{ form.password2 }}</p>
<input type="submit" value="{% trans 'Submit' %}" />
</form>
{% endblock %}
The above answers are right but incomplete. They only work if all the fields are defined as class variables. What about dynamic form fields which have to be defined in the intitialiser (__init__)?
from django import forms
class MyForm(forms.Form):
field1 = ...
field2 = ...
field_order = ['val', 'field1', 'field2']
def __init__(self, val_list, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
vals = zip(val_list, val_list)
self.fields['val'] = forms.CharField(choices=vals)
The above will never work for val but will work for field1 and field2 (if we reorder them). You might want to try defining field_order in the initialiser:
class MyForm(forms.Form):
# other fields
def __init__(self, val_list, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
vals = zip(val_list, val_list)
self.fields['val'] = forms.CharField(choices=vals)
self.field_order = ['val', 'field1', 'field2']
but this will also fail because the field order is fixed before the call to super().
Therefore the only solution is the constructor (__new__) and set field_order to a class variable.
class MyForm(forms.Form):
# other fields
field_order = ['val', 'field1', 'field2']
def __new__(cls, val_list, *args, **kwargs):
form = super(MyForm, cls).__new__(cls)
vals = zip(val_list, val_list)
form.base_fields['val'] = forms.CharField(choices=vals)
return form

Categories