I have two main table which is role and users, and on users I making 3 associate to table operator, teacher and student.
So far, I making it like this:
class Role(db.Model):
__tablename__ = 'roles'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
permissions = db.Column(db.Integer)
users = db.relationship('User',
backref='role', lazy='dynamic')
class User(UserMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), index=True)
email = db.Column(db.String(64), unique=True, index=True)
password_hash = db.Column(db.String(128))
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
__mapper_args__ = {
'polymorphic_identity': 'users',
'with_polymorphic': '*',
}
class Operator(User):
__tablename__ = 'operator'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
__mapper_args__ = {
'polymorphic_identity': 'operator',
'with_polymorphic': '*'
}
class Teacher(User):
__tablename__ = 'teacher'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
phone_number = db.Column(db.Integer)
other_teacher_data = db.Column(db.String)
__mapper_args__ = {
'polymorphic_identity': 'teacher',
'with_polymorphic': '*'
}
class Student(User):
__tablename__ = 'student'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
other_student_data = db.Column(db.String)
__mapper_args__ = {
'polymorphic_identity': 'student',
'with_polymorphic': '*'
}
But I got this error message:
Attempting to flush an item of type as a member of collection
"Role.users". Expected an object of type or a polymorphic subclass of
this type. If is a subclass of , configure mapper "Mapper|User|users"
to load this subtype polymorphically, or set enable_typechecks=False
to allow any subtype to be accepted for flush.
I have tried to set enable_typechecks=False on users field on Role table, and then I got this error message:
psycopg2.errors.UniqueViolation) duplicate key value violates unique
constraint "ix_users_email" DETAIL: Key
(email)=(zidanecr7kaka2#gmail.com) already exists. [SQL: 'INSERT INTO
users (confirmed, first_name, last_name, email, password_hash,
role_id, date_of_birth, address, created_at, updated_at) VALUES
(%(confirmed)s, %(first_name)s, %(last_name)s, %(email)s,
%(password_hash)s, %(role_id)s, %(date_of_birth)s, %(address)s,
CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) RETURNING users.id']
[parameters: {'confirmed': False, 'first_name': 'Tri', 'last_name':
'Nanda', 'email': 'zidanecr7kaka2#gmail.com', 'password_hash':
'pbkdf2:sha1:1000$PtpuVYh4$b5bbb03939cf6ca9013308b62276889d35a8cc1b',
'role_id': 5, 'date_of_birth': None, 'address': None}]
I got that message even when I try with different data, but it still say duplicate key value.
Please, what's wrong with my code..?, or any example with similliar case..?
Spot the difference :)
from app import db
from flask_login import UserMixin
class Role(db.Model):
__tablename__ = 'roles'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
permissions = db.Column(db.Integer)
users = db.relationship('User',
backref='role', lazy='dynamic')
class User(UserMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
type = db.Column(db.String(50))
name = db.Column(db.String(64), index=True)
email = db.Column(db.String(64), unique=True, index=True)
password_hash = db.Column(db.String(128))
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
__mapper_args__ = {
'polymorphic_identity': 'users',
'with_polymorphic': '*',
"polymorphic_on": type
}
class Operator(User):
__tablename__ = 'operator'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
__mapper_args__ = {
'polymorphic_identity': 'operator',
'with_polymorphic': '*'
}
class Teacher(User):
__tablename__ = 'teacher'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
phone_number = db.Column(db.Integer)
other_teacher_data = db.Column(db.String)
__mapper_args__ = {
'polymorphic_identity': 'teacher',
'with_polymorphic': '*'
}
class Student(User):
__tablename__ = 'student'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
other_student_data = db.Column(db.String)
__mapper_args__ = {
'polymorphic_identity': 'student',
'with_polymorphic': '*'
}
It's not a good error message, but you have missed the type field on the base class. It needs somewhere to store the type of the children, otherwise if you ran a query on the base class and expected polymorphism, it would have to search all the other child tables to match up the ids. See:
https://docs.sqlalchemy.org/en/13/orm/inheritance.html
Above, an additional column type is established to act as the discriminator, configured as such using the mapper.polymorphic_on parameter. This column will store a value which indicates the type of object represented within the row. The column may be of any datatype, though string and integer are the most common.
While a polymorphic discriminator expression is not strictly necessary, it is required if polymorphic loading is desired. Establishing a simple column on the base table is the easiest way to achieve this, however very sophisticated inheritance mappings may even configure a SQL expression such as a CASE statement as the polymorphic discriminator.
They also recommend in the tutorial that you don't use a separate id column in the children and make the child id columns both primary and foreign keys back to the base.
You may want to remove the "with_polymorphic": "*" as it loads all the subfields upfront (inefficient). You may want this in certain cases when you are doing filters but you can turn it on as you are doing the queries:
https://docs.sqlalchemy.org/en/13/orm/inheritance_loading.html
Related
Given this polymorphic model
class OrganizationBase(Base):
__tablename__ = "x_organization_base"
__mapper_args__ = {
"polymorphic_identity": "base",
"polymorphic_on": "model_type",
}
model_type = db.Column(db.String(), nullable=False)
id = Column(Integer(), primary_key=True)
cont'd
class UmbrellaOrganization(OrganizationBase):
__tablename__ = "x_umbrella_organization"
__mapper_args__ = {"polymorphic_identity": "umbrella"}
id = db.Column(Integer, db.ForeignKey(OrganizationBase.id), primary_key=True)
umbrella_accounting_id = db.Column(db.String(255), nullable=False, unique=True)
properties = db.relationship(
"UmbrellaOrganizationProperty",
lazy="joined",
backref=backref("umbrella_organization", uselist=False),
)
class Organization(OrganizationBase):
__tablename__ = "x_organization"
__mapper_args__ = {"polymorphic_identity": "organization"}
id = db.Column(Integer, db.ForeignKey(OrganizationBase.id), primary_key=True)
umbrella_accounting_id = db.Column(
db.String(255),
db.ForeignKey(UmbrellaOrganization.umbrella_accounting_id),
nullable=False,
index=True,
)
and this eagerly loaded relationship
class UmbrellaOrganizationProperty(Base):
__tablename__ = "x_umbrella_organization_property"
id = Column(Integer(), primary_key=True)
umbrella_organization_id = db.Column(
Integer, db.ForeignKey(UmbrellaOrganization.id), nullable=False, index=True
)
type = db.Column(db.String(), nullable=False)
this query will produce invalid SQL:
query = (
db.session.query(
Organization,
UmbrellaOrganization,
)
.join(
UmbrellaOrganization,
UmbrellaOrganization.umbrella_accounting_id == Organization.umbrella_accounting_id,
)
)
y = query.limit(5)
Specically, there main query will be duplicated with the same alias 'anon_1' occuring twice:
ProgrammingError: (psycopg2.errors.DuplicateAlias) table name "anon_1" specified more than once
This only happens with limit() applied.
It appears that the polymorphism mapper wants to join the (eagerly loaded) UmbrellaOrganziationProperty to both the UmbrellaOrganization and OrganizationBase, even though it does not belong there. Without changing the model, the only way I have found to prevent this is telling it to not load OrganizationProperty eagerly, by adding this query option:
.options(lazyload(UmbrellaOrganization.properties))
This is potentially problematic because client code may expect the properties in the results. What else can I do?
Consider the following many-to-many relationship:
class Hashes(db.Model):
id = db.Column(db.Integer, primary_key=True)
hash = db.Column(db.String, nullable=False, unique=True)
xref_hashes_users = db.Table("xref_hashes_users",
db.Column('hash', db.ForeignKey('hashes.id'), primary_key=True),
db.Column('user', db.ForeignKey('user.id'), primary_key=True))
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String)
hashes = db.relationship("Hashes", secondary="xref_hashes_users", backref="users")
Let's say I want to allow users to store some additional information about their hashes, perhaps a label. It makes sense to me that given this is a user-specific piece of information about a hash, it would go in the association table like:
xref_hashes_users = db.Table("xref_hashes_users",
db.Column('hash', db.ForeignKey('hashes.id'), primary_key=True),
db.Column('user', db.ForeignKey('user.id'), primary_key=True),
db.Column('label', db.String))
Given this schema, is it possible to use the ORM to add/remove/update labels? How would I do this?
It looks like the answer is to use an Association Object to store the extra data.
https://docs-sqlalchemy.readthedocs.io/ko/latest/orm/basic_relationships.html#association-object
So my example becomes:
class Hashes(db.Model):
id = db.Column(db.Integer, primary_key=True)
hash = db.Column(db.String, nullable=False, unique=True)
users = db.relationship("UserHashAssociation", back_populates="hashes")
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String)
hashes = db.relationship("UserHashAssociation", back_populates="users")
class UserHashAssociation(db.Model):
__tablename__ = "xref_hashes_users"
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), primary_key=True)
hash_id = db.Column(db.Integer, db.ForeignKey("hashes.id"), primary_key=True)
label = db.Column(db.String)
users = db.relationship("User", back_populates="hashes")
hashes = db.relationship("Hashes", back_populates="users")
Then I am able to update the label like:
hash = Hashes(hash=hash_string)
user_hash_association = UserHashAssociation(label="foo", user_id=user.id)
hash.users.append(user_hash_association)
db.session.add(hash)
db.session.commit()
I've got three tables: User, Role, Department
I want to query all the user with selected following filters:
Department.name , Role.name and User.degree
I can't find a solution to this problem, any suggestion would be great!
Here's my models simplified:
class User(UserMixin, db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
password_hash = db.Column(db.String(128))
degree = db.Column(db.String(20),default=None)
department_id = db.Column(db.Integer, db.ForeignKey('departments.id'))
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
class Department(db.Model):
__tablename__ = "departments"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(sqlalchemy.types.NVARCHAR(100), unique=True)
user = db.relationship('User', backref='department',
lazy='dynamic')
class Role(db.Model):
__tablename__ = 'roles'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(sqlalchemy.types.NVARCHAR(100), unique=True)
users = db.relationship('User', backref='role',
lazy='dynamic')
session.query(User).filter(
User.department.has(name='some_name)).filter(
User.role.has(name='some_role')).filter(
User.degree == 'some_degree')
It's a simple query with joins. You can modify "department" with your department filter and "role" with the same. You should modify the select part (session.query(User.id)) with the fields you want.
users = (session.query(User.id).join(Department, Department.user == User.id).join(Role, Role.user == User.id).filter(Department.name=="department").filter(Role.name=="role").group_by(User.id))
I would like to define a many-to-many-relation in flask. (The two models are User and Role. Lateron I want to use them for Flask-Security.) So I followed http://flask-sqlalchemy.pocoo.org/2.1/models/#many-to-many-relationships and wrote
class User(db.Model, UserMixin):
__tablename__ = 'user'
__table_args__ = { 'useexisting': True }
id = Column(Integer, primary_key=True)
roles = relationship("RolesUsers", backref=db.backref("user", lazy='dynamic'))
class Role(db.Model):
__tablename__ = 'role'
__table_args__ = { 'useexisting': True }
id = Column(Integer, primary_key=True)
name = Column(String(80), unique=True)
class RolesUsers(db.Model):
__tablename__ = 'roles_users'
__table_args__ = { 'useexisting': True }
user_id = Column(Integer, ForeignKey('user.id'), primary_key=True)
role_id= Column(Integer, ForeignKey('role.id'), primary_key=True)
But I get the error
Multiple classes found for path "RolesUsers" in the registry of this
declarative base. Please use a fully module-qualified path.
I found out that the line
roles = relationship("RolesUsers", backref=db.backref("ma_user", lazy='dynamic'))
seems to be responsible for that.
You might ask why I use
__table_args__ = { 'useexisting': True }
That is because else I get the error
Table 'user' is already defined for this MetaData instance. Specify
'extend_existing=True' to redefine options and columns on an existing
Table object.
Maybe those two errors are hanging together? To my eye it looks like the Class is somehow called multiple times. But I have no clue how that might come. So how can I repair this?
I think there is something wrong with the usage of a Model Class for the association table. In the end
roles_users = db.Table(
'ma_roles_users',
db.Column('user_id', db.Integer(), db.ForeignKey('ma_user.id')),
db.Column('role_id', db.Integer(), db.ForeignKey('ma_role.id'))
)
class User(db.Model, UserMixin):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True)
roles = db.relationship(
'Role',
secondary=roles_users,
backref=db.backref('user', lazy='dynamic')
class Role(db.Model, RoleMixin):
__tablename__ = "role"
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(80), unique=True)
worked.
I'm designing the database for my own Q&A website, which is somewhat similar to Stack Overflow:
A "question" can have several "answers", but an "answer" has only one "question".
Both "questions" and "answers" can have several "comments", but a "comment" has only one "question" or "answer".
I have no idea how to design such a database. Here is what I've tried:
class Question(db.Model):
__tablename__ = 'questions'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.Unicode(64), unique=True)
body = db.Column(db.UnicodeText)
author_id = db.Column(db.Integer, db.ForeignKey('users.id'))
answers = db.relationship('Answer', backref='question', lazy='dynamic')
class Answer(db.Model):
__tablename__ = 'answers'
id = db.Column(db.Integer, primary_key=True)
body = db.Column(db.UnicodeText)
author_id = db.Column(db.Integer, db.ForeignKey('users.id'))
question_id = db.Column(db.Integer, db.ForeignKey('questions.id'))
class Comment(db.Model):
__tablename__ = 'comments'
id = db.Column(db.Integer, primary_key=True)
# post_id = db.Column(db.Integer, db.ForeignKey('')) ???
So you've already managed the first point.
What you're looking for is a generic relationship. You can find it in sqlalchemy_utils package.
from sqlalchemy_utils import generic_relationship
class Comment(db.Model):
__tablename__ = 'comments'
id = db.Column(db.Integer, primary_key=True)
object_type = db.Column(db.Unicode(255))
object_id = db.Column(db.Integer)
object = generic_relationship(object_type, object_id)
Docs for generic relationship
So basically what it does, it stores the object_type as answer or question and object_id as object's primary key.
I suggest you extract a base class for Question and Answer, e.g. Post, and make Comment relate to Post, such that a Post can have multiple Comments.
SQLAlchemy ORM supports few strategies to implement inheritance in the database, and the right strategy to choose depends on the way you plan to query your entities. Here's the detailed documentation on how to properly configure it.
So you'd get something like this:
(disclaimer: I have not directly ran this code, but composed it from your example, and my own code. If it ain't working for you, let me know and I'll fix)
class Post(db.Model):
__tablename__ = 'posts'
id = db.Column(db.Integer, primary_key=True)
kind = Column(db.Unicode(64), nullable=False)
body = db.Column(db.UnicodeText)
author_id = db.Column(db.Integer, db.ForeignKey('users.id'))
comments = db.relationship('Comment', backref='post', lazy='dynamic')
__mapper_args__ = {
'polymorphic_identity': 'posts',
'polymorphic_on': kind
}
class Question(Post):
__tablename__ = 'questions'
title = db.Column(db.Unicode(64), unique=True)
answers = db.relationship('Answer', backref='question', lazy='dynamic')
__mapper_args__ = {
'polymorphic_identity': 'questions',
}
class Answer(Post):
__tablename__ = 'answers'
question_id = db.Column(db.Integer, db.ForeignKey('questions.id'))
__mapper_args__ = {
'polymorphic_identity': 'answers',
}
class Comment(db.Model):
__tablename__ = 'comments'
id = db.Column(db.Integer, primary_key=True)
post_id = db.Column(db.Integer, db.ForeignKey('posts.id'))