django modelform and additional fields and their order - django

I have a modelform and im creating additional fields (that do not exist in model) for its form.
I know you can reorder the fields in modelform like it says in the docs.
But the problem is - i want the additional fields to be rendered BEFORE the other fields.
Is it possible to somehow reorder the fields of the form before rendering? How does form object keep track of the order of its fields anyway?
Alan

No matter. It seems i found answer already and this seems to do the trick, since i have added 2 additional fields:
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
self.build_fields()
l = len(self.fields.keyOrder)
for i in range(0,2):
f = self.fields.keyOrder[l-1]
self.fields.keyOrder.pop(l-1)
self.fields.keyOrder.insert(0, f)
This above was my initial fix. Later on i found out that it did not cut any more. Then i did this :
class AlertForm(forms.ModelForm):
class Meta:
model = Message
fields = model_fields
def __init__(self, *args, **kwargs):
super(AlertForm, self).__init__(*args, **kwargs)
self.build_fields()
newKeyOrder = []
newKeyOrder.append('field_that_had_to_be_first')
if typechange:
newKeyOrder.append('field_thats_sometimes_necessary')
newKeyOrder += model_fields
self.fields.keyOrder = newKeyOrder

The solutions above no longer works with django 2 (I don't know since when)...
But now, there's an ordered dict fields property on ModelForm that we can use to reorder the fields...
class MyForm(forms.ModelForm):
class Meta:
fields = ['model_field1', 'model_field2']
model = MyModel
extra_field = forms.CharField()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for k in self._meta.fields:
self.fields.move_to_end(k)

The fields attribute of your ModelForm`s Meta class define which fields to show and in which order.
Use the fields attribute of the ModelForm's inner Meta class. This attribute, if given, should be a list of field names to include in the form. The order in which the fields names are specified in that list is respected when the form renders them.

Related

Django admin does not show extra fields added in init method of modelform

I found a couple of questions regarding this, but I specifically wonder about how to add a field in the ModelForms __init__() method.
This is, because I get the number of fields from a function and need to display them in the admin:
class SomeForm(forms.ModelForm):
class Meta:
model = Product
fields = ["name", "price",]
def __init__(self, *args, **kwargs):
number_of_fields = get_number of fields(kwargs["instance"])
print(number_of_fields) ## e.g. 3, gives output
super().__init__(*args, **kwargs)
for i in range(number_of_fields):
self.fields[i] = forms.CharField("test", required = False)
But the fields do not show up in the Template Admin edit page. What did I miss? No error popping up either ...
Try something like this... but you need to pass field name into self.base_fields['name_of_the_field'] somehow
class SomeForm(forms.ModelForm):
class Meta:
model = Product
fields = ["name", "price",]
def __init__(self, *args, **kwargs):
number_of_fields = get_number_of_fields(kwargs["instance"])
print(number_of_fields) ## e.g. 3, gives output
for i in range(number_of_fields):
self.base_fields['name_of_the_field'] = forms.CharField(initial="test", required = False)
super(SomeForm, self).__init__(*args, **kwargs)

modelformset_factory: display more fields for specific instances

I'm using a modelformset_factory to edit multiple instances of Product in the same form:
ProductFormSet = modelformset_factory(Product, fields=('code', 'state'))
form_products = ProductFormSet()
It works well.
But now I need to display an additional field of the Product model in the form but only for a specific instance of Product. I'm not sure if it can be done in a simple manner in Django. Is it possible to do so using a modelformset_factory?
You can specify the form in the modelformset_factory, so create a model form (in forms.py if you have one) override the __init__method to add extra fields.
I would move the fields from the formsetfactory arguments to the form
in forms.py (assuming you have one)
class ProductForm(forms.ModelForm):
model = Product
def __init__(self, *args, **kwargs):
super(ProductForm, self).__init__(*args, **kwargs)
if 'instance' in kwargs :
product = kwargs['instance']
# to add an extra field, add something like this
self.fields['extra_field'] = forms.CharField(max_length=30)
class Meta:
fields = ('code', 'state')
Then pass that to your modelformset factory with the form argument
ProductFormSet = modelformset_factory(Product, form=ProductForm )
form_products = ProductFormSet()

Django model form - Exclude a field that has no model field

I have a simple model form what I use through the admin interface. Some of my model fields store datas that require a bit more time to calculate (they come from other sites). So I decided to put an extra boolean field to the form to decide to crawl these datas again or not.
class MyModelForm(forms.ModelForm):
update_values = forms.BooleanField(required=False) #this field has no model field
class Meta:
model = MyModel
This extra field doesn't exist in the model because only the form needs it.
The problem is that I only want it to appear if it's an existing record in the database.
def __init__(self, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
if self.instance.pk is None:
#remove that field somehow
I tried nearly everything. Exclude it, delete the variable but nothing wants to work. I also tried dynamically add the field if self.instance.pk is exists but that didn't work too.
Any idea how to do the trick?
Thanks for your answers.
You could subclass the form and add the extra field in the subclass:
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
class MyUpdateModelForm(MyModelForm):
update_values = forms.BooleanField(required=False) #this field has no model field
class Meta:
model = MyModel
You can then override the get_form method of your admin, which is passed the current instance: get_form(self, request, obj=None, **kwargs)
Rather than removing the field in __init__ if instance.pk is not None, how about adding it if it is None? Remove the class-level declaration and just change the logic:
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
def __init__(self, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
if self.instance and self.instance.pk is not None:
self.fields['update_values'] = forms.BooleanField(required=False)

CBV Django Form View set data for ChoiceField

I'm using the Django Form View and I want to enter custom choices per user to my Choicefield.
How can I do this?
Can I use maybe the get_initial function?
Can I overwrite the field?
When I want to change certain things about a form such as the label text, adding required fields or filtering a list of choices etc. I follow a pattern where I use a ModelForm and add a few utility methods to it which contain my overriding code (this helps keep __init__ tidy). These methods are then called from __init__ to override the defaults.
class ProfileForm(forms.ModelForm):
class Meta:
model = Profile
fields = ('country', 'contact_phone', )
def __init__(self, *args, **kwargs):
super(ProfileForm, self).__init__(*args, **kwargs)
self.set_querysets()
self.set_labels()
self.set_required_values()
self.set_initial_values()
def set_querysets(self):
"""Filter ChoiceFields here."""
# only show active countries in the ‘country’ choices list
self.fields["country"].queryset = Country.objects.filter(active=True)
def set_labels(self):
"""Override field labels here."""
pass
def set_required_values(self):
"""Make specific fields mandatory here."""
pass
def set_initial_values(self):
"""Set initial field values here."""
pass
If the ChoiceField is the only thing you're going to be customising, this is all you need:
class ProfileForm(forms.ModelForm):
class Meta:
model = Profile
fields = ('country', 'contact_phone', )
def __init__(self, *args, **kwargs):
super(ProfileForm, self).__init__(*args, **kwargs)
# only show active countries in the ‘country’ choices list
self.fields["country"].queryset = Country.objects.filter(active=True)
You can then make your FormView use this form with like this:
class ProfileFormView(FormView):
template_name = "profile.html"
form_class = ProfileForm

Reorder modelform fields

I need to reorder the fields in a modelform that came from another base class. Couldn't find a solution to that. "Address" in the snippet below always show up at the beginning in the HTML template. How can I move it further down with the rendered template? Thanks in advance.
class Address:
street= ...
city= ...
class Customer(Address):
name = ...
...
class CustomerForm(ModelForm):
def __init__(...)
super(CustomerForm, self).__init__(*args, **kw)
self.fields.keyOrder=[
'name',
'Address', #<-- I want "Address" appear after the name in my template
#<-- This obviously is not the right code.
class Meta:
model = Customer
-P
In Django 1.9 they add new argument to Form class
Now you can change the order by defining field_order
for example adding two fields to userena application form:
class SignupFormExtra(SignupForm):
"""
A form to demonstrate how to add extra fields to the signup form, in this
case adding the first and last name.
"""
first_name = forms.CharField(label=_(u'First name'),
max_length=30,
required=False)
last_name = forms.CharField(label=_(u'Last name'),
max_length=30,
required=False)
field_order=['first_name','last_name']
You can use it to any form that inherit class Form.
By default Form.field_order=None, which retains the order in which you define the fields in your form class. If field_order is a list of field names, the fields are ordered as specified by the list and remaining fields are appended according to the default order.
Re-ordering is quite tedious:
# django's SortedDict stores ordering of its fields in this list-type attribute:
keyorder = self.fields.keyOrder
# Remove fields which we want to displace:
keyorder.remove('street')
keyorder.remove('city')
# Get position where we want them to put in:
i = keyorder.index('name') + 1
# Insert items back into the list:
keyorder[i:i] = ['city', 'street']
probably better just to list all the fields again in proper order:
class MyForm(ModelForm):
class Meta:
model=Customer
fields=[..., 'name', 'street', 'city', ...]
from django import forms
class CustomForm(forms.Form):
ORDER = ('field1', 'field2', 'field3')
def __init__(self, *args, **kwargs):
super(CustomForm, self).__init__(*args, **kwargs)
fields = OrderedDict()
for key in self.ORDER:
fields[key] = self.fields.pop(key)
self.fields = fields
When we upgraded to Django 1.7 Skyjur's use of .keyOrder stopped working (Django is using collections.OrderedDict now instead). As a result, I had to find a work-around and this is what appears to be working for me:
from collections import OrderedDict
...
class MyForm(forms
def __init__(self, *args, **kwargs):
fields = OrderedDict()
for key in ("my_preferred_first_field", "my_preferred_second_field"):
fields[key] = self.fields.pop(key)
for key, value in self.fields.items():
fields[key] = value
self.fields = fields
In newer Django the fields attribute is a collections.OrderedDict which since Python 3.2 has a new method move_to_end, so you can simply:
class CustomForm(forms.Form):
...
# Note the following must contain all the fields in the form in the order
# you desire.
CUSTOM_ORDER = ('field1', 'field2', 'field3')
def __init__(self, *args, **kwargs):
super(CustomForm, self).__init__(*args, **kwargs)
# Use custom order for form fields.
for field_name in CUSTOM_ORDER:
self.fields.move_to_end(field_name)