Automatically setting the value of a field based on another with SQLAlchemy - python

Using Flask-SQLAlchemy I have a table which looks like this:
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
slug = db.Column(db.String(200), nullable=False)
def __init__(self, name):
self.name = name
I have a Python library which converts the value of some text into a slug like slugify('Hello World') to hello-world.
Say I was to create a User:
user = User('John Smith')
db.session.add(user)
db.session.commit()
I would like the value of slug in the User table to be Slugify('John Smith').
How would I go about this? Thanks.

As IanAuld said in the comments I should just add self.slug = slugify(name) like so:
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
slug = db.Column(db.String(200), nullable=False)
def __init__(self, name):
self.name = name
self.slug = slugify(name)

Related

Flask Rest API SQLAlchemy foreign key error

I got SQLALchemy error, when I tried "flask db migrate"
NoReferencedTableError: Foreign key associated with column 'user.menu_id' could not find table 'menu' with which to generate a foreign key to target column 'id
Menu table
class Menu(db.Model):
__tablename__ = 'menus'
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(64), index=True, unique=True)
price = db.Column(db.String(64), index=True, unique=True)
description = db.Column(db.String(64), index=True, unique=True)
picture = db.Column(db.String(64), index=True, unique=True)
create_date = db.Column(db.DateTime, default=datetime.utcnow)
users = db.relationship('User', backref="menu", lazy=True)
User table
class User(Model):
""" User model for storing user related data """
id = Column(db.Integer, primary_key=True)
email = Column(db.String(64), unique=True, index=True)
username = Column(db.String(15), unique=True, index=True)
name = Column(db.String(64))
password_hash = Column(db.String(128))
admin = Column(db.Boolean, default=False)
joined_date = Column(db.DateTime, default=datetime.utcnow)
userdataset = db.relationship("Dataset", backref="user", lazy="dynamic")
menu_id = Column(db.Integer(), db.ForeignKey('menu.id'), nullable=False)
def __init__(self, **kwargs):
super(User, self).__init__(**kwargs)
How can ı solve this problem? Where am i doing wrong?
You have renamed your 'Menu' table to 'menus' with this __tablename__ property in your 'Menu' model:
__tablename__ = 'menus'
You then try to reference to the 'Menu' table, when in fact, its name has been changed to 'menus'. The simplest way to solve this would be to change your User.menu_id column to this:
menu_id = Column(db.Integer(), db.ForeignKey('menus.id'), nullable=False)
Another way of fixing this issue would be modifying the __tablename__ property to 'menu'. (You could also just delete it.)

flask admin one to one inline_models

i have those 2 models:
#derive_schema
class Organization(db.Model):
id = Column(UUID(as_uuid=True), unique=True, primary_key=True, server_default=sqlalchemy.text("uuid_generate_v4()"))
name = Column(String, nullable=False, unique=True)
code = Column(String, nullable=False, unique=True)
owner_email = Column(String, nullable=False)
labels = Column(JSONB)
status = Column(Enum(OrganizationStatus), nullable=False)
logo_url = Column(String)
configuration = Column(JSONB, nullable=False)
def __repr__(self):
return self.name
#derive_schema
class PortalSettings(db.Model):
id = Column(UUID(as_uuid=True), unique=True, primary_key=True, server_default=sqlalchemy.text("uuid_generate_v4()"))
organization_id = db.Column(UUID(as_uuid=True), ForeignKey('organization.id'), nullable=False)
portal_settings = Column(JSONB)
organization = relationship(Organization, backref=backref('portal_settings', uselist=False, lazy="joined"))
def __repr__(self):
return self.portal_settings
and this ModelView
class OrganizationView(ConfigurationModelView):
inline_models = (PortalSettings,)
the relationship between organization and portal settings should be one to one,
but i dont understand why in flask admin i got this field when i can add as many portal settings as i want instead of just seen a input field with the portal_settings JSONB field
Based on this gist
https://gist.github.com/DrecDroid/398a05e4945805bc09d1
i've created PR onto Flask-Admin repo and maybe soon it will be merged. Anyway, you may copy-paste code from Gist and use it in your project
https://github.com/flask-admin/flask-admin/pull/2091

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

nonSQL related attribute add to SQLAlchemy model error

