Error in One to Many SQLAlchemy database with Flask - python

I am currently trying to insert items into my database. I am using SQLlite and SQLAlchemy with Flask but there seems to be an issue. Whenever I try to insert items manually from the cmd, I receive an error.
This session's transaction has been rolled back due to a previous
exception during flush.
I have implemented an one to many relationship in my database but something seems to keep messing up. Here is my Python code:
from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
from flask_bootstrap import Bootstrap
main = Flask(__name__)
db = SQLAlchemy(main)
main.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://YYYYYYY:XXXXXXX#localhost/address'
main.config['SECRET_KEY'] = 'something-secret'
Bootstrap(main)
class Organisation(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(80), unique=True)
email = db.Column(db.String(40), unique=True)
number = db.Column(db.String(40), unique=True)
employees = db.relationship('Person', backref='employer', lazy='dynamic')
def __init__(self, title, email, number):
self.title = title
self.email = email
self.number = number
class Person(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(60), unique=False)
email = db.Column(db.String(40), unique=True)
mobile = db.Column(db.String(40), unique=True)
employer_id = db.Column(db.Integer, db.ForeignKey('organisation.id'))
def __init__(self, name, email, mobile, employer_id):
self.name = name
self.email = email
self.mobile = mobile
self.employer_id = employer_id
#main.route('/', methods=['GET'])
def index():
result = Person.query.all()
org_result = Organisation.query.all()
return render_template("index.html", result=result, org_result=org_result)
#main.route('/additems', methods=['GET'])
def additems():
return render_template('add.html')
#main.route('/add', methods=['GET', 'POST'])
def add():
person = Person(request.form['name'], request.form['email'], request.form['mobile'])
db.session.add(person)
db.session.commit()
if __name__ == "__main__":
main.run(debug=True)
If I have to honest, I think that my issue is somewhere in the init functions. I have tried changing them in several ways:
1.Adding employees as self.employees = employees and trying directly to input an Organisation as:
organisation_one=Organisation(title="XX",email="xx#mail.com",number="3838",employees=person_one) but it fired back an error even before I could submit person_one
2.I have tried referencing the employer_id in the Person __init__ file and when I try to add the organisation id, I recive an error "can't adapt type".
What am I doing wrong with the one to many database model? Can someone help me out?

Your database models require a __tablename__ attribute like this: This tells it what the actual table name is in the database. Otherwise SQLAlchemy doesn't know how to write the SQL for you.
class Organisation(db.Model):
__tablename__ = 'organisation'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(80), unique=True)
email = db.Column(db.String(40), unique=True)
number = db.Column(db.String(40), unique=True)
employees = db.relationship('Person', backref='employer', lazy='dynamic')
def __init__(self, title, email, number):
self.title = title
self.email = email
self.number = number
You must also reference this table name in the backref for your Person model:
db.ForeignKey('organisation.id')) # assuming "organisation" is the table name
Also, your /add route is incomplete and will result in an error:
#main.route('/add', methods=['GET', 'POST'])
def add():
person = Person(request.form['name'], request.form['email'], request.form['mobile'])
db.session.add(person)
db.session.commit()
# e.g. add some instruction here on what to do...
flash('Person %s <%s>added!' % (request.form['name'], request.form['email']))
return redirect(url_for('main.additems'))

Related

Problem Using QuerySelectMultipleField in Flask-Admin Edit Form

