How to get Django form field from model field? - python

I'd like to create a form that includes fields from two separate models, along with some other regular (non-model) fields. The form will create an instance of each model. I don't think I can use inline formsets for this, since I don't want to include all the fields from both models.
I'd like to create the form field without hard-coding the type of the model fields.
I know I can get a form field from a model field using model_field.formfield(). But how can I get the specific model field?
My first solution:
def get_fields(model_class):
fields = {}
for f in model_class._meta.fields:
fields[f.name] = f
class MyForm(forms.Form):
foo_name = get_fields(Foo)['name'].formfield()
bar_name = get_fields(Bar)['name'].formfield()
other_field = ...
Is there an equivalent of get_fields already? Is this a bad idea? I'm uncomfortable relying on the model _meta attribute. Or am I going about this the completely wrong way?

You also can take a look at django.forms.models.fields_for_model.
That should give you a dictionary of fields, and then you can add the fields of the form

You should never have to build the fields yourself unless you want some special behavior.
This should be as simple as using two ModelForms and an extra Form inside one <form> tag in your template with one submit button.
in forms.py:
class Model1Form(forms.ModelForm):
class Meta:
model = Model1
fields = ('fields', 'you', 'want')
class Model2Form(forms.ModelForm):
class Meta:
model = Model2
fields = ('fields', 'you', 'want')
class ExtraFieldsForm(forms.Form):
extra_field = form.TextField() # or whatever field you're looking for
in views.py:
form1 = Model1Form(request.POST or None)
form2 = Model2Form(request.POST or None)
form3 = ExtraFieldsForm(request.POST or None)
if form1.is_valid() and form2.is_valid() and form3.is_valid():
form1.save()
form2.save()
form3.save()
...do other stuff like a redirect...
and in the template:
<form method="POST" action="">{% csrf_token %}
<fieldset>
{{ form1|as_uni_form }}
{{ form2|as_uni_form }}
{{ form3|as_uni_form }}
<div class="form_block">
<input type="submit" value="Save both models"/>
</div>
</fieldset>
</form>
I'm used to using django-uni-form, but you can render the form fields however you like. Good luck with your site.

There is now a documented API for getting the model field from a model class:
my_model_field = MyModel._meta.get_field('my_model_field_name')
Although it's not officially documented until Django 1.8, this should work with earlier Django versions too.
Once you have this, you can get the form field like so:
form_field = my_model_field.formfield()

Another solution can be to create one 'uber'-form that aggregates the concrete modelforms. The form supports the methods that a form normally provides and it forward them to all the child forms. Some will be simple, other complicated. The big advantage of that approach is that no code beyond the form is affected (client validation code and alike). The concept isn't really revolutionary but i guess complicated to add afterwards.
Paul

It's still undocumented as far as I know, but since Django 3.0 you can make a form field from a single model field called field_name with:
MyModel.field_name.field.formfield()
If your model is a parler.models.TranslatableModel, and the field_name is inside translations, you can use:
MyModel.translations.field.model.field_name.field.formfield()
Or, if you prefer a bit more verbose, but better documented ways, you can use:
MyModel._meta.get_field("field_name").formfield()
and
MyModel._parler_meta.get_model_by_related_name("translations")._meta.get_field("field_name").formfield()
See:
Django get_field
django-parler get_model_by_related_name

Related

How can I create a submit form in Django with a dropping down list?

