Problem Using QuerySelectMultipleField in Flask-Admin Edit Form - python

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)

Related

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 %}

User.query.filter_by(id=user_id).first() and User.query.get(user_id) returns None in Flask Sql Alchemy

I have run into an error with querying the user according to the user_id passed along the parameter of my route. I have used the same method in many other functions and none of them throws any errors. I have users in my database with the corresponding ids. This query shouldn't detect any errors but it does and would really appreciate any help.
the target_user variable in the function gets assigned None and that gives me the error
"NoneType' object has no attribute 'id'"
This is the User Model
followers = db.Table('followers',
db.Column('follower_id', db.Integer, db.ForeignKey('user.id')),
db.Column('followed_id', db.Integer, db.ForeignKey('user.id'))
)
class User(UserMixin, db.Model):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
user_handle = db.Column(db.String(15), unique=True)
display_name = db.Column(db.String(30), nullable=False)
password = db.Column(db.String(20), nullable=False)
profile_picture_url = db.Column(db.String(100), nullable=True, default="./static/user_media/default_egg.png")
# profile_description = db.Column(db.String(140), nullable=True)
tweets = db.relationship('Tweet', backref="owner")
# relationships
liked = db.relationship('LikeTweet', foreign_keys="LikeTweet.liked_by",
backref="user", lazy="dynamic")
def like_tweet(self, tweet):
if not self.has_liked_tweet(tweet):
like = LikeTweet(liked_by=self.id, liked_tweet=tweet.id)
db.session.add(like)
def unlike_tweet(self, tweet):
if self.has_liked_tweet(tweet):
LikeTweet.query.filter_by(
liked_by=self.id,
liked_tweet=tweet.id).delete()
def has_liked_tweet(self, tweet):
return LikeTweet.query.filter(
LikeTweet.liked_by == self.id,
LikeTweet.liked_tweet == tweet.id).count() > 0
followed = db.relationship('User', secondary=followers,
primaryjoin=(followers.c.follower_id == id),
secondaryjoin=(followers.c.followed_id == id),
backref=db.backref('followers', lazy='dynamic'),
lazy='dynamic')
def follow(self, user):
if not self.is_following(user):
self.followed.append(user)
return self
def unfollow(self, user):
if self.is_following(user):
self.followed.remove(user)
return self
def is_following(self, user):
return self.followed.filter(followers.c.followed_id == user.id).count() > 0
This is the function that I am making the query from
#main.route('/follow/<user_id>')
#login_required
def follow_user(user_id):
referrer = request.headers.get("Referer")
target_user = User.query.filter_by(id=user_id).first()
print(target_user)
if current_user.is_following(target_user):
current_user.unfollow(target_user)
db.session.commit()
else:
current_user.follow(target_user)
db.session.commit()
return redirect(referrer)

Flask Many to Many relationship sqlalchemy.exc.InvalidRequestError: failed to locate a name