I'm newly come to Flask-Admin, and I'm trying to customize a model edit form. My User model has a roles attribute which is a relationship through the role_users table to Role.name, with a foreign key constraint.
Everything works so far, except that the Flask-Admin default User edit form renders roles as a string field. I'd like to render it as a QuerySelectMultipleField. My models are:
from flask import current_app
from flask_login import UserMixin
from werkzeug.security import check_password_hash, generate_password_hash
from app import db, login_manager
roles = db.Table(
'role_users',
db.Column('user_id', db.String(64), db.ForeignKey('users.username')),
db.Column('role_id', db.String(80), db.ForeignKey('roles.name')),
db.PrimaryKeyConstraint('user_id', 'role_id')
)
class User(UserMixin, db.Model):
__tablename__ = 'users'
username = db.Column(db.String(64), primary_key=True)
password_hash = db.Column(db.String(128))
firstname = db.Column(db.String(64))
lastname = db.Column(db.String(64))
roles = db.relationship('Role', secondary=roles, backref=db.backref('users', lazy='dynamic'))
def __init__(self, username='', password=''):
default = Role.query.filter_by(name='View_contact').one()
self.roles.append(default)
self.username = username
self.password = password
def __repr__(self):
return '<User {}>'.format(self.username)
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
#property
def password(self):
raise AttributeError('Password is not a readable attribute.')
#password.setter
def password(self, password):
self.set_password(password)
# Required for user_loader with non-integer User PK not named 'id'
def get_id(self):
return self.username
def grant_role(self, role_name):
if not self.has_role(role_name):
role = Role.query.filter_by(name=role_name).first()
self.roles.append(role)
db.session.commit()
def revoke_role(self, role_name):
if self.has_role(role_name):
role = Role.query.filter_by(name=role_name).first()
self.roles.remove(role)
def has_role(self, role_name):
for role in self.roles:
if role.name == role_name:
return True
return False
#login_manager.user_loader
def load_user(username):
return User.query.get(str(username))
class Role(db.Model):
__tablename__ = 'roles'
role_list = [
'View_contact',
'Add_contact',
'Edit_contact',
'Delete_contact',
'View_user',
'Add_user',
'Edit_user',
'Delete_user',
'Admin',
]
name = db.Column(db.String(80), primary_key=True)
description = db.Column(db.String(255))
def __init__(self, name):
self.name = name
def __repr__(self):
return '<Role {}>'.format(self.name)
#classmethod
def load_roles(cls):
for role_name in cls.role_list:
role = Role.query.filter_by(name=role_name).first()
if role is None:
role = Role(name=role_name)
db.session.add(role)
db.session.commit()
My custom ModelView is
from flask_admin.contrib.sqla import ModelView
from flask_admin.contrib.sqla.fields import QuerySelectMultipleField, QuerySelectField
from flask_admin.form import Select2TagsWidget, Select2Widget
from wtforms import PasswordField
from app import db
from app.models import Role, User
class UserView(ModelView):
column_display_pk = True
form_extra_fields = {
'password': PasswordField('Password'),
'role_sel': QuerySelectMultipleField(
label='Roles',
query_factory=lambda: Role.query.all,
widget=Select2TagsWidget()
)
}
form_columns = (
'username',
'password',
'firstname',
'lastname',
'role_sel',
)
def on_model_change(self, form, User, is_created):
if form.password.data != '':
User.set_password(form.password.data)
but when I try to access admin/user/new/ or admin/user/edit Flask throws AttributeError: 'QuerySelectMultipleField' object has no attribute '_value'.
If I try changing from QuerySelectMultipleField & Select2TagsWidget to QuerySelectField & Select2Widget Flask throws TypeError: 'method' object is not iterable.
Can someone tell me what I'm doing wrong? Is _value() a method I need to implement? I'm using Flask-Admin 1.5.8.
First of all, i think you should use another naming system for your tables:
# change this to roles_users
roles = db.Table(
'role_users',
db.Column('user_id', db.String(64), db.ForeignKey('users.username')),
db.Column('role_id', db.String(80), db.ForeignKey('roles.name')),
db.PrimaryKeyConstraint('user_id', 'role_id')
)
Now, moving to a possible solution:
Even though you think you dont need an id attribute (or column) on your User and Rolemodels, it is kinda default to use id as the primary key for models. Many extensions and frameworks will work "as is" if you just follow the patterns.
I think that what is going wrong with your implementation, is that FlaskAdmin's ModelView is having troubles when defining the Roles model primary key.
Try the following code:
class Role(db.Model):
...
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), unique=True)
...
roles = db.Table(
'role_users',
db.Column('user_id', db.String(64), db.ForeignKey('users.username')),
db.Column('role_id', db.String(80), db.ForeignKey('roles.id')),
db.PrimaryKeyConstraint('user_id', 'role_id')
)
and remove the role_sel from your form_args, and form_columns in the UserView.
FlaskAdmin should guess that the User.roles is a many-to-many field and render it as a multi select field.
your error is where your secondary needs to be pointed to the
role_users table where the relationship is kept track
lass User(UserMixin, db.Model):
__tablename__ = 'users'
username = db.Column(db.String(64), primary_key=True)
password_hash = db.Column(db.String(128))
firstname = db.Column(db.String(64))
lastname = db.Column(db.String(64))
roles = db.relationship('Role', secondary=role_users)