I am just starting to work with Django and I have some problems with forms and dropping lists.
I have a model with two attributes, and I want to display one of the attributes in a dropping down list (this one will be unchangeable) and another one in a text field (this one will be changeable). Also, I have a submit button, so I want to change a second attribute in a text field and by pressing on the button. How can I do this? What would some examples be?
As you are starting to work with Django, you might or might not know about how Django handle forms.
In Django, forms can be handled in two ways:
User-created and managed forms (without any form class)
Class-managed forms (connected to Django models)
Documentation form Django Forms
Now let’s talk about the first type of forms (where you create your HTML form and manage the request sent to server):
These forms are simple to make and when there are only a few and are only suggested when you have a very small amount of inputs (I would say four or fewer inputs).
Here is a simple example of subscription of a newsletter with an email example.
<form id='sub-form' method="POST">
{% csrf_token %}
<div>
<input type="email" name="sub_email">
</div>
<input class="button" value="Subscribe" type="submit" id="subbutton">
</form>
So a very important thing to look at here is {% csrf_token %}, about which you can read more about here and about how it works and prevents cross-site request forgery. This token will be required when you make a request to Django server with any post request and data.
In this subscription form you see one <input> with name="sub_email". Take note of this as we will use this to get this value on the server as this is the key to its value, and then a simple Submit Button.
When you press Submit on a page let’s say url = "http://BASE_URL/home" you will receive a POST request on the view that handles that URL.
So now coming to the view.py, let’s say you only allow registered users to subscribe then the view will go something like this (assuming you are not expecting any other request from the home URL).
def home(request):
user=request.user
if request.method == "POST":
if user.is_authenticated:
email = request.POST['sub_email'] #Using name of input
#Logic to save this email
return HttpResponse("You are Subscribed",status=200)
else:
return HttpReposnse("You are not Authenticated",status=401)
else:
return render(request,"home.html")
Now as you are the expert of simple forms, let’s work with Django class-based forms.
These views are a little work when you have very few inputs, but they are a great help in manageability and when you have to work with large number of inputs.
You will request these Class Based Forms as in your question you are trying to send an instance of a model from your Models.py to a form to user.
I have a model of Posts that can be used for this example:
class Post(models.Model):
postTitle = models.CharField(max_length = 90,null=True)
subTitle = models.CharField(max_length = 160,null=True)
location = models.CharField(max_length = 3,default = 'IN',null=True)
Now according to your question, you are trying to let the user change one attribute, let’s say postTitle and for location you are not letting the user select one of the countries which is preselected and for your post.
Now we have to create a form for this. Forms in class based are created in Forms.py. If you don't have forms.py then you can create one right along models.py and views.py.
Now for the form, I would like to edit some existing data as you are saying one of the attributes (Fields) is fixed and another editable, but you get the value from the model.
class PostEditForm(ModelForm):
location = forms.CharField(label='Country ',widget=forms.Select(attrs={'class': 'Classes_HERE','placeholder':' Select a Country','disabled':'disabled'} ,choices=country_list),required=True)
class Meta:
model = Post
fields= ['postTitle','subTitle','location']
labels = {
'postTitle':'Title',
'subTitle':'Sub-Title',
}
widgets = {
'postTitle': forms.TextInput(attrs={'class': 'mention_class_here','placeholder':' Add Title'}),
'subTitle': forms.TextInput(attrs={'class': 'mention_class_here','placeholder':' Add Sub-Title'})
}
Attributes can be mentioned in forms fields the way I have mentioned them in the above example. I used disabled="disabled" to disable (not editable) location field and used forms.Select to make it drop down.
You might also see that I gave the location field a list to choose from. This is how you can create a list of your items. It's been quite some time when I wrote this, so there might be errors or it may not work for you, so just make sure you are referring to the current documentation and searching Stack Overflow for answers.
country_list = [
('', 'Select a Country'),
("AF", "Afghanistan"),
("AX", "Aland Islands"),
("AL", "Albania"),
("DZ", "Algeria"),
("AS", "American Samoa"),
("AD", "Andorra"),
("AO", "Angola"),
("AI", "Anguilla"),
("AQ", "Antarctica"),
("AG", "Antigua And Barbuda"),
("AR", "Argentina"),
("AM", "Armenia"),
("AW", "Aruba"),
.
.
.
Now this form can be passed as context in a view to an HTML page.
def editPost(request,post_id):
user=request.user
post = get_object_or_404(Post,id=post_id) #Getting the instance of Post
if user.is_authenticated:
formPost = PostEditForm(request.POST or None,instance=post)
if request.method=='POST':
if formPost.is_valid():
savedPost=formPost.save()
else:
return render(request,'postEdit.html',{'formPost':formPost})
else:
return HttpResponse("Not Authorized",status:401)
Now your HTML file postEdit.html should look something like this:
<form id="post-form" method="POST" enctype="multipart/form-data">
{% csrf_token %}
<div>
{{formPost}}
</div>
</form>
That is it and adding a submit button in the same form, you can now edit your instance of post that you passed along with this {{formPost}}. Combine your logic wherever you think needs a change to fit in what you want to do.
By no means I am saying all this code is in working condition, but it is shown only to illustrate the flow and working.

How to override ModelChoiceField / ModelMultipleChoiceField default widget with a template for each choice

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.

Display html form with bound data using django

I have django form, but in my HTML i added one extra input field (directly added in html page) which i can access it using request.POST.get('extra_field_name') in my django views.
If form.is_valid() is false i can get the form as HTML with the data displayed in the HTML but with empty value for the extra added field( directly added in html page)
How can i get the bounded form data for this newly added extra html field after validation fials.
Please provide your suggestions?
View:
html_added_field = ''
error_added_field = None
if request.method == 'POST':
html_added_field = request.POST.get('extra_field_name')\
if form.is_valid():
pass
else:
error_added_field = _('Error')
context = {'html_added_field':html_added_field,'error_added_field':error_added_field}
HTML:
<input type="text" value="{{ html_added_field }}" />{% if error_added_field %}<div class="error">{{ error_added_field }}</div>{% endif %}
This article by Bennett may help you.
http://www.b-list.org/weblog/2008/nov/09/dynamic-forms/
Briefly, you have to override the __init__ method of your form, adding there the new "extra_field_name". The fields are included in the self.fields list, so doing:
self.fields['extra_field_name'] = forms.CharField(put_here_definitions_for_your_field)
should do the trick.
Honestly the best way would be for your base django form to handle the extra field. Not using extra fields in the html. However if you can't/don't want, as xbello states, in http://www.b-list.org/weblog/2008/nov/09/dynamic-forms/ you have various tricks for handling dynamic fields.
I found more robust to use the form factory method. A function that generates a dynamic form, prepared for more future changes. Still, you have to decide the best approach :-)

