Python Flask-SQLAlchemy inheritance - python

I'm trying to make an insert query but I'm getting this error:
sqlalchemy.orm.exc.FlushError: Attempting to flush an item of type
as a member of collection "Tag.item". Expected
an object of type or a polymorphic subclass of
this type. If is a subclass of , configure mapper "Mapper|Item|items" to load this
subtype polymorphically, or set enable_typechecks=False to allow any
subtype to be accepted for flush.
The insert query
project = Proyecto.query.filter_by(id_project=id_project).first()
creado_en = datetime.datetime.utcnow()
nuevo_hub = Hub(name,location,comments,creado_en)
#FAIL HERE, even if Hub extends of Item
nuevo_tag = Tag(project,nuevo_hub,TYPE_HUB,creado_en)
db.session.add(nuevo_tag)
db.session.add(nuevo_hub)
db.session.commit()
The rest of code
class Item(db.Model):
__tablename__ = "items"
id_item = db.Column(db.Integer, autoincrement=True, primary_key=True)
type = db.Column(db.Integer)
created_at = db.Column(db.DateTime)
updated_at = db.Column(db.DateTime)
__mapper_args__ = {
'polymorphic_identity': 'items',
'polymorphic_on':type,
'with_polymorphic':'*'
}
def __init__(self,creado_en=None):
self.created_at = creado_en
self.updated_at = creado_en
class Hub(Item):
__tablename__ = "hubs"
__mapper_args__ = {
'polymorphic_identity': 0,
'with_polymorphic':'*'
}
id_hub = db.Column(db.Integer, db.ForeignKey('items.id_item'), primary_key=True)
# id_hub = db.Column(db.Integer, autoincrement=True, primary_key=True)
name = db.Column(db.String(50), nullable=False, index= True)
location = db.Column(db.String(50))
comments = db.Column(db.String(128))
created_at = db.Column(db.DateTime)
updated_at = db.Column(db.DateTime)
# conexiones = db.Column(db.Integer)
def __init__(self, nombre=None, location=None,comments=None, creado_en=None):
self.name = nombre
self.location = location
self.comments = comments
self.created_at = creado_en
self.updated_at = creado_en
class Tag(db.Model):
__tablename__ = "tags"
id_tag = db.Column(db.Integer, autoincrement=True, primary_key=True)
id_project = db.Column(db.Integer,db.ForeignKey("projects.id_project"))
id_item = db.Column(db.Integer,db.ForeignKey("items.id_item"))
project = db.relationship(Proyecto, backref=db.backref('list_tags', lazy='dynamic'))
item = db.relationship(Item, backref=db.backref('list_tags', lazy='dynamic'))
type = db.Column(db.Integer) #(0,hub);(1,cable);(2,pipe);(3,electrical_pipes)
created_at = db.Column(db.DateTime)
updated_at = db.Column(db.DateTime)
def __init__(self,project,item,type,created_at):
self.project = project
self.item = item
self.type = type
self.created_at = created_at
self.updated_at = created_at

I had the same problem, in my case I fix that based on this answer:
Flask-SQLAlchemy polymorphic association
Also, the official SQLAlchemy docs can help:
Joined Table Inheritance In joined table inheritance, each class along
a particular classes’ list of parents is represented by a unique
table. The total set of attributes for a particular instance is
represented as a join along all tables in its inheritance path. Here,
we first define the Employee class. This table will contain a primary
key column (or columns), and a column for each attribute that’s
represented by Employee. In this case it’s just name:
class Employee(Base):
__tablename__ = 'employee'
id = Column(Integer, primary_key=True)
name = Column(String(50))
type = Column(String(50))
__mapper_args__ = {
'polymorphic_identity':'employee',
'polymorphic_on':type
}
I think your code should look like:
class Hub(Item):
__tablename__ = "hubs"
id_hub = db.Column(db.Integer, autoincrement=True, primary_key=True)
name = db.Column(db.String(50), nullable=False, index= True)
location = db.Column(db.String(50))
comments = db.Column(db.String(128))
created_at = db.Column(db.DateTime)
updated_at = db.Column(db.DateTime)
# conexiones = db.Column(db.Integer)
__mapper_args__ = {
'polymorphic_identity': 0,
'polymorphic_on': id_hub
}
def __init__(self, nombre=None, location=None,comments=None, creado_en=None):
self.name = nombre
self.location = location
self.comments = comments
self.created_at = creado_en
self.updated_at = creado_en
You may also need to set is to concrete = True to be able to query it from child class.
Concrete inheritance is inheritance of method implementations and member variables from a super-class.
Details at:
Abstract Concrete Classes
__mapper_args__ = {
'polymorphic_identity': 0,
'polymorphic_on': id_hub,
'concrete': True
}