How to determine if a user is a member of an admin group in Flask

I'm new to using Flask, so apologies for what might be a basic question.
I'm working on a new Flask application that has multiple tables in a database. There is a table for storing the users information (name, password, enabled). Another table holds the group names and if it is an admin group (type is boolean). A third table relates the user to the group(s) they are a member of, since they could be a member of more than one. Here is the relevant code from models.py
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(10), index=True, unique=True)
password_hash = db.Column(db.String(128))
active_user = db.Column(db.Boolean)
group_ids = db.relationship('GroupMembers', backref='user', lazy='dynamic' )
def __repr__(self):
return 'User {} | Active {}'.format(self.username, self.active_user)
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
def check_admin(self):
group_ids = self.group_ids.all()
for row in group_ids:
group = Group.query.get(int(row.gid))
if group.admin_rights:
return True
return False
class Group(db.Model):
gid = db.Column(db.Integer, primary_key=True)
groupname = db.Column(db.String(60), index=True, unique=True)
admin_rights = db.Column(db.Boolean)
group_members = db.relationship('GroupMembers', backref='members', lazy='dynamic')
def __repr__(self):
return 'ID: {} | Name: {} | Admin: {}'.format(self.gid, self.groupname, self.admin_rights)
def is_admin_group(self, gid):
_group = self.query.get(int(gid))
return _group.admin_rights
class GroupMembers(db.Model):
__table_args__ = ( db.UniqueConstraint('gid', 'uid'), )
id = db.Column(db.Integer, prmary_key=True)
gid = db.Column(db.Integer, primary_key=True, db.ForeignKey('group.gid'))
uid = db.Column(db.Integer, primary_key=True, db.ForeignKey('user.id'))
def __repr__(self):
return "{}.{}".format(self.gid, self.uid)
I'm trying to find the best way to determine if a user is a member of a admin group (group.admin_rights == True). The 'check_admin' function in 'User' works, but I'm unsure of how to call this from within the html templates. Could this be called using something like
{% if current_user.check_admin() %}
assuming that your check_admin() is working fine, you can pass the additional check_admin flag like this
return render_template('sample_page.html', admin=check_admin())
and in the Jinja template, you can simply check for an admin user with:
{% if admin %}

Python Flask: How to convert a query object to string?

