My group and I are trying to develop a "very-dynamic" web app with Django.
We have a "configurations" template for the users where everyone can configure his/her own space.
Every configuration option should be read from the DB.
view:
def config(request):
if 'logged' not in request.session:
return redirect(login)
apps_list = App.objects.all()
varS = varsesion(request)
context = Configurations.objects.all()
for row in context:
row.form = eval(row.form) #Not sure at all if this is ok
print (row.form)
context.App = 'config'
return render(request, "index.html", {'context': context, 'apps_list ': apps_list , 'varS': varS,
'context.App': context.App})
And in our DB we have the Configurations model table like this:
+----+-----+-----+-----+---------+---------------+
| id |user |Title|Body |OptionBtn| form |
+----+-----+-----+-----+---------+---------------+
| N |userA| X | X | X | DataConfig() |
+----+-----+-----+-----+---------+---------------+
| N2 |userA| X | X | X | ColorsConfig()|
+----+-----+-----+-----+---------+---------------+
| N3 |userA| X | X | X |ButtonsConfig()|
+----+-----+-----+-----+---------+---------------+
And many other columns that I'll skip...
Of course every Form value in the DB's form field exists in forms.py with their respective names.
The problem comes when I try to iterate these forms fields in our template (every other column from the DB is displayed properly like buttons, titles, texts, etc)
Here is the template (I've skipped some of the attrs in the example table):
<div class="breadcrumb">
<li class="breadcrumb-item">
<h1 style="color:#871830">Configurations panel</h1>
</li>
</div>
{% for configuration in context %}
<div style="" id="panel{{ configuration.codConfi }}" class="breadcrumb form-inline">
<div class="form-group col-12" style="color:#228718"><h3>{{ configuration.title}}</h3></div>
<div class="form-group col-3 m-1">
<label class="form-group">{{ configuration.body }}</label>
</div>
<button id="btn{{ configuration.codConfi }}" type="submit" class="btn btn-info ml-auto mr-0 mr-md-3 my-2 my-md-4">{{ configuration.OptionBtn }}</button>
</div>
All this is displayed properly and perfect. But when it comes to the form DB colum...
<!-- _______________________Forms iterations__________________________ -->
<form style="display:none" id="frm{{ configuration.codConfi }}" class="breadcrumb form-inline" action="/Config/{{ configuration.codConfi }}" method="POST" enctype="application/x-www-form-urlencoded">{% csrf_token %}
<div class="form-group col-12" style="color:#228718"><h3>Configure {{ configuration.Title }}</h3></div>
{% for field in configuration.form %}
<div class="form-group col-3 m-1">
<label class="form-group">{{ field.label }}: </label>
{{ field }}
</div>
{% endfor %}
<button type="submit" class="btn btn-success ml-auto mr-0 mr-md-3 my-2 my-md-4">Apply changes</button>
</form>
{% endfor %}
(If you see some attrs that are not showed in my example table it's just because I haven't typed all of them).
Instead of displaying the actual forms properly, it is displaying the value of form DB's column as a string. For instance, for the first value in form column (DataConfig()) it is displaying every letter as a string in the iteration (first it displays "D", then "a", then "t", etc until the last ")").
How can I tell Django it is not a String value but a variable?
On your Django model Configuration you could add a property that fetches and instanciates the actual form class.
For example:
class Configuration(models.Model):
...
def get_form(self):
# 'forms_module' is the Python module (file) that holds the form classes
# 'self.form' is a string that holds the name of the form class
form_class = getattr(forms_module, self.form)
# create an (un-bound) instance of the form
return form_class()
Then, in your template (assuming that configuration is an instance of the model Configuration) you can change this line:
{% for field in configuration.form %}
into this
{% for field in configuration.get_form %}
Note: for this to work, you will need to store the form class name without the parethesis in your DB field (or remove the parethesis before calling getattr(forms_module, self.form)).
If you need something more specific you will need to add more info to your question.
Related
I have a Django form that receives a text (that I copy from Google Classroom: a bunch of student comments). I use these comments to make student's attendance. What I want to achieve is:
Accessing /insertion/ url via GET user receive the page form as a response, to choose the class (class01, class02, etc) and to past the text
When the user clicks on submit in this form (post method), it is redirect to the same /insertion/ url, but now the form is bound to the data submited, and the page shows a preview page (based on a boolean variable I'm passing through context), showing what students are present and what are absent based on the text informed. At that page, a new submit button will be shown below a text like "if everything's ok, hit the ok button".
After click this ok button, a pdf will be generated and the user will be redirected to /files/ url, to see the generated pdf and previous generated pdf.
views.py
def insertion(request):
context = {}
if request.method == 'GET':
form = AttendanceDataForm()
context.update({"form": form})
if request.method == 'POST':
form = AttendanceDataForm(request.POST)
context.update({"form": form})
if form.is_valid():
lesson = form.cleaned_data['lesson']
raw_text = form.cleaned_data['raw_text']
# Get course students
course_students = md.Student.objects.filter(course_id=lesson.course_id)
# Get present students based on raw text informed
present_students = [s for s in course_students if s.full_name in raw_text]
# Get absent students based on raw text informed
absent_students = [s for s in course_students if s.full_name not in raw_text]
context.update({
"present_students": present_students,
"absent_students": absent_students,
"render_preview": True
})
context.update({"active_freq": True})
return render(request, 'core/insertion.html', context)
def files(request):
context = {}
if request.method == 'POST':
# How can I access all expensive calculation I did in the previous view?
context.update({"active_gen": True})
return render(request, "core/files.html", context)
insertion.html
<div class="row">
<div class="col-12 col-md-6">
<h3>Informar Frequência</h3>
{% crispy form %}
</div>
<div class="col-12 col-md-6">
{% if render_preview %}
<div class="container">
<div class="row p-4 bg-white rounded mt-4">
<div class="col-12 col-sm-6">
<h5>Alunos presentes</h5>
<ul class="previewer-list">
{% for student in present_students %}
<li>{{ student.id }} - {{ student.full_name }}</li>
{% endfor %}
</ul>
</div>
<div class="col-12 col-sm-6">
<h5>Alunos ausentes</h5>
<ul class="previewer-list">
{% for student in absent_students %}
<li>{{ student.id }} - {{ student.full_name }}</li>
{% endfor %}
</ul>
</div>
</div>
<p class="mt-3">If everything's ok, hit the OK button</p>
<form method="post" action="{% url "core:files" %}">
{% csrf_token %}
<button type="submit" class="btn btn-primary">OK</button>
</form>
</div>
{% endif %}
</div>
</div>
I could get to implement 1 and 2, but 3 is a mistery right now. What I couldn't get is how I can access the expensive calculations I did in insertion view in the files view. How can I do that?
Here's a solution using session framework.
We'll save the calculations in the session and access those values in another view later.
For starters, we'll just save the ids (pk) of the students instead of the student instances because they are not JSON serializable [See note below].
def insertion(request):
# do expensive calucations ...
present_ids = [s.pk for s in present_students]
absent_ids = [s.pk for s in absent_students]
request.session['attendance_data'] = {
'present_ids': present_ids,
'absent_ids': absent_ids
}
def files(request):
attendance_data = request.session.get('attendance_data')
if not attendance_data:
# show error or something else ...
pass
present_students = md.Student.objects.filter(
pk__in=attendance_data['present_ids']
)
absent_students = md.Student.objects.filter(
pk__in=attendance_data['absent_ids']
)
# generate the pdf ...
Note: If you wish, you can also save the student instances in the session but you'll have to change the SESSION_SERIALIZER setting to use the PickleSerializer. See notes about session serialization.
You could submit the primary keys as form data in hidden fields. Just choose an appropriate delimiter based on your primary key (for example, don't delimit with a hyphen if you use a GUID primary key).
<form method="post" action="{% url "core:files" %}">
{% csrf_token %}
<input type="hidden"
name="present"
value="{% for s in present_students %}{{ s.pk }},{% endfor %}"
>
<input type="hidden"
name="absent"
value="{% for s in absent_students %}{{ s.pk }},{% endfor %}"
>
<button type="submit" class="btn btn-primary">OK</button>
</form>
Then in the view you can pick up the PKs in the view from the form data then request.
def files(request):
context = {}
if request.method == 'POST':
present_pks = request.POST.pop('present').split(',')[:-1]
absent_pks = request.POST.pop('absent').split(',')[:-1]
# do type conversions if needed
...
# Because we already have the pks separated, we can combine them
# for the query in order to do just 1 query
course_students = md.Student.objects.filter(pk__in=present_pks + absent_pks).all()
absent_students = []
present_students = []
for student in course_students:
if student.pk in absent_pks:
absent_students.append(student)
else:
present_students.append(student)
I'm building a simple Django app that lets users track stuff for specific days:
It records entries with a name and a date using the upper form.
<form action="" method="post" style="margin-bottom: 1cm;">
{% csrf_token %}
<div class="form-group">
{{ form.entry_name.label_tag }}
<div class="input-group">
<input type="text" class="form-control" id="{{ form.entry_name.id_for_label }}" name="{{ form.entry_name.html_name }}" aria-label="new entry field">
{{ form.entry_date }}
<div class="input-group-append">
<button type="submit" class="btn btn-primary">Add</button>
</div>
</div>
<small id="{{ form.entry_name.id_for_label }}Help" class="form-text text-muted">This can be anything you want to track: An activity, food, how you slept, stress level, etc.</small>
</div>
</form>
Below the form, there are quick add buttons that let users quickly add a new entry with a specific name. In addition, I'd like to use the date selected in the form above. I.e., if a user sets a date in the upper form but then clicks one of the suggested buttons, it should still use the selected date for adding the new entry.
This is what the code for the suggested buttons currently looks like:
{% if entry_counts and entry_dict|length > 0 %}
<div class="card" style="margin-bottom: 1cm;">
<div class="card-body">
<div class="card-title">Suggested entries</div>
{% for name, count in entry_counts.items %}
<form method="post" action="{% url 'app:add_entry_with_date' name form.entry_date.value %}" style="display: inline-block;">
{% csrf_token %}
<button type="submit" class="btn btn-secondary" name="{{ name }}" style="margin-bottom: 5px;">{{ name }}</button>
</form>
{% endfor %}
</div>
</div>
{% endif %}
I'm trying to access the selected date and pass it to the corresponding view: action="{% url 'app:add_entry_with_date' name form.entry_date.value %}", but it still adds the entry at the default date (today) not on the selected date.
My guess, is that the problem is with <button type="submit" class="btn btn-secondary" name="{{ name }}" style="margin-bottom: 5px;">{{ name }}</button>. Does this just pass name but not the date when submitting?
Here are the relevant URL patterns:
class DateConverter:
regex = '\d{4}-\d{2}-\d{2}'
def to_python(self, value):
return datetime.datetime.strptime(value, '%Y-%m-%d')
def to_url(self, value):
return value
register_converter(DateConverter, 'yyyymmdd')
urlpatterns = [
path('', views.index, name='index'),
path('add/<entry_name>/', views.add_entry, name='add'),
path('add/<entry_name>/<yyyymmdd:entry_date>/', views.add_entry, name='add_entry_with_date'),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
So whenever adding a new entry (with or without specific date), my add_entry view is called:
#login_required
def add_entry(request, entry_name, entry_date=datetime.date.today()):
# only works for post
# if request.method == 'POST':
entry_name = entry_name.strip().lower()
entry = Entry.objects.create(name=entry_name, date=entry_date, owner=request.user)
return HttpResponseRedirect(reverse('app:index'))
You're trying to pass the date value as part of the URL,
{% url 'app:add_entry_with_date' name form.entry_date.value %}
however, form.entry_date.value won't have a defined value unless your form is bound before it's passed to the template for rendering. As a result, probably, your add_entry view is being called via the URL pattern add, not add_entry_with_date.
Another challenge with your current code is that you want to have the same date-type input element ({{ form.entry_date }}) serve as the source for different, separate HTML forms (you have the first form for adding entries, and then you have one form for each suggested entry). Changing the value of that input when the page is already rendered in the browser won't update the action URLs for the suggested entry forms—unless you use JavaScript.
I think the quickest way to make your existing code work is to write some JavaScript to manipulate the action attribute for the suggested-entry forms whenever the date input value changes.
Manipulating action attributes looks strange though, and also I believe your view, which should work only for POST requests, should use only POST data and not rely on URL parameters. Therefore I recommend that you use hidden inputs, e.g.
<input type="hidden" name="variable-name" value="temporary-date-value-here">
and then have the JavaScript manipulate these input elements' values instead of the form action attribute. Of course you have to update the view too.
Update: sample JS for synchronizing inputs across forms
HTML:
<html>
<head>
<title>Sample synchronization of inputs across forms</title>
</head>
<body>
<h1>Sample synchronization of inputs across forms</h1>
<h2>Form 1</h2>
<form>
<input class="synchronized-inputs" type="date" name="input_date">
</form>
<h2>Form 2</h2>
<form>
<input class="synchronized-inputs" type="date" name="input_date">
</form>
<script src="sync-inputs-across-forms.js" type="text/javascript"></script>
</body>
</html>
JavaScript (sync-inputs-across-forms.js):
var syncedInputs = document.getElementsByClassName('synchronized-inputs');
Array.from(syncedInputs).forEach((source) => {
source.addEventListener('change', () => {
Array.from(syncedInputs).forEach((target) => {
target.value = source.value;
});
});
});
Note that:
Without the JS, selecting a date in one form won't update the other form's value
As indicated in the original answer, you'd want to use hidden inputs for the suggested-entry forms. To do that, just change type="date" to type="hidden" for the other form. Synchronization will still work as the value is tracked in the (invisible parts of the) DOM.
I have looked online and found that I can use macros to render different content to my modal. Yet there is something missing in my code that prevents it from getting updated and I'm not sure of what exactly.
#app.route("/candidates")
def candidate_fun():
mapping={
"001":"Bangalore",
"002":"Delhi",
"003":"Chennai",
"004": "Mumbai",
"005":"Kolkata",
"006":"Hyderabad"
}
myclient=pymongo.MongoClient(uri)
mydb = myclient["codefundo"]
mycol=mydb['cand_reg']
result=[]
for x in mycol.find():
obj=NewsSearch()
# import pdb; pdb.set_trace()
t=dict(x)
t['a'],t['b'],t['c']=obj.news_candidate(search_term=str(x["First Name"].lower()+" "+x["Last Name"].lower()+ " election"))
# t['News']=str(a+". "+b+". "+c)
result.append(t)
# result.append({'News':obj.news_candidate(search_term=str(result["First Name"]+" "+result["Last Name"]+" election"))})
return flask.render_template("candidate.html",result=result,mapping=mapping)
While the python code isn't of significance, I have provided it to show that I am passing a list result of type dict.
HTML Jinja
<!--MODAL PART-->
{% macro render_modal(a,b,c) -%}
div class="modal-body">
<p>{{a}}</p>
<p>{{b}}</p>
<p>{{c}}</p>
</div>
{%- endmacro%}
<!-- Jinja to make a call -->
{% for key in result %}
<div class="col-6">
<button type="button" class="btn btn-info" data-toggle="modal" data-target="#exampleModalLong">Info</button>
{{render_modal(key['a'],key['b'],key['c'])}}
<!-- Just to test if the value sent is received {{key['a']}} -->
</div>
{% endfor %}
It returns the same data over the modal box for all entries being passed. I want for it to show me the specific values - key[a], key[b], key[c] for every new key in the iteration.
Similar question here: Passing a row ID in a table to a modal in flask (python)
You need to set an id on your modal and refer to that specific id in your button to identify a specific modal. In order for each modal to render different inputs, you need to set different ids for each button-modal group.
In your case, add a unique identifier for each key object in addition to your data entries, e.g.
# in the routes
t['id'] = # unique id identifying the modal
t['a'] = ...
t['b'] = ...
# etc.
Then in the for-loop that calls the modal macro, include the unique id in some way on the data-target attribute of your button:
{% for key in result %}
<div class="col-6">
<button type="button" data-target="#exampleModalLong{{ key.id }}" class="btn btn-info" data-toggle="modal">
Info
</button>
{{ render_modal(key) }}
</div>
{% endfor %}
Use the exact same name you used for data-target for the id of your modal div:
{% macro render_modal(key) -%}
<div class="modal-body" id="exampleModalLong{{ key.id }}">
<p>{{ key.a }}</p>
<p>{{ key.b }}</p>
<p>{{ key.c }}</p>
</div>
{%- endmacro%}
Note that this will inject a lot of HTML into your code if you have a lot of entries in your for-loop. As mentioned in the Stack Overflow post linked at the top, you can consider defining just one modal, and use AJAX to change the modal content.
I'm working on a Flask webapp that asks (internal) users to select options for an arbitrary number of files that will be in a directory on our network so that some other scripts can process the files according to those options (not relevant to this issue, but if you're curious they are A/V files we need to process).
The problem I'm having is that I can't seem to both dynamically generate the form fields needed (there could be 0 - dozens of files in the server directory the app is looking in) and collect the form data for each instance of the form class I created for the input objects. How do you shoehorn n form instances into an instance of another form class??
I have a base IngestForm class and an ObjectForm class that describes fields pertaining to each individual object. My basic suspicion is that wtforms can't have a subclass that includes other forms... but I can print everything out at various steps before the form gets POSTed and see all the data that I expect as a dict, and I can see that the sub-forms are there as wtforms objects. From my index.html template I can see all the expected data from the ObjectForm instances. But once the super-form is posted, all that is returned is a blank choicesDict (see below) and the submit value. Does the IngestForm instance get reinitialized or something weird when I hit Submit?
Here's what I have now. I have set up a dict for choices where each key is the path of the file in question and the value is an instance of the ObjectForm class:
forms.py
class ObjectForm(FlaskForm):
"""
Fields for an individual object
"""
targetFilePath = wtforms.HiddenField('targetObjectPath')
option1 = wtforms.BooleanField('Option1?')
option2 = wtforms.BooleanField("Option2?")
# etc.
class IngestForm(FlaskForm):
'''
General input form
'''
choicesDict = {}
# also tried just targetObject = wtforms.FormField(ObjectForm)
submit = wtforms.SubmitField('Submit')
routes.py:
[import relevant stuff]
#app.route('/index',methods=['GET','POST'])
def index():
# GET A DICT OF PATHS AND BASENAMES TO PROCESS {'fullPath':'basename'}
objects = listObjects.list_objects()
class OneObject(forms.ObjectForm):
pass
choices = {}
for path,_object in objects.items():
choices[path] = OneObject(targetPath=path,targetBase=_object)
# also tried setattr(forms.IngestForm,'choicesDict',choices)
form = forms.IngestForm()
form.choicesDict = choices
if form.is_submitted():
return redirect(url_for('status'))
return render_template(
'index.html',title='Index',objects=objects,form=form
)
#app.route('/status',methods=['GET','POST'])
def status():
# DO THE STUFF
ingest.html template:
{% block content %}
<h1>Here are files to ingest:</h1>
<form action="{{url_for('status')}}" method='POST'>
<div>
{% for item,vals in form.choicesDict.items() %}
<p>{{vals.targetBase.data}}</p>
<p>{{vals.option1.label}}{{vals.option1()}}</p>
<p>{{vals.option2.label}} {{vals.option3()}}</p>
<p>{{vals.etc.label}}{{vals.etc()}}</p>
{%endfor%}
</div>
{{form.submit()}}
</form>
{% endblock %}
status.html template just takes the POST data. Not really relevant here except to say I can see it is getting none of the choicesDict
OK so I solved this in a really hack-y way but whatever. I used a jinja2 macro following this example and in my form template constructed field names/ids that are unique to the files I'm interested in.
So for each file ['a.mov','b.mov','c.mp4'] in my network directory, I create a dict like so: {'a.mov': SubclassObjectForm, 'b.mov': SubclassObjectForm } and I have a MainForm instance field that includes this dict. When I render the form, the jinja macro creates name and id attributes for the <label> and <input> fields as needed that include a prefix for the file in question.
For example <input name='targetObjectFilePath-movieA.mov' value='/full/path/to/file' type='hidden>.
When the form gets POSTed, it's just a matter of pulling the relevant bits of data in my view.
I hope this helps someone! It might not be elegant or 'pro' but it gets my task done. Next step... styling!
forms.py
class ObjectForm(FlaskForm):
"""
Fields for an individual object
"""
targetFilePath = wtforms.HiddenField('targetObjectPath')
targetBase = wtforms.HiddenField('targetObjectBasename')
option1 = wtforms.BooleanField('Option1?')
option2 = wtforms.BooleanField("Option2?")
# etc.
class IngestForm(FlaskForm):
'''
General input form
'''
choicesDict = wtforms.HiddenField(default='no choices')
submit = wtforms.SubmitField('Submit')
routes.py
[import relevant stuff]
#app.route('/index',methods=['GET','POST'])
def index():
# GET A DICT OF PATHS AND BASENAMES TO PROCESS {'fullPath':'basename'}
objects = listObjects.list_objects()
class OneObject(forms.ObjectForm):
pass
choices = {}
for path,_object in objects.items():
choices[path] = OneObject(targetPath=path,targetBase=_object)
form = forms.IngestForm()
form.choicesDict = choices
return render_template(
'index.html',title='Index',form=form
)
#app.route('/status',methods=['GET','POST'])
def status():
data = request.form.to_dict(flat=False)
# DO THE STUFF
index.html
{% import "macros.html" as macros %}
{% extends "base.html" %}
{% block content %}
<h1>Here are files to ingest:</h1>
<form action="{{ url_for('status') }}" method='POST'>
{{ form.hidden_tag() }}
{{ form.csrf_token }}
{# iterate over the form dict with subforms included: #}
{% for item,vals in form.choicesDict.items() %}
<div>
{# iterate over subform fields and include target file basename #}
{% for field in vals %}
{{macros.render_field(field,vals.targetBase.data)}}
{% endfor %}
</div>
{%endfor%}
{{form.submit()}}
</form>
{% endblock %}
macros.html
{% macro render_field(field, uniqueName) %}
<p>
{# I only care about 2 kinds of data: filenames/paths and boolean options. #}
{% if field.type == 'BooleanField' %}
<label for="{{ field.id }}-{{ uniqueName }}">{{ field.label.text }}</label>
<input name="{{ field.id }}-{{ uniqueName }}" id="{{ field.id }}-{{ uniqueName }}" type="checkbox" default=""></input>
{# render hidden input for full path for later processing #}
{% elif field.name == 'targetPath' %}
<input name="{{ field.id }}-{{ uniqueName }}" id="{{ field.id }}-{{ uniqueName }}" type="hidden" value="{{ field.data }}"/>
{# use basename for local id purposes and display value as label for users #}
{% elif field.name == 'targetBase' %}
<label for="{{ field.id }}-{{ uniqueName }}">{{ uniqueName }}</label>
<input name="{{ field.id }}-{{ uniqueName }}" id="{{ field.id }}-{{ uniqueName }}" type="hidden" value="{{ field.data }}"/>
{% endif %}
</p>
{% endmacro %}
I have been working with a Django form that can generate a number of fields dynamically according to the parameters passed in, as I have learnt from SO, like this:
class Review_Form(forms.Form):
def __init__(self, *args, **kwargs):
sentences = kwargs.pop('sentences')
super(Review_Form, self).__init__(*args, **kwargs)
counter = 1
for q in sentences:
self.fields['review' + str(counter)] = forms.CharField(label='Review' + str(counter), required=False)
counter += 1
As for the corresponding html, I pass the each field separately into contest['block'], where block is a list of dictionaries, with key value pair indicating the ith field of the form and the corresponding items I need.
So the html is as following:
<form action="{% url 'done_review' title%}" method="post">
<div class="container">
<div class="row">
{% for b in block %}
<div class="8u">
<p> {{b.sent}} </p>
</div>
<div class="4u">
<input class="front_form-control" value="{{b.text}}" type={{b.field}}
</div>
</div>
{% csrf_token %}
{% endfor %}
</div>
<div class="container">
<div class="row">
<div class="12u">
<button class="btn btn-lg btn-info" type="submit" value="Submit">Done Review
</button>
</div>
</div>
</div>
{% csrf_token %}
</form>
Then, unfortunately, the form is not valid after I submit it. I tried to test it by printing the errors in views.py, like the following:
if form.is_valid():
# do something
else:
print form.errors, 'here1'
print form.non_field_errors(), 'here2'
field_errors = [(field.label, field.errors) for field in form]
print field_errors, 'here3'
It prints out like this:
here1
here2
[('Review1', []), ('Review2', []), ...many more... ('Review38', [])] here3
I really don't understand why the form is not valid and I have got stuck here for days and googled everywhere. Hope someone could help me here.
Many thanks!!!
Finally, the problem is solved.
This happens because the form is unbound, because of one of my careless mistakes:
form = Review_Form(sentences=sents)
instead of what it should be:
form = Review_Form(request.POST, sentences=sents)
I answered this because I think this is probably useful for other new developers like me.
Read more on unbound and bound forms, if you like, from here: Django form API