When trying to get a many to many relationship working I keep getting the following error:
sqlalchemy.exc.InvalidRequestError: One or more mappers failed to initialize - can't proceed with initialization of other mappers. Triggering mapper: 'Mapper|User|Users'. Original exception was: When initializing mapper Mapper|User|Users, expression 'Device' failed to locate a name ("name 'Device' is not defined"). If this is a class name, consider adding this relationship() to the class after both dependent classes have been defined.
I have looked over all the sqlalchemy documents and reviewed multiple links on many to many but no luck. I am sure its a naming or importing issue, but have not found a solution yet
I removed some of the code that I don't feel is related
Users.py
from random import SystemRandom
from backports.pbkdf2 import pbkdf2_hmac, compare_digest
from flask_login import UserMixin
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import relationship
from devices.models import Device
user_device = db.Table('UserDevice', db.Model.metadata,
db.Column('userID', db.Integer, db.ForeignKey('Users.userID')),
db.Column('deviceID', db.Integer, db.ForeignKey('Device.deviceID')))
class User(UserMixin, db.Model):
__tablename__ = 'Users'
__table_args__ = {'mysql_engine': 'InnoDB',
'extend_existing': True}
id = db.Column('userID', db.Integer, primary_key=True)
# Relationship to UserDevice association table
user_device = relationship('Device',
secondary=user_device,
backref=db.backref('users', lazy='dynamic'))
Device.py
class Device(db.Model):
__tablename__ = 'Device'
__table_args__ = {'mysql_engine': 'InnoDB',
'extend_existing': True}
id = db.Column('deviceID', db.Integer, primary_key=True)
date_created = db.Column('deviceDateCreated', db.DateTime, default=db.func.current_timestamp())
date_modified = db.Column('deviceDateModified', db.DateTime, default=db.func.current_timestamp(), onupdate=db.func.current_timestamp())
device_created_user = db.Column('deviceCreatedUser', db.String, default='App Server')
device_last_updated_user = db.Column('deviceLastUpdatedUser', db.String, default='App Server', onupdate=current_user)
#Serial Number
serial_number = db.Column('deviceSerialNumber', db.Integer, nullable=False, unique=True)
#Sampling Interval
sampling_interval = db.Column('deviceSamplingInterval', db.Integer, default=60, nullable=False)
# Relationship to Device Status Table
device_status_id = db.Column('deviceStatusID', db.Integer, db.ForeignKey('DeviceStatus.deviceStatusID'))
# New instance instantiation procedure
def __init__(self, serial_number):
self.serial_number = serial_number
self.device_status_id = 1
def __repr__(self):
return '<Device %r>' % self.serial_number
Image of Database Model:
Turns out I didn't provide enough information to solve this problem. The problem turned out to be using the db variable created by calling SQLAlchemy. I created a python file just for the database called database.py. The mistake I made was in User\models.py I called the following import from database import db and in Device\models.py I called from app import db. This caused the db.Model to not function properly and also wouldn't create the user tables when calling create_all(). Hope this helps someone in the future.
Database.py
from flask_influxdb import InfluxDB
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
influx_db = InfluxDB()
influx_db_client = None
def init_db():
# import all modules here that might define models so that
# they will be registered properly on the metadata. Otherwise
# you will have to import them first before calling init_db()
from users.models import User, UserStatus, UserDevice
from devices.models import Device, DeviceStatus
db.Model.metadata.drop_all(bind=db.engine)
db.Model.metadata.create_all(bind=db.engine)
Devices\models.py
from app import db
from flask_login import current_user
from sqlalchemy.orm import relationship
import enum
class DeviceStatusType(enum.Enum):
INACTIVE = "Inactive"
ACTIVE = "Active"
# Define a Device model
class Device(db.Model):
__tablename__ = 'Device'
__table_args__ = {'extend_existing': True}
id = db.Column('deviceID', db.Integer, primary_key=True)
date_created = db.Column('deviceDateCreated', db.DateTime, default=db.func.current_timestamp())
date_modified = db.Column('deviceDateModified', db.DateTime, default=db.func.current_timestamp(), onupdate=db.func.current_timestamp())
device_created_user = db.Column('deviceCreatedUser', db.String(128), default='App Server')
device_last_updated_user = db.Column('deviceLastUpdatedUser', db.String(128), default='App Server', onupdate=current_user)
#Serial Number
serial_number = db.Column('deviceSerialNumber', db.Integer, nullable=False, unique=True)
#Sampling Interval
sampling_interval = db.Column('deviceSamplingInterval', db.Integer, default=60, nullable=False)
# Relationship to Device Status Table
device_status_id = db.Column('deviceStatusID', db.Integer, db.ForeignKey('DeviceStatus.deviceStatusID'))
users = relationship("User", secondary="userDevice")
# New instance instantiation procedure
def __init__(self, serial_number):
self.serial_number = serial_number
self.device_status_id = 1
def __repr__(self):
return '<Device %r>' % self.serial_number
users\models.py
from random import SystemRandom
from backports.pbkdf2 import pbkdf2_hmac, compare_digest
from flask_login import UserMixin, current_user
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import relationship, backref
from devices.models import Device
import enum
# Import the database object (db) from the main application module
# We will define this inside /app/__init__.py in the next sections.
from app import db
class UserStatusType(enum.Enum):
INACTIVE = "Inactive"
ACTIVE = "Active"
# Define a User model
class User(UserMixin, db.Model):
__tablename__ = 'User'
__table_args__ = {'extend_existing': True}
id = db.Column('userID', db.Integer, primary_key=True)
date_created = db.Column('userDateCreated', db.DateTime, default=db.func.current_timestamp())
date_modified = db.Column('userDateModified', db.DateTime, default=db.func.current_timestamp(), onupdate=db.func.current_timestamp())
user_created_user = db.Column('userCreatedUser', db.String(128), default=current_user)
user_last_updated_user = db.Column('userLastUpdatedUser', db.String(128), default=current_user, onupdate=current_user)
# First Name
first_name = db.Column('userFirstName', db.String(128), nullable=False)
# Last Name
last_name = db.Column('userLastName', db.String(128), nullable=False)
# User Name
user_name = db.Column('userUserName', db.String(128), nullable=False, unique=True)
# Email
email = db.Column('userEmailAddress', db.String(128), nullable=False, unique=True)
# Password
_password = db.Column('userPassword', db.LargeBinary(128))
_salt = db.Column('userSalt', db.LargeBinary(128))
# Relationship to User Status table
user_status_id = db.Column('userStatusID', db.Integer, db.ForeignKey('UserStatus.userStatusID'))
# Relationship to UserDevice association table
devices = relationship("Device", secondary="userDevice")
#hybrid_property
def password(self):
return self._password
# In order to ensure that passwords are always stored
# hashed and salted in our database we use a descriptor
# here which will automatically hash our password
# when we provide it (i. e. user.password = "12345")
#password.setter
def password(self, value):
# When a user is first created, give them a salt
if self._salt is None:
self._salt = bytes(SystemRandom().getrandbits(8))
self._password = self._hash_password(value)
def is_valid_password(self, password):
"""Ensure that the provided password is valid.
We are using this instead of a ``sqlalchemy.types.TypeDecorator``
(which would let us write ``User.password == password`` and have the incoming
``password`` be automatically hashed in a SQLAlchemy query)
because ``compare_digest`` properly compares **all***
the characters of the hash even when they do not match in order to
avoid timing oracle side-channel attacks."""
new_hash = self._hash_password(password)
return compare_digest(new_hash, self._password)
def _hash_password(self, password):
pwd = password.encode("utf-8")
salt = bytes(self._salt)
buff = pbkdf2_hmac("sha512", pwd, salt, iterations=100000)
return bytes(buff)
# New instance instantiation procedure
def __init__(self, first_name, last_name, user_name, email, password):
self.first_name = first_name
self.last_name = last_name
self.user_name = user_name
self.email = email
self.password = password
self.user_status_id = 2
def __repr__(self):
return "<User #{:d}>".format(self.id)

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)

Error in One to Many SQLAlchemy database with Flask

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

Categories