This issue has confused me for a long time and I searched for a few days but still cannot get it resolved, including this, this, and this.
Below code returns a query object and it shows correctly in the select field.
But when submitting to database, the errors occurred.
# Query the user with Role.id == 4 as reviewer
def reviewer_choices():
return User.query.join(User.roles).filter(Role.id == 4)
# Build a select field
class ProjectView(sqla.ModelView):
form_extra_fields = {
'reviewer': sqla.fields.QuerySelectField(
query_factory=reviewer_choices,
)}
I tried to define __repr__ and __str__ in order to convert it to string but in vain, is there any other way to convert the query object to string? Thanks in advance.
1. __repr__:
The error returns:
sqlalchemy.exc.InterfaceError
InterfaceError:
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.String(255))
# ...
# ...
def __repr__(self):
return self.first_name
2. __str__:
The error returns:
sqlalchemy.exc.InterfaceError InterfaceError: (raised as a result of
Query-invoked autoflush; consider using a session.no_autoflush block
if this flush is occurring prematurely) (sqlite3.InterfaceError) Error
binding parameter 8 - probably unsupported type. [SQL: u'INSERT INTO
project
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.String(255))
# ...
# ...
def __str__(self):
return self.first_name
I currently used:
In the Project class
class Project(db.Model):
# ...
reviewer = db.Column(db.Unicode(128))
# ...
In the Project table
CREATE TABLE `project` (
# ...
`reviewer1` TEXT,
# ...
Assuming that having reviewer as string field in your Project model is intentional (as opposed to being a relationship).
A QuerySelectField data property stores an ORM instance, which in your case is an instance of a User model, whilst your reviewer field is a string, hence the error message.
You can create an inherited QuerySelectField class and override its populate_obj method to convert the selected User instance to a string of your choice, e.g.
class ProjectQuerySelectField(QuerySelectField):
def populate_obj(self, obj, name):
# obj is the current model being edited/created
# name is the field name - 'reviewer' in this instance
# self.data is the user instance selected in the form
setattr(obj, name, str(self.data))
Note the use of the str function to get the string representation of the selected user instance.
See self-contained sample app below. Navigate to http://127.0.0.1:5000/admin/project/ to see how a selected user gets converted to a string.
from flask import Flask
from flask_admin.contrib.sqla import ModelView
from flask_admin.contrib.sqla.fields import QuerySelectField
from flask_security import Security, SQLAlchemyUserDatastore, RoleMixin, UserMixin
from flask_sqlalchemy import SQLAlchemy
from flask_admin import Admin
# Create application
app = Flask(__name__)
# Create dummy secrey key so we can use sessions
app.config['SECRET_KEY'] = '123456790'
# Create in-memory database
app.config['DATABASE_FILE'] = 'sample_db.sqlite'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + app.config['DATABASE_FILE']
app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)
# Flask views
#app.route('/')
def index():
return 'Click me to get to Admin!'
# Define models
roles_users = db.Table('roles_users',
db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
db.Column('role_id', db.Integer(), db.ForeignKey('role.id')))
class Role(db.Model, RoleMixin):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(255), unique=True)
description = db.Column(db.String(255))
def __str__(self):
return unicode(self).encode('utf-8')
def __unicode__(self):
return self.name
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.String(255))
last_name = db.Column(db.String(255))
email = db.Column(db.String(254), unique=True)
roles = db.relationship('Role', secondary=roles_users, backref=db.backref('users', lazy='dynamic'))
def __str__(self):
return unicode(self).encode('utf-8')
def __unicode__(self):
return ', '.join(filter(None, [self.first_name, self.last_name, self.email]))
class Project(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255))
reviewer = db.Column(db.Unicode(128))
def __str__(self):
return unicode(self).encode('utf-8')
def __unicode__(self):
return self.name
# Setup Flask-Security
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore)
class UserView(ModelView):
column_list = ['first_name', 'last_name', 'email', 'roles']
form_columns = ['first_name', 'last_name', 'email', 'roles']
class RoleView(ModelView):
form_columns = ['name', 'description']
def reviewer_choices():
# return User.query.join(User.roles).filter(Role.id == 4)
return User.query.join(User.roles).filter(Role.name == u'Reviewer')
class ProjectQuerySelectField(QuerySelectField):
def populate_obj(self, obj, name):
setattr(obj, name, str(self.data))
class ProjectView(ModelView):
form_extra_fields = {
'reviewer': ProjectQuerySelectField(
query_factory=reviewer_choices,
)}
admin = Admin(app, template_mode="bootstrap3")
admin.add_view(UserView(User, db.session))
admin.add_view(RoleView(Role, db.session))
admin.add_view(ProjectView(Project, db.session))
def build_sample_db():
db.drop_all()
db.create_all()
# Reviewer will have id : 4 and will have index 3 in _roles list
_roles = []
for _name in ['Admin', 'Editor', 'Reader', 'Reviewer']:
_role = Role(name=_name)
_roles.append(_role)
db.session.add_all(_roles)
# get the "Reviewer" Role
_reviewer_role = _roles[3]
# Give Paul and Serge "Reviewer" role
_user_1 = User(first_name="Paul", last_name="Cunningham", email="paul#example.com", roles=[_reviewer_role])
_user_2 = User(first_name="Luke", last_name="Brown", email="luke#example.com")
_user_3 = User(first_name="Serge", last_name="Koval", email="serge#example.com", roles=[_reviewer_role])
db.session.add_all([_user_1, _user_2, _user_3])
db.session.commit()
if __name__ == '__main__':
build_sample_db()
app.run(port=5000, debug=True)

How to query multiple items using Flask SQLAlchemy