Related

Save multiple media pictures for a value in database

I have a Table called property that accepts a contains column which refers to propertyContains table, then propertyContains will have a media column which refers to media table. i have no idea if i am storing the images the right way and i am wondering if there is any better/more efficient ways.
my code
class Property(Base):
__tablename__ = "property"
property_id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("propertyOwner.user_id"))
title = Column(String)
description = Column(String)
Location = Column(String)
rented = Column(Boolean)
rented_by = Column(Integer, ForeignKey("client.client_id"))
contains = relationship("PropertyContains", back_populates="contains_owner")
owner = relationship("Owner", back_populates="properties")
date = Column(Date, default=func.now())
class Config:
arbitrary_types_allowed = True
class Media(Base):
__tablename__ = "media"
media_id = Column(Integer, unique=True, primary_key=True, index=True)
media1 = Column(LargeBinary)
media2 = Column(LargeBinary)
media3 = Column(LargeBinary)
media_owner = Column(Integer, ForeignKey('propertyContains.property_contains_id', ondelete="CASCADE"))
class Config:
arbitrary_types_allowed = True
class PropertyContains(Base):
__tablename__ = "propertyContains"
property_contains_id = Column(Integer, unique=True, primary_key=True, index=True)
property_id = Column(Integer, ForeignKey("property.property_id"))
# media_id = Column(Integer, ForeignKey("media.media_id"))
bed_rooms = Column(Integer)
media = relationship("Media", backref="media", passive_deletes=True)
contains_owner = relationship("Property", back_populates="contains")
class Config:
arbitrary_types_allowed = True
please keep in note that i am a beginner <3.

Many to Many relationship calling on Flask SQL Alchemy

I have the following badge (achievement) system database structure:
class Base(db.Model):
__abstract__ = True
id = db.Column(db.Integer, primary_key=True)
date_created = db.Column(db.DateTime, default=db.func.current_timestamp())
date_modified = db.Column(db.DateTime, default=db.func.current_timestamp(),
onupdate=db.func.current_timestamp())
class User(UserMixin, Base):
__tablename__ = 'users'
username = db.Column(db.String(20), nullable=False, unique=True)
email = db.Column(db.String(50), nullable=False, unique=True)
password = db.Column(db.String(200), nullable=False)
name = db.Column(db.String(30), nullable=False)
badges = db.relationship('UserBadge', backref='ubadge',
lazy='dynamic')
class Badge(Base):
__tablename__ = 'badges'
name = db.Column(db.String(35), unique=True)
description = db.Column(db.String(300))
imgfile = db.Column(db.String(80))
badges = db.relationship('UserBadge', backref='badge',
lazy='dynamic')
def __repr__(self):
return '<Achievement: {} - {}>'.format(self.name, self.description)
class UserBadge(Base):
__tablename__ = 'userbadges'
user_id = db.Column(db.Integer(), db.ForeignKey('users.id'))
badge_id = db.Column(db.Integer(), db.ForeignKey('badges.id'))
def __repr__(self):
return '<Achievement: {} - {}>'.format(self.user_id, self.badge_id)
So i can return all the badges by a specific user, using:
ubadges = UserBadge.query.filter_by(user_id=user.id).all()
It returns:
[<Achievement: 1 - 1>]
But instead of 1 (user_id) and 1 (badge_id) i want to show the users.name and badges.name. How can i access those attributes?
In your UserBadge class, just use:
def __repr__(self):
return '<Achievement: {} - {}>'.format(self.ubadge.name, self.badge.name)
It has both properties because you set them up using backref in the other classes.
PS: You might need to change the User backref to user, and then use self.user.name int he function above

Deleting from self-referential inherited objects does not cascade in SQLAlchemy-SQLite