Django formsets: make first required?

These formsets are exhibiting exactly the opposite behavior that I want.
My view is set up like this:
def post(request): # TODO: handle vehicle formset
VehicleFormSetFactory = formset_factory(VehicleForm, extra=1)
if request.POST:
vehicles_formset = VehicleFormSetFactory(request.POST)
else:
vehicles_formset = VehicleFormSetFactory()
And my template looks like this:
<div id="vehicle_forms">
{{ vehicles_formset.management_form }}
{% for form in vehicles_formset.forms %}
<h4>Vehicle {{forloop.counter}}</h4>
<table>
{% include "form.html" %}
</table>
{% endfor %}
</div>
That way it initially generates only 1 form, like I want. But I want that one form to be required!
When I dynamically add blank forms with JavaScript and vehicles_formset.empty_form all those extra forms are required, which I don't want.
From the docs:
The formset is smart enough to ignore extra forms that were not changed.
This is the behavior the first form is exhibiting (not what I want) but not the behavior that the extra forms are exhibiting (what I do want).
Is there some attribute I can can change to at least make one form required?
Found a better solution:
class RequiredFormSet(BaseFormSet):
def __init__(self, *args, **kwargs):
super(RequiredFormSet, self).__init__(*args, **kwargs)
for form in self.forms:
form.empty_permitted = False
Then create your formset like this:
MyFormSet = formset_factory(MyForm, formset=RequiredFormSet)
I really don't know why this wasn't an option to begin with... but, whatever. It only took a few hours of my life to figure out.
This will make all the forms required. You could make just the first one required by setting self.forms[0].empty_permitted to False.
New in Django 1.7: you can specify this behaviour with your formset_factory
https://docs.djangoproject.com/en/1.8/topics/forms/formsets/#validate-min
VehicleFormSetFactory = formset_factory(VehicleForm, min_num=1, validate_min=True, extra=1)
Well... this makes the first form required.
class RequiredFormSet(BaseFormSet):
def clean(self):
if any(self.errors):
return
if not self.forms[0].has_changed():
raise forms.ValidationError('Please add at least one vehicle.')
Only "problem" is that if there are 0 forms, then the clean method doesn't seem to get called at all, so I don't know how to check if there are 0. Really...this should never happen though (except that my JS has a bug in it, allowing you to remove all the forms).
Oh I think I see. Try this:
from django.forms.formsets import BaseFormSet, formset_factory
class OneExtraRequiredFormSet(BaseFormSet):
def initial_form_count(self):
return max(super(OneExtraRequiredFormSet,self).initial_form_count() - 1,0)
VehicleFormSetFactory = formset_factory(VehicleForm, formset=OneExtraRequiredFormSet, extra=1)
== Original answer below ==
When you say "at least make one form required", I assume you mean "make only one extra form required, regardless of how many have been added via javascript".
You will need to have hidden input on your page which contains the number of forms that have been added via javascript, and then use that number, minus 1, as the value to pass in as the extra attribute to your formsets constructor.