After importing the modules:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
and declaring app and db objects:
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'
db = SQLAlchemy(app)
I go ahead and create two tables: User and Email:
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
addresses = db.relationship('Email', backref='person', lazy='dynamic')
class Email(db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(50))
person_id = db.Column(db.Integer, db.ForeignKey('user.id'))
With db.relationship in place I can now link some multiple emails to the same user. First I create two email addresses:
first_email = Email(email='first#email.com')
second_email = Email(email='second#email.com')
Then I am passing these two emails to User class at the time it is being created:
user = User(name='User Name', addresses = [first_email, second_email])
To see which user is linked to which email I can simply use:
print first_email.person
print user.addresses.all()
Now I want to add another third email to the same user. How do I append a new email to the list of the emails that have been already linked to the user?
new_email = Email(email='new_email#example.com')
user.addresses.append(new_email)
db.session.commit()
This will append the email address to the relationship.
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///inquestion.db'
db = SQLAlchemy(app)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
addresses = db.relationship('Email', backref='person', lazy='dynamic')
def add_email(self, new_email):
linked_emails = [email.email for email in self.addresses.all()]
if not new_email in linked_emails:
linked_emails.append(new_email)
self.addresses = [Email.find_or_create(email) for email in linked_emails]
class Email(db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(50))
person_id = db.Column(db.Integer, db.ForeignKey('user.id'))
#staticmethod
def find_or_create(email):
try:
return Email.query.filter_by(email=email).one()
except:
new_email = Email(email=email)
db.session.add(new_email)
db.session.commit()
return new_email
first_email = Email(email='first#email.com')
second_email = Email(email='second#email.com')
user = User(name='User Name', addresses = [first_email, second_email])
db.drop_all()
db.create_all()
db.session.add(first_email)
db.session.add(second_email)
db.session.add(user)
db.session.commit()
# some extra queries
user.add_email('third#email.com')
print user.addresses.all()
print Email.find_or_create('fourth#email.com')
print Email.query.filter_by(email='fourth#email.com').one().email
print first_email.query.filter_by(email='second#email.com').one()

sqlalchemy.exc.InterfaceError: <unprintable InterfaceError object>

I'm trying out Flask but I'm having the error sqlalchemy.exc.InterfaceError: <unprintable InterfaceError object> while submitting a wtforms. The model class is:
class Post(db.Model):
__tablename__ = 'blog_posts'
id = db.Column(db.Integer, unique=True, primary_key=True)
title = db.Column(db.String(50), unique=False)
content = db.Column(db.Text, unique=False)
user_id = db.Column(db.String, db.ForeignKey('users.username'))
#staticmethod
def post_new_entry(title, content, user_id):
""" Post new entry to database """
new_post = Post(title=title, content=content, user_id=user_id)
db.session.add(new_post)
db.session.commit()
return new_post
def __repr__(self):
return 'PostID {}: {} by {}'.format(self.id, self.title, self.user_id)
For my Form, I have the following:
class PostForm(Form):
title = StringField('Title', validators=[DataRequired(), Length(10, 65)])
post_content = TextAreaField('Content', validators=[DataRequired(), Length(50, 500)])
submit = SubmitField('Publish Post')
The route is:
#main.route('/new_post/', methods=['GET', 'POST'])
#login_required
def add_post():
form = PostForm()
if form.validate_on_submit():
Post.post_new_entry(title=form.title.data,
content=form.post_content.data,
user_id=current_user)
flash("Amazing stuff! Thanks for your submission.")
return redirect(url_for('main.index'))
return render_template('single.html', form=form)
On my html, I'm importing the wtf.html page of the flask-bootstrap:
{{ wtf.quick_form(form) }}
The form shows right but I get the above error on form submission. Any tip or idea on how to proceed would be helpful.
Under def add_post() you write user_id=current_user, but that's not right.
Since you defined for class Post:
user_id = db.Column(db.String, db.ForeignKey('users.username'))
in def add_post() you should use user_id=current_user.username.
In your table class definition you need to add one more line to complete the foreign key relationship.
class Post(db.Model):
__tablename__ = 'blog_posts'
id = db.Column(db.Integer, unique=True, primary_key=True)
title = db.Column(db.String(50), unique=False)
content = db.Column(db.Text, unique=False)
user_id = db.Column(db.String, db.ForeignKey('users.username'))
# Setup the relationship to the User table
users = db.relationship(User)
I was having the same error message in an app which was working one day then not the next. Drove me nuts, the solution was that I had removed a relationship() somewhere.
I have received a similar message when writing data from my application to a database. This is due to the fact that the data that is written from the application needs to have the same format as a defined in the database a db.Column(db.String()) data type cannot have a list as input for example, or any other form.data. You need to use ``str()``` in these cases to prevent this error.
I think your problem came from this area:
Post.post_new_entry(title=form.title.data, content=form.post_content.data, user_id=current_user)
Try to be specific and do it this way:
Post.post_new_entry(title=form.title.data, content=form.post_content.data, user_id=current_user.id)

Categories