Flask - commit FieldList to database using SQLAlchemy? - python

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

WTForms SelectField returning None

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())

Form isn't validating despite having a csrf token

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'.

python django how to only show the information about an individual employee when I click on his name

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)

Flask-WTForms not passing data when submitted. AttributeError: 'NoneType' object has no attribute 'id'

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

Flask sqlalchemy InterfaceError

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()

Categories