I'm developing a web app with python and flask. I use Flask, SQLAlchemy and PostgreSQL for development. I have many-to-one related models. By this models one company can have many users but each user can only have one company.
models.py
class Company(ResourceMixin, db.Model):
__tablename__ = 'companies'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), unique=True, index=True,
nullable=False, server_default='')
phone = db.Column(db.String(24))
email = db.Column(db.String(255), index=True)
address = db.Column(db.String(255))
# Relations
users = db.relationship('User', backref='company')
class User(UserMixin, ResourceMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
# User details
name = db.Column(db.String(50), index=True)
phone = db.Column(db.String(24))
address = db.Column(db.String(255))
email = db.Column(db.String(255), unique=True, index=True, nullable=False,
server_default='')
password = db.Column(db.String(128), nullable=False, server_default='')
# Relations
company_id = db.Column(db.Integer, db.ForeignKey('companies.id',
onupdate='CASCADE',
ondelete='SET NULL'),
index=True)
views.py
app.route('/')
def index():
company = Company.query.get(1)
flash(company.name, company.user_count)
return render_template('index.html')
Error summary: "user_count" attribute is not part of the Company model.
I want to get the number of the users dynamically from Company model. Attribute should count users on each call of the model and serve it on a regular attribute (like company.user_count). I made it by creating a class method and calling it in view function but i want it to make the process automatic without calling method prior to use attribute.
I tried init function like this:
def __init__(self):
self.user_count = len(self.users)
And like this:
def __init__(self):
self.status()
def status(self):
self.user_count = len(self.users)
return True
And like this:
def __init__(self):
self.status()
#classmethod
def status(self):
self.user_count = len(self.users)
return True
all three versions throws same error. How can i overcome the problem.
Thanks a lot!
You can use a property:
class User(Base):
...
#property
def user_count(self):
return len(self.users)

How to add a custom function/method in sqlalchemy model to do CRUD operations?

Below I have a Flask-SQLAlchemy model for the table User.
class User(db.Model):
__tablename__ = 'user'
user_id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(30), nullable=False)
created_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP"))
updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP"))
def __init__(self):
#self.name = name
self.name = None
def add_user(self, name):
self.name = name
Here add_user is a custom method. So if I call the add_user method it should add the name to the User table.
Likewise how do I write custom methods for CRUD operations in that model itself?
You'll probably want to use a classmethod to accomplish this.
class User(db.Model):
__tablename__ = 'user'
user_id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(30), nullable=False)
created_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP"))
updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP"))
def __init__(self, name):
self.name = name
#classmethod
def create(cls, **kw):
obj = cls(**kw)
db.session.add(obj)
db.session.commit()
This way you can use User.create(name="kumaran") to create a new user that will be committed to the database.
Better yet, it is a great idea to create a mixin for this method and others like it so that the functionality can be easily reused in your other models:
class BaseMixin(object):
#classmethod
def create(cls, **kw):
obj = cls(**kw)
db.session.add(obj)
db.session.commit()
You can then reuse this functionality in your models by using multiple inheritance, like so:
class User(BaseMixin, db.Model):
__tablename__ = 'user'
user_id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(30), nullable=False)
created_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP"))
updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP"))
def __init__(self, name):
self.name = name
Not sure this is relevant to Flask-SQLAlchemy, but basic SQLAlchemy has examples of creating Mixin classes or augmenting the Base class.
https://docs.sqlalchemy.org/en/13/orm/extensions/declarative/mixins.html
e.g.
from sqlalchemy.ext.declarative import declared_attr
class MyMixin(object):
#declared_attr
def __tablename__(cls):
return cls.__name__.lower()
__table_args__ = {'mysql_engine': 'InnoDB'}
__mapper_args__= {'always_refresh': True}
id = Column(Integer, primary_key=True)
class MyModel(MyMixin, Base):
name = Column(String(1000))
I would accomplish what you're after like this:
class User(db.Model):
__tablename__ = 'user'
user_id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(30), nullable=False)
created_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP"))
updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP"))
def __init__(self):
#self.name = name
self.name = None
#classmethod
def add_user(cls, session, name):
user = User(name)
session.add(user)
return User
Then in whatever context you're using it in, create a session, call your method, and commit it.
from .user import User
session = Session()
# or if using Flask SQLAlchemy
# session = db.session
User.add_user(session, 'Foo')
session.commit()
From the sqlalchemy docs:
Keep the lifecycle of the session (and usually the transaction) separate and external.
In contrast to one of the other answers, which assumes you are using this model in a Flask app with FlaskSQLAlchemy's global db.session object, parametrizing the session object like this keeps your model code separate from your session management code. This allows it to be used flexibly in many different contexts.

Categories

Resources