How to customize a django form (adding multiple columns) - python

Currently, I am building a form using the default django template like this:
class old_form(forms.Form):
row_1 = forms.FloatField(label='Row 1')
row_2_col_1 = forms.FloatField(label='Row 2_1')
row_2_col_2 = forms.FloatField(label='Row 2_2')
html = str(old_form())
However, I would like to add multiple columns to my template, and still use django forms object to define parameters.
New temp. should something like (or it can loop through all the variables):
def getdjtemplate():
dj_template ="""
<table>
<tr>{{ table.row_1 }}</tr>
<tr>
<td>{{ table.row_2_col_1 }}</td>
<td>{{ table.row_2_col_2 }}</td>
</tr>
"""
return dj_template
djtemplate = getdjtemplate()
newtmpl = Template(djtemplate)
My question is how to 'combine' the new template and class old_form()?
Thanks for the help!

You can customize the form HTML using its fields, as shown in the documentation. You are doing this in an unusual way; normally you would have the template in a file instead of returning it from a function, but you can still do this:
from django.template import Context
def getdjtemplate():
dj_template = """
<table>
{% for field in form %}
<tr>{{ field }}</tr>
{% endfor %}
</table>
"""
return dj_template
form = old_form()
djtemplate = getdjtemplate()
newtmpl = Template(djtemplate)
c = Context({'form': form})
newtmpl.render(c)

Related

Render label names instead of integer fields in Django templates

As part of my models, I have an IntergerField "choices".
These choices have been labelled. (Label 1 = Straw, Label 2 = Yellow...)
However, the HTML renders the integer rather than the actual labels.
What do I need to do return the labels and not the field's interger?
Is it somethng I need to do in views? Or do I address it directly in the html file?
Code below:
Models
CHOICE1=(
('',''),
(1,'Straw'),
(2,'Yellow'),
)
class Model1(models.Model):
user = models.ForeignKey(User,blank=True,on_delete=models.CASCADE)
Choice_A = models.IntegerField(choices=Choice1,default=0)
Views
def account(request):
review_list = Model1.objects.all
return render(request,"main/account.html", {'review_list':review_list})
HTML
<h6>Champagnes</h6>
{% for Model1 in review_list%}
<table class="table table-hover table-striped table-bordered ">
{% if Model1.user == user%}
<tr>
<th>Text</th><th>{{Model1.Choice_A }}</th>
</tr>
{%endif%}
</table>
{% endfor %}
You need to use the following in your template:
{{ Model1.get_Choice_A_display }}
It will render the string instead of the integer
You do this with the .get_fieldname_display(…) method [Django-doc], so here get_Choice_A_display:
<th>Text</th><th>{{Model1.get_Choice_A_display }}</th>
Note: normally the name of the fields in a Django model are written in snake_case, not PascalCase, so it should be: choice_a instead of Choice_A.

Saving class-based view formset items with a new "virtual" column