I posted a question previously here where I tried to build a hierarchy using different objects. Each object can have any type of object as it's parent, and any type as children. I solved it by using the Node class suggested by SQLAlchemy here and letting the other objects inherit from it.
Now I'm having the problem that deleting a node does not delete it's children. I have tried a lot of solutions like different cascade settings, using ondelete='CASCADE' in the foreignkey, as well as DBSession.execute('pragma foreign_keys=on') but none are working. I think the problem is in the ParentID key because in the child it is not null when the parent is delete.
I'm pretty new to SQLAlchemy so I'm not at all sure where I'm going wrong, any help would be appreciated.
These are my current models:
DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
# DBSession.execute('pragma foreign_keys=on')
Base = declarative_base()
class Node(Base):
def getID():
return uuid.uuid1().hex
__tablename__ = 'Node'
ID = Column(Text, primary_key=True, default=getID)
ParentID = Column(Text, ForeignKey('Node.ID', ondelete='CASCADE'))
type = Column(Text(50))
Children = relationship("Node",
backref=backref('Parent',
remote_side=[ID]
),
single_parent=True,
cascade="all, delete, delete-orphan",
passive_deletes = True
)
__mapper_args__ = {
'polymorphic_identity':'Node',
'polymorphic_on':type
}
class A(Node):
__tablename__ = 'A'
ID = Column(Text, ForeignKey('Node.ID', ondelete='CASCADE'), primary_key=True)
Name = Column(Text)
Description = Column(Text)
__mapper_args__ = {'polymorphic_identity':'A'}
class B(Node):
__tablename__ = 'B'
ID = Column(Text, ForeignKey('Node.ID', ondelete='CASCADE'), primary_key=True)
Name = Column(Text)
Description = Column(Text)
__mapper_args__ = {'polymorphic_identity':'B'}
class C(Node):
__tablename__ = 'C'
ID = Column(Text, ForeignKey('Node.ID', ondelete='CASCADE'), primary_key=True)
Name = Column(Text)
Description = Column(Text)
Quantity = Column(Integer)
Rate = Column(Integer)
__mapper_args__ = {'polymorphic_identity':'C' }
This is how I build a hierarchy:
a = A(Name="PName",
Description="PDesc",
ParentID='0')
b = B(Name="BGName",
Description="BGDesc",
ParentID=project.ID)
c = C(Name="BIName",
Description="BIDesc",
Quantity=10,
Rate=5,
ParentID=budgetgroup.ID)
# Append the children nodes to their parents
b.Children.append(c)
a.Children.append(b)
DBSession.add(a)
And this is how I delete it:
def deleteitem(id):
deleteid = id
deletethis = DBSession.query(Node).filter_by(ID=deleteid).first()
qry = DBSession.delete(deletethis)
# qry = DBSession.query(Node).filter_by(ID=deleteid).delete(
# synchronize_session='fetch')
transaction.commit()
Note: neither the one way or the other commented out delete cascades.
I was able to find a solution from this answer here.
Now my Node class looks as follows:
class Node(Base):
__tablename__ = 'Node'
ID = Column(Integer, primary_key=True)
ParentID = Column(Integer, ForeignKey('Node.ID', ondelete='CASCADE'))
type = Column(Text(50))
Children = relationship(
'Node',
cascade="all",
backref=backref("Parent", remote_side='Node.ID'),
)
__mapper_args__ = {
'polymorphic_identity':'Node',
'polymorphic_on':type
}
And this seems to work, all my deletes are cascading.

sqlalchemy.exc.CircularDependencyError: Circular dependency detected