How do I associate input to a Form with a Model in Django?

In Django, how do I associate a Form with a Model so that data entered into the form are inserted into the database table associated with the Model? How do I save that user input to that database table?
For example:
class PhoneNumber(models.Model):
FirstName = models.CharField(max_length=30)
LastName = models.CharField(max_length=30)
PhoneNumber = models.CharField(max_length=20)
class PhoneNumber(forms.Form):
FirstName = forms.CharField(max_length=30)
LastName = forms.CharField(max_length=30)
PhoneNumber = forms.CharField(max_length=20)
I know there is a class for creating a form from the the model, but even there I'm unclear on how the data actually gets to the database. And I'd like to understand the inner workings before I move on to the time-savers. If there is a simple example of how this works in the docs, I've missed it.
Thanks.
UPDATED:
To be clear -- I do know about the ModelForm tool, I'm trying to figure out how to do this without that -- in part so I can better understand what it's doing in the first place.
ANSWERED:
With the help of the anwers, I arrived at this solution:
Form definition:
class ThisForm(forms.Form)
[various Field assignments]
model = ThisModel()
Code in views to save entered data to database:
if request_method == 'POST':
form = ThisForm(request.POST)
if form.is_valid():
for key, value in form.cleaned_data.items():
setattr(form.model, key, value)
form.model.save(form.model)
After this the data entered in the browser form was in the database table.
Note that the call of the model's save() method required passage of the model itself as an argument. I have no idea why.
CAVEAT: I'm a newbie. This succeeded in getting data from a browser to a database table, but God only knows what I've neglected or missed or outright broken along the way. ModelForm definitely seems like a much cleaner solution.
Back when I first used Forms and Models (without using ModelForm), what I remember doing was checking if the form was valid, which would set your cleaned data, manually moving the data from the form to the model (or whatever other processing you want to do), and then saving the model. As you can tell, this was extremely tedious when your form exactly (or even closely) matches your model. By using the ModelForm (since you said you weren't quite sure how it worked), when you save the ModelForm, it instantiates an object with the form data according to the model spec and then saves that model for you. So all-in-all, the flow of data goes from the HTML form, to the Django Form, to the Django Model, to the DB.
Some actual code for your questions:
To get the browser form data into the form object:
if request.method == 'POST':
form = SomeForm(request.POST)
if form.is_valid():
model.attr = form.cleaned_data['attr']
model.attr2 = form.cleaned_data['attr2']
model.save()
else:
form = SomeForm()
return render_to_response('page.html', {'form': form, })
In the template page you can do things like this with the form:
<form method="POST">
{{ form.as_p }}
<input type="submit"/>
</form>
That's just one example that I pulled from here.
I'm not sure which class do you mean. I know that there were a helper, something like form_for_model (don't really remember the exact name; that was way before 1.0 version was released). Right now I'd it that way:
import myproject.myapp.models as models
class PhoneNumberForm(forms.ModelForm):
class Meta:
model = models.PhoneNumber
To see the metaclass magic behind, you'd have to look into the code as there is a lot to explain :]. The constructor of the form can take instance argument. Passing it will make the form operate on an existing record rather than creating a new one. More info here.
I think ModelForm.save documentation should explain it. With its base class (Form) you would need to use the Form.cleaned_data() to get the field values and set them to appropriate Model fields "by hand". ModelForm does all that for you.
The Django documentation is pretty clear on this subject. However, here is a rough guide for you to get started: You can either override the form's save method or implement that functionality in the view.
if form.is_valid() # validation - first the fields, then the form itself is validated
form.save()
inside the form:
def save(self, *args, **kwargs):
foo = Foo()
foo.somefield = self.cleaned_data['somefield']
foo.otherfield = self.cleaned_data['otherfield']
...
return foo.save()

Categories

Resources