I am trying to commit fields generated from a FiedList but getting the error:
AttributeError: 'str' object has no attribute 'data'
What I'm trying to do is add a list of fields to the database which I can then retrieve and display on the page.
#App.py
#app.route('/', methods=['GET', 'POST'])
def index():
form = MainSubscriptionForm()
if form.validate_on_submit():
for x in form.subscription:
sub = Subscription(company=x.company.data, description=x.description.data)
db.session.add(sub)
db.session.commit()
elif request.method == 'GET':
list = Subscription.query.all()
return render_template('index.html', title="Home", form=form, list=list)
#forms.py
class SubscriptionForm(FlaskForm):
company = StringField(('Company'), validators=[DataRequired(), Length(min=0, max=20)])
description = StringField(('Description'), validators=[Length(min=0, max=120)])
save = SubmitField('Save')
class MainSubscriptionForm(FlaskForm):
subscription = FieldList(FormField(SubscriptionForm), min_entries=1)
#models.py
class Subscription(db.Model):
id = db.Column(db.Integer, primary_key=True)
company = db.Column(db.String(20))
description = db.Column(db.String(120))
#index.html
{% extends "base.html" %}
{% from 'bootstrap/form.html' import render_form_row %}
{% block content %}
<form method="post">
{{ form.csrf_token() }}
{% for sub in form.subscription %}
{{ render_form_row(sub) }}
{% endfor %}
</form>
{{ list }}
{% endblock %}
The issue was that a dictionary was being sent and therefore it was crashing on the below line:
sub = Subscription(company=x.company.data, description=x.description.data)
The solution was to get the key value as per below:
sub = Subscription(company=x.data['company'], description=x.data['description'])
Related
I'm using WTForms and Flask, I am trying to create a form where I can enter information about a recipe, but the product_name SelectField is returning None every time.
The form:
class CreateRecipeForm(Form):
product_name = SelectField(choices=get_craftables_options())
product_quantity = IntegerField(default=1)
job_field = SelectField(choices=['ALC', 'GSM', 'WVR'])
line_item_list = FieldList(FormField(RecipeLineForm), min_entries=6)
save_button = SubmitField()
The view:
#bp.route('/edit/new', methods=('GET', 'POST'))
def create_recipe():
form = CreateRecipeForm()
if request.method == 'POST':
selected_product = Item.query.get(form.product_name.data)
(do stuff here)
The template
{% block content %}
<form method="post">
{{ render_field(form.product_name) }}
{{ render_field(form.product_quantity) }}
{{ render_field_no_label(form.line_item_list) }}
{{ render_field_no_label(form.save_button) }}
</form>
{% endblock %}
I believe your issue lies in declaring the product_name. Make sure the get_craftables_options() is supposed to be a function and is returning a list of items compatible with the choices argument.
product_name = SelectField(choices=get_craftables_options())
I have seen a few similar questions here, but none of the solutions seem to apply here(the problem is usually that it lacks a csrf token, which is not the case here).
I have a form with four fields- 3 drop down lists with SelectField and one StringField- built using flask wtforms. I tried adding an edit feature to this, which uses the same HTML template, but this it isn't getting validated(not entering the form.validate_on_submit section). This is the code for the function:
#app.route('/movements/<int:movement_id>/edit', methods=['GET', 'POST'])
def edit_movement(movement_id):
movement = Movement.query.get_or_404(movement_id)
form = MovementForm()
if form.validate_on_submit():
product = Product.query.filter_by(id=form.product.data).first()
from_location = Location.query.filter_by(id=form.from_location.data).first()
to_location = Location.query.filter_by(id=form.to_location.data).first()
if int((Balance.query.filter_by(product = product.name).filter_by(location = from_location.name).first()).balance) < int(form.quantity.data) and from_location.name != "":
flash("Invalid movement. Quantity of the product is insufficient.")
else:
movement.product_id = product.id
movement.product = product.name
movement.from_location_id = from_location.id
movement.from_location = from_location.name
movement.to_location_id = to_location.id
movement.to_location = to_location.name
movement.quantity = form.quantity.data
db.session.commit()
flash('The product movement has been edited!', 'success')
return redirect(url_for('movements'))
elif request.method == 'GET':
form.product.choices = [(product.id,product.name) for product in Product.query.all()]
form.from_location.choices = [(location.id,location.name) for location in Location.query.all()]
form.to_location.choices = [(location.id,location.name) for location in Location.query.all()]
form.quantity.data = movement.quantity
edit_button = True
return render_template('movements.html',form=form, edit_button=edit_button)
This is the code for the form:
class MovementForm(FlaskForm):
product = SelectField("Product", choices = [])
from_location = SelectField("From Location", choices = [], coerce=int)
to_location = SelectField("To Location", choices = [], coerce=int)
quantity = StringField("Quantity", validators=[DataRequired()])
add_movement = SubmitField("Add Movement")
And this is the model for the table:
class Movement(db.Model):
id = db.Column(db.Integer, primary_key=True)
product_id = db.Column(db.Integer, db.ForeignKey('product.id'), nullable=False)
product = db.Column(db.String(50), nullable=False)
from_location_id = db.Column(db.Integer, db.ForeignKey('location.id'))
from_location = db.Column(db.String(50))
to_location_id = db.Column(db.Integer, db.ForeignKey('location.id'))
to_location = db.Column(db.String(50))
quantity = db.Column(db.Integer, nullable=False)
timestamp = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
The HTML code for the form:
<form action="" method="POST">
{{ form.csrf_token }}
{{ form.product.label }}
{{ form.product }}
{{ form.from_location.label }}
{{ form.from_location }}
{{ form.to_location.label }}
{{ form.to_location }}
{{ form.quantity.label }}
{{ form.quantity }}
{% if edit_button %}
<input type="submit" value="Edit Movement">
{% else %}
{{ form.add_movement }}
{% endif %}
</form>
validate_on_submit is a convenient function that combines a check for whether the form was submitted (i.e., a POST, PUT, PATCH, or DELETE) with call to form.validate. If validation fails, the dictionary held byform.errors will get populated with useful information.
A useful step to debug your problem would be to log (print) the contents of form.errors if validate_on_submit returns False.
The form.errors in your code is as follows:
{'product': ['Not a valid choice'], 'from_location': ['Not a valid choice'], 'to_location': ['Not a valid choice']}
So it is reasonable for the validate_on_submit to return False.
If you comment out these fields and leave only the quantity it should work. The following changes worked for me and I managed to update the quantity in the DB. If it works also for you then you could try to uncomment each field and debug further.
Change the form to:
class MovementForm(FlaskForm):
quantity = StringField("Quantity", validators=[DataRequired()])
add_movement = SubmitField("Add Movement")
and the route to:
#app.route('/movements/<int:movement_id>/edit', methods=['GET', 'POST'])
def edit_movement(movement_id):
movement = Movement.query.get_or_404(movement_id)
form = MovementForm()
if form.validate_on_submit():
movement.quantity = form.quantity.data
db.session.commit()
flash('The product movement has been edited!', 'success')
return redirect(url_for('movements'))
elif request.method == 'GET':
form.quantity.data = movement.quantity
print(form.errors)
edit_button = True
return render_template('movements.html', form=form, edit_button=edit_button)
Change the template to:
<form action="" method="POST">
{{ form.csrf_token }}
{{ form.quantity.label }}
{{ form.quantity }}
{% if edit_button %}
<input type="submit" value="Edit Movement">
{% else %}
{{ form.add_movement }}
{% endif %}
</form>
Update:
The issue you are facing is described here.
Please try the following and if it works as expected then set similarly the remaining fields (it worked for me and I managed to update the quantity and the from_location/id in the DB):
the form:
class MovementForm(FlaskForm):
fromloc = [(location.id,location.name) for location in Location.query.all()]
from_location_id = SelectField("From Location ID", choices = fromloc, coerce=int)
quantity = StringField("Quantity", validators=[DataRequired()])
add_movement = SubmitField("Add Movement")
the route:
#app.route('/movements/<int:movement_id>/edit', methods=['GET', 'POST'])
def edit_movement(movement_id):
movement = Movement.query.get_or_404(movement_id)
form = MovementForm()
if form.validate_on_submit():
movement.from_location_id = form.from_location_id.data
movement.from_location = (Location.query.filter_by(id = form.from_location_id.data).first()).name
movement.quantity = form.quantity.data
db.session.commit()
flash('The product movement has been edited!', 'success')
return redirect(url_for('movements'))
elif request.method == 'GET':
form.from_location_id.choices = [(location.id,location.name) for location in Location.query.all()]
form.quantity.data = movement.quantity
print(form.errors)
edit_button = True
return render_template('movements.html', form=form, edit_button=edit_button)
the template:
<form action="" method="POST">
{{ form.csrf_token }}
{{ form.from_location_id.label }}
{{ form.from_location_id }}
{{ form.quantity.label }}
{{ form.quantity }}
{% if edit_button %}
<input type="submit" value="Edit Movement">
{% else %}
{{ form.add_movement }}
{% endif %}
</form>
I'm still not quite sure why my form wasn't validating, but my function started working after I replaced if form.validate_on_submit() with if request.method == 'POST'.
I'm new to python django, trying to create an employee records project, on the django admin site I added some employees and their information, on the django site, I had the hyperlink for the individual employee, but when I click on the individual name, the next page comes all the employees information instead of the particular one, how can I only make it come out the information of the employee I click? Please help, thank you!
models.py
from django.db import models
import datetime
class Employee(models.Model):
'''An employee's information.'''
full_name = models.CharField(max_length=100)
address = models.CharField(max_length=100)
city = models.CharField(max_length=100)
state = models.CharField(max_length=100)
zip = models.CharField(max_length=100)
hire_date = models.DateField(default=datetime.date.today)
def __str__(self):
'''Return a string representation of the model.'''
return self.full_name
return self.address
return self.city
return self.state
return self.zip
return self.hire_date
views.py
from django.shortcuts import render
from .models import Employee
def index(request):
'''The home page for employee_record.'''
return render(request, 'employee_records/base.html')
def employees(request):
'''Shows all employees'''
employees = Employee.objects.order_by('full_name')
context = {'employees': employees}
return render(request, 'employee_records/employees.html', context)
def employee(request, employee_id):
'''Show a single employee and all his records.'''
employee = Employee.objects.get(id=employee_id)
objects = Employee.objects.all
context = {'employee': employee, 'objects': objects}
return render(request, 'employee_records/employee.html', context)
urls.py
'''Defines URL patterns for employee_records.'''
from django.urls import path
from . import views
app_name = 'employee_records'
urlpatterns = [
# Home page
path('', views.employees, name='employees'),
# Detail page for a single employee
path('employees/<int:employee_id>/', views.employee, name='employee'),
]
base.html
<p>
Employee-Record
</p>
{% block content %}{% endblock content %}
employees.py
{% extends 'employee_records/base.html' %}
{% block content %}
<ul>
{% for employee in employees %}
<li>
{{ employee }}
</li>
{% empty %}
{% endfor %}
</ul>
{% endblock content %}
employee.html
{% extends 'employee_records/employees.html' %}
{% block content %}
<p>{{ employee }}
{% for object in objects %}
<li>{{ object.full_name }}</li>
<li>{{ object.address }}</li>
<li>{{ object.city }}</li>
<li>{{ object.state }}</li>
<li>{{ object.zip }}</li>
<li>{{ object.hire_date }}</li>
{% empty %}
<li>There are no records for this employee yet.</li>
{% endfor %}
</p>
{% endblock content %}
models.py
# you should have only one return statement
class Employee(models.Model):
'''An employee's information.'''
full_name = models.CharField(max_length=100)
address = models.CharField(max_length=100)
city = models.CharField(max_length=100)
state = models.CharField(max_length=100)
zip = models.CharField(max_length=100)
hire_date = models.DateField(default=datetime.date.today)
def __str__(self):
'''Return a string representation of the model.'''
return self.full_name
views.py
# you have to change employee function
def employee(request, employee_id):
'''Show a single employee'''
try:
employee = Employee.objects.get(id=employee_id)
except Employee.DoesNotExist:
employee = None
context = {'employee': employee}
return render(request, 'employee_records/employee.html', context)
employee.html
{% extends 'employee_records/employees.html' %}
{% block content %}
{% if employee %}
<ul>
<li>{{ employee.full_name}}</li>
<li>{{ employee.address}}</li>
<li>{{ employee.city}}</li>
<li>{{ employee.state}}</li>
<li>{{ employee.zip}}</li>
<li>{{ employee.hire_date}}</li>
</ul>
{% endif %}
{% endblock content %}
other way to achieve it is with a built in get_object_or_404 method
individual view
from django.shortcuts import get_object_or_404
def employee(request, employee_id):
employee = get_object_or_404(Employee, employee_id=employee_id)
context = {'employee': employee}
return render(request, 'employee_records/employee.html', context)
I'm new to Python and the Flask framework and I can't figure out how to pass data to a form. I keep getting AttributeError: 'NoneType' object has no attribute 'id' when I submit the form.
I'm able to pass the object (invoice1) from the view to the template and display the object properties. (invoice.id) but I can't pass the object to the Form.
#app.route('/invoice/add', methods=['GET', 'POST'])
#login_required
def addItem():
invoice2 = request.args.get('invoice_id', type=int)
invoice1 = Invoice.query.filter_by(id=invoice2).first()
form = ItemForm(obj=invoice1)
if form.validate_on_submit():
newItem = Item(
invoice = invoice1,
quantity = form.quantity.data,
category = form.category.data,
brand = form.brand.data,
model = form.model.data,
serialNumber = form.serialNumber.data,
condition = form.condition.data,
notes = form.notes.data,
cost = form.cost.data
)
db.session.add(newItem)
db.session.commit()
return redirect(url_for('invoice'))
return render_template('invoice/addItems.html', form=form, invoice1=invoice1)
form.py
from flask_wtf import Form
from wtforms import validators, IntegerField, StringField, TextAreaField, HiddenField
from wtforms.ext.sqlalchemy.fields import QuerySelectField
from item.models import Category, Brand
class BrandForm(Form):
name = StringField('Enter Brand', [validators.Required(), validators.Length(max=80)])
class CategoryForm(Form):
name = StringField('Enter Category', [validators.Required(), validators.Length(max=80)])
def categories():
return Category.query.order_by(Category.name).all()
def brands():
return Brand.query.order_by(Brand.name).all()
class ItemForm(Form):
#invoice = IntegerField('Invoice Number')
quantity = IntegerField('Quantity', [validators.Required()])
category = QuerySelectField('Category', [validators.Required()], query_factory=categories, allow_blank=True, blank_text=u'-- please choose --')
brand = QuerySelectField('Brand', [validators.Required()], query_factory=brands, allow_blank=True, blank_text=u'-- please choose --')
model = StringField('Model')
serialNumber = StringField('Serial Number')
condition = StringField('Condition')
notes = TextAreaField('Notes')
cost = IntegerField('Cost', default=0)
addItems.html
{% extends "base.html" %}
{% block title %}Invoice{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-offset-3 col-md-6">
<h3>Add Items to Invoice</h3>
{{ invoice1.id }}
{% from "_formhelpers.html" import render_field %}
<form method="POST" action="{{ url_for('addItem') }}" role="form">
{{ form.hidden_tag() }}
{{ render_field(form.quantity, class ='form-control') }}
{{ render_field(form.category, class ='form-control') }}
{{ render_field(form.brand, class ='form-control') }}
{{ render_field(form.model, class ='form-control') }}
{{ render_field(form.serialNumber, class ='form-control') }}
{{ render_field(form.condition, class ='form-control') }}
{{ render_field(form.notes, class ='form-control') }}
{{ render_field(form.cost, class ='form-control') }}
<button type="submit" class="btn btn-default">Add Item</button>
</form>
</div>
</div>
{% endblock %}
I can successfully save records to the DB via the shell.
Any help would be greatly appreciated.
Thank you!
Izzy
I'm not sure whats going wrong here...I get this error:
InterfaceError: (InterfaceError) Error binding parameter 0 - probably unsupported type. u'SELECT contact.id AS contact_id, contact.surname AS contact_surname, contact.firstname AS contact_firstname, contact.email AS contact_email, contact.mobile AS contact_mobile, contact.work_location AS contact_work_location \nFROM contact \nWHERE contact.id = ?' ([1],)
My method:
#app.route('/contacts/<int:contact_id>', methods=['GET'])
def contact_detail(contact_id):
if request.method == 'GET':
db.session.query(Contact).filter_by(id=[contact_id]).all()
return render_template('modcontact.html', title = 'Contact Detail')
My models:
class Contact(db.Model):
id = db.Column(db.Integer, primary_key = True)
surname = db.Column(db.String(100))
firstname = db.Column(db.String(100))
email = db.Column(db.String(100))
mobile = db.Column(db.String(20))
work_location = db.Column(db.String(100))
#user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
def __repr__(self):
return '<Contact %r>' % (self.surname)
template:
{% extends "base.html" %}
{% block content %}
<h1>List of contacts</h1>
<ul class=contacts>
{% for contacts in contacts %}
<li><h3>
<a href="{{ url_for('contact_detail',contact_id=contacts.id)}}">
{{ contacts.surname }}, {{ contacts.firstname }}
</a>
</h3></li>
{% else %}
<li><em>No contacts available</em></li>
{% endfor %}
</ul>
Add a new contact
{% endblock %}
You pass a list in your query filter. So the parameter in the query is a list therefore the 'Error binding parameter 0'.
Try this instead: db.session.query(Contact).filter_by(id=contact_id).all()