The business logic - One Category may have multiple (1:M) attributes, like Category "Memory" could have attributes Speed, Size, Type etc.
at the same time one Category could be sorted by the attribute value (this is stored inside Category.sortByAttribute - which is foreign key to LookupCategoryAttributes table.
Trying to construct it via SQLAlchemy, but getting circular dependency detected. What is wrong?
class Attribute(Base):
__tablename__ = "LookupCategoryAttributes"
types = ["date", "float", "integer", "select", "string", "text"]
# Properties
ID = Column(BigInteger, primary_key=True)
categoryID = Column(BigInteger, ForeignKey('LookupCategories.ID'), nullable=False )
attribute = Column(VARCHAR(255), nullable=False)
listValues = Column(VARCHAR(4000))
typeID = Column(VARCHAR(40), nullable=False)
isRequired = Column(SmallInteger, nullable=False, default=0)
displayInMenu = Column(SmallInteger, nullable=False, default=0)
displayInFilter = Column(SmallInteger, nullable=False, default=0)
class Category(Base):
__tablename__ = "LookupCategories"
# Properties
ID = Column(BigInteger, primary_key=True)
category = Column(VARCHAR(255), nullable=False)
description = Column(VARCHAR(1000), nullable=False)
parentCategoryID = Column(BigInteger, ForeignKey('LookupCategories.ID'))
leftPos = Column(Integer)
rightPos = Column(Integer)
sortByAttribute = Column(BigInteger, ForeignKey('LookupCategoryAttributes.ID'))
sortOrder = Column(SmallInteger, default=1)
# Relationships
ParentCategory = relationship("Category", uselist=False, remote_side=[ID], backref='SubCategories')
SortByAttribute = relationship("Attribute", uselist=False, foreign_keys=[sortByAttribute], primaryjoin="Attribute.ID==Category.sortByAttribute")
Attributes = relationship("Attribute", backref="Category", primaryjoin="Attribute.categoryID==Category.ID")
and then the code looks like this:
category = Category(record['Name'], extID=extID)
attr1 = Attribute(v)
attr2 = Attribute(v)
category.Attributes.append(attr1)
category.Attributes.append(attr2)
category.SortByAttribute = attr1
when I execute commit I get:
sqlalchemy.exc.CircularDependencyError: Circular dependency detected.
Okay found the answer - use post_update in relationship
http://docs.sqlalchemy.org/en/latest/orm/relationship_persistence.html#post-update
so what I did is inside Category class is changed this:
SortByAttribute = relationship(
"Attribute",
uselist=False,
foreign_keys=[sortByAttribute],
primaryjoin="Attribute.ID==Category.sortByAttribute"
)
to this:
SortByAttribute = relationship(
"Attribute",
uselist=False,
foreign_keys=[sortByAttribute],
primaryjoin="Attribute.ID==Category.sortByAttribute",
post_update=True
)

Is multi-level polymorphism possible in SQLAlchemy?

Is it possible to have multi-level polymorphism in SQLAlchemy? Here's an example:
class Entity(Base):
__tablename__ = 'entities'
id = Column(Integer, primary_key=True)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
entity_type = Column(Unicode(20), nullable=False)
__mapper_args__ = {'polymorphic_on': entity_type}
class File(Entity):
__tablename__ = 'files'
id = Column(None, ForeignKey('entities.id'), primary_key=True)
filepath = Column(Unicode(255), nullable=False)
file_type = Column(Unicode(20), nullable=False)
__mapper_args__ = {'polymorphic_identity': u'file', 'polymorphic_on': file_type)
class Image(File):
__mapper_args__ = {'polymorphic_identity': u'image'}
__tablename__ = 'images'
id = Column(None, ForeignKey('files.id'), primary_key=True)
width = Column(Integer)
height = Column(Integer)
When I call Base.metadata.create_all(), SQLAlchemy raises the following error:
IntegrityError: (IntegrityError) entities.entity_type may not be NULL`.
This error goes away if I remove the Image model and the polymorphic_on key in File.
What gives?
Yes. The problem with your code is that you're making Image a type of file, when you must aim for the head of the tree, making Image a type of Entity.
Example:
from sqlalchemy import (Table, Column, Integer, String, create_engine,
MetaData, ForeignKey)
from sqlalchemy.orm import mapper, create_session
from sqlalchemy.ext.declarative import declarative_base
e = create_engine('sqlite:////tmp/foo.db', echo=True)
Base = declarative_base(bind=e)
class Employee(Base):
__tablename__ = 'employees'
employee_id = Column(Integer, primary_key=True)
name = Column(String(50))
type = Column(String(30), nullable=False)
__mapper_args__ = {'polymorphic_on': type}
def __init__(self, name):
self.name = name
class Manager(Employee):
__tablename__ = 'managers'
__mapper_args__ = {'polymorphic_identity': 'manager'}
employee_id = Column(Integer, ForeignKey('employees.employee_id'),
primary_key=True)
manager_data = Column(String(50))
def __init__(self, name, manager_data):
super(Manager, self).__init__(name)
self.manager_data = manager_data
class Owner(Manager):
__tablename__ = 'owners'
__mapper_args__ = {'polymorphic_identity': 'owner'}
employee_id = Column(Integer, ForeignKey('managers.employee_id'),
primary_key=True)
owner_secret = Column(String(50))
def __init__(self, name, manager_data, owner_secret):
super(Owner, self).__init__(name, manager_data)
self.owner_secret = owner_secret
Base.metadata.drop_all()
Base.metadata.create_all()
s = create_session(bind=e, autoflush=True, autocommit=False)
o = Owner('nosklo', 'mgr001', 'ownerpwd')
s.add(o)
s.commit()
Not possible (see SQL ALchemy doc):
Currently, only one discriminator column may be set, typically on the base-most class in the hierarchy. “Cascading” polymorphic columns are not yet supported.
So you should follow #nosklo proposal to change your heritage pattern.

Categories

Resources