Suppose I have such table
class Base(object):
id = Column(Integer, primary_key=True)
date_updated = Column(Date, nullable=True)
rating = Column(Integer, nullable=False, default=0)
status = Column(SmallInteger, nullable=False, default=0)
#declared_attr
def __tablename__(cls):
return "table_%s" % cls.LANG
#declared_attr
def __table_args__(cls):
return (
Index('ix__%s__rating' % cls.__tablename__, 'rating'),
Index(
'ix__%s__status' % cls.__tablename__,
'status',
postgresql_where=column('status') == 0,
),
Index(
'ix__%s__date_updated' % cls.__tablename__,
'date_updated',
postgresql_where=column('date_updated') != None,
),
)
class TableEn(Base, db.Model):
LANG = 'en'
class TableUk(Base, db.Model):
LANG = 'uk'
Some way I've found how to create such (partial) indexes as status and date_updated in parent __table_args__.
But I need to create desc sorting index rating, like func.desc(rating), but I do not know how to do that.
None of variants worked for me (variant and it's error):
Index('ix__%s__rating' % cls.__tablename__, 'rating desc')
KeyError: 'rating desc'
Index('ix__%s__rating' % cls.__tablename__, cls.rating.desc())
sqlalchemy.exc.ArgumentError: Can't add unnamed column to column collection
Index('ix__%s__rating' % cls.__tablename__, desc('rating'))
while creating schema in db
sqlalchemy.exc.ProgrammingError: (ProgrammingError) syntax error at or near ")"
LINE 1: CREATE INDEX ix__table_en__rating ON table_en ()
Of course I can create that index manually with direct SQL, but I'm sure solution exists somewhere.
Thanks in advance!
Here simplified code that doing what you want:
class BaseModel(Base):
__abstract__ = True
id = Column(Integer, primary_key=True)
rating = Column(Integer, nullable=False, default=0)
#declared_attr
def __tablename__(cls):
return "table_%s" % cls.LANG
#classmethod
def build_indexes(cls):
Index('ix__%s__rating' % cls.__tablename__, cls.__table__.c.rating.desc())
#event.listens_for(BaseModel, 'instrument_class', propagate=True)
def receive_mapper_configured(mapper, class_):
class_.build_indexes()
class TableEn(BaseModel):
LANG = 'en'
class TableUk(BaseModel):
LANG = 'uk'
Full code here.
Here you are having 2 problems. First, you should call desc against table column, not an mapper object attribute. But if you do it you fill have problem, because __table__ attribute is created after getting info from __table__args__. So you need create indexes after creating __table__ field. One way to do it is via sqlalchemy events.
Related
I am trying to build a model where there is the default values then there is the user defined values. So the default values would come from the spices table. Yes the spices table would contain default data. The user would define the composite spice and make modifications as desired for a specific recipe. If you think I am structuring this wrong please provide your expertise. I feel lost on how to do this.
class User(db.Model, UserMixin):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=True)
#... extra
#... extra
class Spice(db.Model):
__tablename__ = 'spices'
code = db.Column(db.String(5), primary_key=True) # this is the id code
name = db.Column(db.String(60))
origin = db.Column(db.String(15))
def __init__(self, code, name, origin):
self.code = code
self.name = name
self.origin = origin
class Seasoning(Spice):
__tablename__ = 'seasonings'
# Note that the below item should come from Recipe. How would I do this?
recipe_id = db.Column(db.Integer, db.ForeignKey('recipe.id'), nullable=False)
class Recipe(db.Model):
__tablename__ = 'recipe'
user = db.relationship(User)
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(60))
description = db.Column(db.Text(), nullable=False)
date = db.Column(db.DateTime, default=datetime.datetime.utcnow)
ingredient = db.relationship('Ingredient', backref='recipe', lazy='dynamic', primaryjoin="Recipe.id == Seasoning.recipe_id")
def __init__(self, id, name, description, date):
self.id = id
self.name = name
self.description = description
self.date = date
in my views.py I have
...
seasoning = Seasoning(code=from.code.data, name=form.name.data, origin=form.origin,
recipe_id=recipe_id)
db.session.add(seasoning)
db.create_all()
db.session.commit()
...
When I run this I do get an error when I try to commit() to seasoning. How do I resolve this?
sqlalchemy.exc.OperationalError: (raised as a result of Query-invoked
autoflush; consider using a session.no_autoflush block if this flush
is occurring prematurely) (sqlite3.OperationalError) table spices has
no column named recipe_id
You need to describe recipe_id in your spices class
table spices has no column named recipe_id
class MyClass(Base):
__tablename__ = 'my_table'
id = Column(Integer, primary_key=True)
type_id = Column(String(50))
# map value
type_name = {
"type_id_1":"name_1",
"type_id_2":"name_2"
}
Is there a way to return "type name" when doing query type_id from table Myclass ?
by using #hybrid_property, I got really close to the goal
class Myclass(Base):
...
#hybrid_property
def type_name(self):
# The goal is get type_name, but it's not working since self.type_id is not a String object
name = type_name[self.type_id]
return cast(name, String)
reading your question I interpreted it this way. Do you want this?
class MyClass(Base):
__tablename__ = 'my_table'
id = Column(Integer, primary_key=True)
type_id = Column(String(50))
def __repr__(self):
return "<my_table(id='%s', type_id='%s')>" % (self.id, self.type_id)
I have a many to many relationship in my DB. The problem is that the many to many table is not populated when I submit my form.
That is my code for my models:
CompanyCategory = Table('CompanyCategory', Base.metadata,
Column('id', Integer, primary_key=True),
Column('categoryId', Integer, ForeignKey('categories.id')),
Column('companyId', Integer, ForeignKey('companies.id')))
class Company(Base):
__tablename__ = 'companies'
id = Column(Integer, primary_key=True)
company_name = Column(Text, nullable=False)
users_id = Column(Integer, ForeignKey('users.id'))
users = relationship("User", backref=backref('users', cascade="all, delete-orphan"),
lazy='joined')
# foreign key for category
addresses_category = relationship('Category', secondary=CompanyCategory, backref='companies')
def __init__(self, company_name=None, users_id=None):
self.company_name = company_name
self.users_id = users_id
def get(self, id):
if self.id == id:
return self
else:
return None
def __repr__(self):
return '<%s(%r, %r, %r)>' % (self.__class__.__name__, self.id, self.company_name, self.users_id)
class Category(Base):
__tablename__ = 'categories'
id = Column(Integer, primary_key=True)
category_name = Column(Text, nullable=False)
addresses_company = relationship('Company', secondary=CompanyCategory, backref='categories')
def __init__(self, category_name=None):
self.category_name = category_name
def get(self, id):
if self.id == id:
return self
else:
return None
def __repr__(self):
return '<%s(%r, %r)>' % (self.__class__.__name__, self.id, self.category_name)
I have two classes Company and Category. A company can have multiple categories (later there will be a fixed number of categories which I will create).
To show this I created a simple many to many relation CompanyCategory.
As far as I understand sqlalchemy should automatically add the foreign keys of company and categorie if these are created.
That is the part where I am adding a company and a category to the DB:
new_user_company = Company(company_name=form.company_name.data.strip(), users_id = new_user.id)
if new_user_company:
db_session.add(new_user_company)
db_session.commit()
new_user_company_category = Category(category_name=form.category_name.data)
if new_user_company_category:
db_session.add(new_user_company_category)
db_session.commit()
In this case the data is added to Company and also to Category but the there is no entry to CompanyCategory.
I have already tried a few solutions here but they are mostly for flask-sqlalchemy and not for bidirectional behavior. I need bidirectional behavior because if a company is deleted all many to many relations where the company occures should be also deleted. Also if a category will be deleted (wont probably happen) all relations with companies where these category occured should be deleted.
Thanks
As SQLAlchemy ORM tutorial explains: you need to specify relationship attribute for an instance. In your case you need to set Company.addresses_category or Category.addresses_company:
new_user_company = Company(company_name=form.company_name.data.strip(), users_id = new_user.id)
if new_user_company:
db_session.add(new_user_company)
db_session.commit()
new_user_company_category = Category(category_name=form.category_name.data)
new_user_company_category.addresses_company.append(new_user_company)
if new_user_company_category:
db_session.add(new_user_company_category)
db_session.commit()
Also if you need the relations to be deleted if one of parent object is deleted it is better to set ondelete attributes:
CompanyCategory = Table('CompanyCategory', Base.metadata,
Column('id', Integer, primary_key=True),
Column('categoryId', Integer, ForeignKey('categories.id', ondelete='CASCADE')),
Column('companyId', Integer, ForeignKey('companies.id', ondelete='CASCADE')))
I am trying to create a relationship between the two tables.
When instantiating a model I get the following error:
from core.models import Activation
a = Activation()
ArgumentError: Class object expected, got 'Table(u'activations', MetaData(bind=Engine(postgresql+psycopg2://localhost:5432/mydb)), Column('id', Integer(), table=<activations>, primary_key=True, nullable=False), Column('valid_until', DateTime(), table=<activations>), Column('code', Unicode(length=30), table=<activations>, nullable=False), Column('created_by', Unicode(length=16), table=<activations>, nullable=False), schema=None)'.
core/models.py
class ActivationMixin(Base):
#declared_attr
def code(self):
return Column(Unicode(30), nullable=False, unique=True)
#declared_attr
def valid_until(self):
return Column(DateTime, nullable=True)
#declared_attr
def created_by(self):
return Column(Unicode(16), nullable=False)
#classmethod
def get_by_code(cls, request, code):
session = get_session(request)
return session.query(cls).filter(cls.code == code).first()
def __init__(self, created_by='web', valid_until=None):
"""Create a new activation. *valid_until* is a datetime.
It defaults to 3 days from current day.
"""
self.code = generate_random_string(24)
self.created_by = created_by
if valid_until:
self.valid_until = valid_until
else:
self.valid_until = datetime.utcnow() + timedelta(days=3)
class Activation(ActivationMixin):
pass
user/models.py
class User(Base):
email = Column(Unicode(256), nullable=False, unique=True)
status = Column(Boolean, default=False)
salt = Column(Unicode(32), nullable=False)
_password = Column('password', Unicode(256), nullable=False)
logins = Column(Integer, default=0)
last_login = Column(
TIMESTAMP(timezone=False),
default=func.now(),
server_default=func.now(),
nullable=False
)
account_type = Column(AccountType.db_type())
#declared_attr
def activation_id(self):
return Column(
Integer,
ForeignKey('%s.id' % (Activation.__tablename__))
)
#property
def is_activated(self):
if self.activation_id is None:
return True
return False
#declared_attr
def activation(self):
return relationship(
Activation.__tablename__,
backref=self.__tablename__
)
The error is occures with the following declaration:
#declared_attr
def activation(self):
return relationship(
Activation.__tablename__,
backref=self.__tablename__
)
There seems to be some code that you haven't posted since running just this code would give you other errors as well (e.g., related to the __tablename__ attributes not being properly set). However, the actual error you are getting is probably related to this code...
#declared_attr
def activation(self):
return relationship(
Activation.__tablename__,
backref=self.__tablename__
)
According to the documentation for relationship function, the first argument should be...
a mapped class, or actual Mapper instance, representing the target of
the relationship.
However, you are using the __tablename__ attribute, which should be just by the name of the table (a string).
So, try changing this to...
#declared_attr
def activation(self):
return relationship(
Activation,
backref=self.__tablename__
)
So using SqlAlchemy, I'm creating a fairly simple many-to-many relationship between a users model and a comments model.
users.py
class UsersModel(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
username = Column(String(50))
email = Column(String(50))
issues = relationship(IssuesModel, backref="user")
comments = relationship(IssueCommentsModel, backref="user")
voted_comments = association_proxy('users_comments', 'comment')
def __init__(self, **fields):
self.__dict__.update(fields)
def __repr__(self):
return "<Users('%s', '%s', '%s')>" % (self.id,
self.username,
self.email)
users_comments.py
class UsersCommentsModel(Base):
__tablename__ = 'users_comments'
user_id = Column(Integer, ForeignKey('users.id'), primary_key=True)
comment_id = Column(Integer, ForeignKey('issue_comments.id'), primary_key=True)
vote = Column(Integer(1))
user = relationship(UsersModel,
backref="users_comments")
comment = relationship(IssueCommentsModel,
backref="users_comments")
def __init__(self, **fields):
self.__dict__.update(fields)
def __repr__(self):
return "<UsersComments('%s', '%s', '%s')>" % (self.user_id,
self.comment_id,
self.vote)
issue_comments.py
class IssueCommentsModel(Base):
__tablename__ = 'issue_comments'
id = Column(Integer, primary_key=True)
body = Column(String(300))
created = Column(DateTime)
change_time = Column(DateTime)
score = Column(Integer(100), default=0)
issue_id = Column(Integer, ForeignKey('issues.id'))
user_id = Column(Integer, ForeignKey('users.id'))
voted_users = association_proxy('users_comments', 'user')
def __init__(self, **fields):
self.__dict__.update(fields)
def __repr__(self):
return "<IssueComments('%s', '%s', '%s', '%s', '%s', '%s')>" % (self.id,
self.body,
self.issue,
self.user,
self.created,
self.change_time)
Each user has the ability to both create comments, and either up/down vote them. The above code is completely working code. My question is this. When I remove the two backrefs in the UsersCommentsModel class, the code no longer works and an InvalidRequestError is thrown. It states that the users_comments attribute cannot be found on the UsersModel mapper. I thought this was strange, b/c I would think you would be able to proxy all relationships through the central users_comments relationship model, and never have to actually store an instance of that model in the users/comments models.
Question:
Is there any way to remove the backrefs, so no other attributes are stored in the users/comments models?