I have a table inside a form, generated by a formset.
In this case, my problem is to save all the items after one of them is modified, adding a new "virtual" column as the sum of other two (that is only generated when displaying the table, not saved).
I tried different ways, but no one is working.
Issues:
This save is not working at all. It worked when it was only one form, but not for the formset
I tried to generate the column amount as a Sum of box_one and box_two without success. I tried generating the form this way too, but this is not working:
formset = modelformset_factory(
Item, form=ItemForm)(queryset=Item.objects.order_by(
'code__name').annotate(amount=Sum('box_one') + Sum('box_two')))
This issue is related to this previous one, but this new one is simpler:
Pre-populate HTML form table from database using Django
Previous related issues at StackOverflow are very old and not working for me.
I'm using Django 2.0.2
Any help would be appreciated. Thanks in advance.
Current code:
models.py
class Code(models.Model):
name = models.CharField(max_length=6)
description = models.CharField(max_length=100)
def __str__(self):
return self.name
class Item(models.Model):
code = models.ForeignKey(Code, on_delete=models.DO_NOTHING)
box_one = models.IntegerField(default=0)
box_two = models.IntegerField(default=0)
class Meta:
ordering = ["code"]
views.py
class ItemForm(ModelForm):
description = CharField()
class Meta:
model = Item
fields = ['code', 'box_one', 'box_two']
def save(self, commit=True):
item = super(ItemForm, self).save(commit=commit)
item.box_one = self.cleaned_data['box_one']
item.box_two = self.cleaned_data['box_two']
item.code.save()
def get_initial_for_field(self, field, field_name):
if field_name == 'description' and hasattr(self.instance, 'code'):
return self.instance.code.description
else:
return super(ItemForm, self).get_initial_for_field(
field, field_name)
class ItemListView(ListView):
model = Item
def get_context_data(self, **kwargs):
data = super(ItemListView, self).get_context_data()
formset = modelformset_factory(Item, form=ItemForm)()
data['formset'] = formset
return data
urls.py
app_name = 'inventory'
urlpatterns = [
path('', views.ItemListView.as_view(), name='index'),
item_list.html
...
<div>
<form action="" method="post"></form>
<table>
{% csrf_token %}
{{ formset.management_form }}
{% for form in formset %}
<thead>
<tr>
{% if forloop.first %}
<th>{{ form.code.label_tag }} </th>
<th>{{ form.description.label_tag }} </th>
<th> <label>Amount:</label> </th>
<th>{{ form.box_one.label_tag }} </th>
<th>{{ form.box_two.label_tag }} </th>
{% endif %}
</tr>
</thead>
<tbody>
<tr>
<td>{{ form.code }}</td>
<td>{{ form.description }}</td>
<td>{{ form.amount }}</td>
<td>{{ form.box_one }}</td>
<td>{{ form.box_two }}</td>
</tr>
</tbody>
{% endfor %}
<input type="submit" value="Update" />
</table>
</form>
</div>
...
Annotating query with virtual column
Sum is an aggregate expression and is not how you want to be annotating this query in this case. Instead, you should use an F exrepssion to add the value of two numeric fields
qs.annotate(virtual_col=F('field_one') + F('field_two'))
So your corrected queryset would be
Item.objects.order_by('code__name').annotate(amount=F('box_one') + F('box_two'))
The answer provided by cezar works great if intend to use the property only for 'row-level' operations. However, if you intend to make a query based on amount, you need to annotate the query.
Saving the formset
You have not provided a post method in your view class. You'll need to provide one yourself since you're not inheriting from a generic view that provides one for you. See the docs on Handling forms with class-based views. You should also consider inheriting from a generic view that handles forms. For example ListView does not implement a post method, but FormView does.
Note that your template is also not rendering form errors. Since you're rendering the formset manually, you should consider adding the field errors (e.g. {{ form.field.errors}}) so problems with validation will be presented in the HTML. See the docs on rendering fields manually.
Additionally, you can log/print the errors in your post method. For example:
def post(self, request, *args, **kwargs):
formset = MyFormSet(request.POST)
if formset.is_valid():
formset.save()
return SomeResponse
else:
print(formset.errors)
return super().post(request, *args, **kwargs)
Then if the form does not validate you should see the errors in your console/logs.
You're already on the right path. So you say you need a virtual column. You could define a virtual property in your model class, which won't be stored in the database table, nevertheless it will be accessible as any other property of the model class.
This is the code you should add to your model class Item:
class Item(models.Model):
# existing code
#property
def amount(self):
return self.box_one + self.box_one
Now you could do something like:
item = Item.objects.get(pk=1)
print(item.box_one) # return for example 1
print(item.box_two) # return for example 2
print(item.amount) # it will return 3 (1 + 2 = 3)
EDIT:
Through the ModelForm we have access to the model instance and thus to all of its properties. When rendering a model form in a template we can access the properties like this:
{{ form.instance.amount }}
The idea behind the virtual property amount is to place the business logic in the model and follow the approach fat models - thin controllers. The amount as sum of box_one and box_two can be thus reused in different places without code duplication.

Flask / Python / WTForms validation and dynamically set SelectField choices

I'm trying to create a simple Flask / Python one page web app that uses dynamically created choices for a SelectField.
However, I can't get it to POST using dynamically created choices, and there's also some funny validation behaviour going on (will explain after the code)
I created a minimum failing example here:
from flask import Flask, render_template, flash, redirect
from flask_wtf import Form
from wtforms import IntegerField, SubmitField, SelectField
from wtforms.validators import DataRequired, NumberRange, Optional
# Set up app and config
DEBUG = True
SECRET_KEY = '42721564'
app = Flask(__name__)
app.config.from_object(__name__)
# Main stuff starts here
class SelectFieldForm(Form):
default_field = SelectField('Default Set SelectField', choices=[(i, i) for i in range(0,60,5)], coerce=int)
default_field_2 = SelectField('Default Set SelectField', choices=[(i, i) for i in range(0,60,5)], coerce=int)
dynamic_field = SelectField('Dynamically Set Selectfield', choices=[], validators=[Optional()], coerce=int)
get_default_field_value_difference = SubmitField(label='Get Default Field Difference')
deduct_dynamic_field_value = SubmitField(label='Deduct Dynamic Field Value')
#app.route('/mfe-dynamic-selectfield', methods=['GET', 'POST'])
def failingSelectField():
form = SelectFieldForm()
if form.validate_on_submit():
print("validated")
difference = form.default_field_2.data - form.default_field.data
if form.get_default_field_value_difference.data:
flash( difference )
form.dynamic_field.choices = [(i,i) for i in range(0,difference,5)]
return render_template('mfe-dynamic-selectfield.html', form=form)
if form.deduct_dynamic_field_value.data:
if form.dynamic_field.data:
deducted = difference - form.dynamic_field.data
flash( deducted )
else:
flash( "no dynamic field value chosen")
return render_template('mfe-dynamic-selectfield.html', form=form)
else:
flash( "nope" )
return render_template('mfe-dynamic-selectfield.html', form=form)
if __name__ == '__main__':
app.run()
Bringing up the page works just fine, and immediately flashes "nope", as expected.
Calculating the difference between the default set fields:
-works every time as long they are both set to '0'
-if either field is not set to '0', every other POST fails validation, and the time correctly calculates the difference and dynamically sets the last field.
Trying to POST using the dynamically set field fails every time.
Am I missing something very obvious here?
I'm rendering using this:
{% block content %}
<form action="" method="post" name="mfe-dynamic-selectfield">
{{ form.hidden_tag() }}
<table>
<tr>
<td> Calculated value </td>
<td>
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul class=flashes>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
</td>
</tr>
<br>
<tr>
<td>Default SelectField 1</td>
<td>Default SelectField 2</td>
<td>Dynamic Selectfield</td>
</tr>
<br>
<tr>
<td>{{ form.default_field }}</td>
<td>{{ form.default_field_2 }}</td>
<td>{{ form.dynamic_field }}</td>
</tr>
<tr>
<td>{{ form.get_default_field_value_difference }}</td>
<td>{{ form.deduct_dynamic_field_value }}</td>
</tr>
</table>
</form>
{% endblock %}
It's failing every other time because the value of form.dynamic_field is oscillating between 0 and None. The form passes validation only when the value is None.
This is because form.dynamic_field.choices is [] (an empty list) at the time of validation. So any value that comes there is rejected. You probably want to dynamically set the choices before you try the validating; maybe with something like this:
#app.route('/mfe-dynamic-selectfield', methods=['GET', 'POST'])
def failingSelectField():
form = SelectFieldForm()
# Calculate dynamic field choices
try:
difference = form.default_field_2.data - form.default_field.data
form.dynamic_field.choices = [(i,i) for i in range(0,difference,5)]
except TypeError:
pass
if form.validate_on_submit():
# (continue as usual)
Now the form will validate as expected.
Of course, you should probably add some code in front to make sure that the default_fields do have valid choices values (not just any two integers). Another option is to put dynamic_field in a second form. Then you can validate the first form, and use its values to calculate valid choices for the second one.

flask-wtf same form on same page using for loop

I'm brand new to programming. I came up with a project to help me learn and I'm stuck already. I'm using Flask, Flask-SQLAlchemy and Flask-wtf.
I'm trying to create a club attendance system that lists members and checks them off if they are present and logs the amount they paid (either $15 for 1 lesson, or $25 for the week). I have a table that I populate from my database that looks like this:
I want to click on submit to mark the person as present but this ticks the checkbox for everyone in the list and sets the amount paid to the same for everyone.
I have tried lots of things. I have seen similar issues here and people suggesting using FieldList and FormField - I tried this with no luck. Here is my Form code:
class MemberForm(Form):
form_id = HiddenField()
member_id = DecimalField('id')
member_name = StringField('name')
attend_date = StringField('date', default=todays_date())
is_here = BooleanField('here')
has_paid = SelectField('Amount', choices=[(15, '15'), (25, '25')])
submit = SubmitField("Submit")
def __init__(self, *args, **kwargs):
super(MemberForm, self).__init__(*args, **kwargs)
read_only(self.member_name)
My controller code:
#app.route('/', methods=['GET', 'POST'])
def home():
members = Member.query.order_by(Member.name).all()
form = MemberForm()
if request.method == 'POST': # TODO form validation and database stuff
print('got this far')
print(form.data)
return render_template('index.html', title='Tong Long',
today=todays_date(), members=members,
form=form)
and the jinja2 template part:
<table width="483" border="1">
<tbody>
<tr>
<th width="271"><strong>Member</strong></th>
<th width="152"><strong>Grade</strong></th>
<th><strong>Last Seen</strong></th>
<th width="38"><strong>Paid?</strong></th>
<th><strong>Is Here?</strong></th>
<th>Submit</th>
</tr>
{% for member in members %}
<form action="" method="post" name="{{ member.id }}">
<tr>
<td>{{form.member_name(value=member.name)}}</td>
{% for g in member.grade %}
<td>{{ g.grade }}</td>
{% endfor %}
<td>{{ form.attend_date }}</td>
<td>{{ form.has_paid }}</td>
<td>{{form.is_here}}</td>
<td>
{{ form.submit }}
</td>
</tr>
</form>
{% endfor %}
</tbody>
</table>
Viewing the rendered HTML I can see that all the fields have the same id.
I'm starting to think this can't be done with WTForms. Will I need to use javascript perhaps (something I know nothing about). Or manually create the forms rather than using WTF? Any help appreciated!
This is very late, but perhaps it is helpful to somebody.
What calabash is doing, is create one single form and then display it multiple times in the template.
However, to achieve the desired outcome (independend forms with independend submit buttons), multiple forms need to be created within the route function. They can be passed as a list to the template and then looped over. (A simpler solution would be one form with one submit button and dynamically created "lines" for each member. See FieldList...)
Logic:
def home():
members = Member.query.order_by(Member.name).all()
forms = []
for member in members:
form = MemberForm(prefix=member.name)
form.member_name.data = member.name
forms.append(form)
# validation:
for form in forms:
if form.submit.data and form.validate_on_submit():
# do_something here for each form, e.g. write to database
return render_template('index.html', title='Tong Long',
today=todays_date(),
forms=forms,
members=members)
The different forms need to have individual prefixes. They need to be validated individually and it needs to be checked which submit-button was used.
Note: It is perhaps not a good idea to use a form field for the name, as that information is already known from the members database entry and it might not be intended to change it here. A simple text label would make more sense in that case.
The table rows in the template could look like this:
{% for form in forms %}
<form action="" method="post">
{{ form.hidden_tag() }}
<tr>
<td>{{ form.member_name }}</td>
<td>{{ members[loop.index0].grade }}</td>
<td>{{ form.attend_date }}</td>
<td>{{ form.has_paid }}</td>
<td>{{ form.is_here }}</td>
<td>{{ form.submit }}</td>
</tr>
</form>
{% endfor %}

Display table of objects django

I need to display a table from my database with Django. The obvious way is to manually type in the table headings and loop through query results of model.objects.all(). However, being quite lazy, I want to do this automatically, i.e. load all fields from model through introspection to display as column headings and load all field values to display as rows. This approach can also save me some time later because I don't have to update my template code when my model changes. I got it to work but there are two problems:
I can't find away to load the AutoField field (id) value so I have to slice off the ID column.
The code looks quite messy especially with the use of random template tags.
Here is my code. Please note that the code works fine so I'll skip all the imports as they are correct:
views.py I use serializers to serialize the data, a trick I read somewhere on stackoverflow
def index(request):
fields = MyModel._meta.fields
data = serializers.serialize("python", MyModel.objects.all())
context_instance = RequestContext(request, {
'data' : data,
'fields' : fields,
})
return TemplateResponse(request, 'index.html', context_instance)
template/index.html: note that I have to slice off the ID column by slicing off the first element of the fields list
{% with fields|slice:"1:" as cached_fields %}
<table>
<thead>
<tr>
{% for field in cached_fields %}
<th>{% get_verbose_name field %}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for instance in data %}
<tr>
{% for field in cached_fields %}
<td>{% get_value_from_key instance.fields field %}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
{% endwith %}
templatetags/extra_tags.py
# tag to get field's verbose name in template
#register.simple_tag
def get_verbose_name(object):
return object.verbose_name
# tag to get the value of a field by name in template
#register.simple_tag
def get_value_from_key(object, key):
# is it necessary to check isinstance(object, dict) here?
return object[key.name]
serializers.serialize("json or xml", Model.objects.all()) formats return the id field; probably not what you are looking for but some of the jQuery grid plugins can further automate the task.
Yay! I found a work around thanks to Видул Петров's suggestion about serializing the data to json, which allows me to load the pk field as well. It still feels too manual and hackish (and verbose) but I think I'm getting close. Please help me refactor this code further.
views.py Serialize data into a list of JSON objects and parse it into a list of dictionaries to pass it to template
from django.utils import simplejson as json
def index(request):
fields = MyModel._meta.fields
data = json.loads(serializers.serialize("json", MyModel.objects.all()))
def parse_data(data):
result = []
# flatten the dictionary
def flatten_dict(d):
"""
Because the only nested dict here is the fields, let's just
remove the 'fields' suffix so that the fields can be loaded in
template by name
"""
def items():
for key, value in d.items():
if isinstance(value, dict):
for subkey, subvalue in flatten_dict(value).items():
yield subkey, subvalue
else:
yield key, value
return dict(items())
for d in data:
# change the 'pk' key name into its actual name in the database
d[Employee._meta.pk.name] = d.pop('pk')
# append the flattend dict of each object's field-value to the result
result.append(flatten_dict(d))
return result
context_instance = RequestContext(request, {
'data' : parse_data(data),
'fields' : fields,
})
return TemplateResponse(request, 'index.html', context_instance)
template/index.html The template is much nicer now
<table>
<thead>
<tr>
{% for field in cached_fields %}
<th>{% get_verbose_name field %}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for d in data %}
<tr>
{% for field in fields %}
<td>{% get_value_from_key d field %}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>

Categories