I have the following problem:
on stack Flask, Sqlalchemy, Flask-Admin created the following models:
class Store(db.Model):
id = db.Column(db.Integer, primary_key=True)
address = db.Column(db.String(200))
users = db.relationship('User', backref='store', lazy='dynamic')
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
store_id = db.Column(db.Integer, db.ForeignKey('store.id'))
name = db.Column(db.String(128))
login = db.Column(db.String(20))
password = db.Column(db.String(20))
I use the following forms and views:
class UserForm(FlaskForm):
store_id = QuerySelectField('Склад', query_factory=lambda: Store.query)
name = StringField('Name')
login = StringField('Login')
password = StringField('Password')
class AdminSet(AdminModelView):
def edit_form(self, obj=None):
form = UserForm(obj=obj)
return form
The view works without problems, but when I try to save the changes, the following error appears:
Failed to update record. (psycopg2.ProgrammingError) can't adapt type 'Store' [SQL: UPDATE "user" SET store_id=%(store_id)s WHERE "user".id = %(user_id)s] [parameters: {'store_id': <Store 2>, 'user_id': 2}] (Background on this error at: http://sqlalche.me/e/f405)
Why is this happening and what am I doing wrong?
From the WTForms documentation:
"The data property actually will store/keep an ORM model instance, not the ID."
Change your form definition to the following:
class UserForm(FlaskForm):
store = QuerySelectField('Склад', query_factory=lambda: Store.query)
name = StringField('Name')
login = StringField('Login')
password = StringField('Password')
Related
I'm having difficulties in relationships. I have users and roles and defined model and schema for them.
the problem is when I try to add a new user with a previously defined role (I have its ID and name)it will try to update/insert the role table by the value the user specifies. but I only want to select from roles and specify that as a user role and not updating the role table(if role not found return error).
what I want to achieve is how to limit SQLalchemy in updating related tables by the value that the user specifies.
here is my models:
class User(db.Model):
"""user model
"""
__tablename__ = 'user'
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, unique=True, nullable=False)
username = db.Column(db.String(40), unique=True, nullable=False)
password = db.Column(db.String(255), nullable=False)
role_id = db.Column(UUID(as_uuid=True), db.ForeignKey('role.id') , nullable=False)
class Role(db.Model):
"""role model
"""
__tablename__ = 'role'
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, unique=True, nullable=False)
name = db.Column(db.String(40), unique=True, nullable=False)
perm_add = db.Column(db.Boolean, default=False)
perm_edit = db.Column(db.Boolean, default=False)
perm_del = db.Column(db.Boolean, default=False)
here is the schema that I defined:
class UserSchema(ma.SQLAlchemyAutoSchema):
password = ma.String(load_only=True, required=True)
email = ma.String(required=True)
role = fields.Nested("RoleSchema", only=("id", "name"), required=True)
class Meta:
model = User
sqla_session = db.session
load_instance = True
and I grab user input which is checked by schema and commit it to DB.
schema = UserSchema()
user = schema.load(request.json)
db.session.add(user)
try:
db.session.commit()
the point is here I could not change anything regarding role name or ID as it seems it is changed by schema even before applying to DB (I mean request.json)
In my example, I am using the additional webargs library. It facilitates validation on the server side and enables clean notation. Since marschmallow is based on webargs anyway, I think the addition makes sense.
I have based myself on your specifications. Depending on what you intend to do further, you may need to make adjustments.
I added a relationship to the user model to make the role easier to use.
class User(db.Model):
"""user model"""
# ...
# The role is mapped by sqlalchemy using the foreign key
# as an object and can be reached via a virtual relationship.
role = db.relationship('Role')
I have allowed the foreign key as a query parameter in the schema and limited the nested schema to the output. The email is assigned to the username.
class RoleSchema(ma.SQLAlchemyAutoSchema):
class Meta:
model = Role
load_instance = True
class UserSchema(ma.SQLAlchemyAutoSchema):
# The entry of the email is converted into a username.
username = ma.String(required=True, data_key='email')
password = ma.String(required=True, load_only=True)
# The foreign key is only used here for loading.
role_id = ma.Integer(required=True, load_only=True)
# The role is dumped with a query.
role = ma.Nested("RoleSchema", only=("id", "name"), dump_only=True)
class Meta:
model = User
load_instance = True
include_relationships = True
It is now possible to query the role from the database and react if it does not exist. The database table for the roles is no longer updated automatically.
from flask import abort
from sqlalchemy.exc import SQLAlchemyError
from webargs.flaskparser import use_args, use_kwargs
# A schema-compliant input is expected as JSON
# and passed as a parameter to the function.
#blueprint.route('/users/', methods=['POST'])
#use_args(UserSchema(), location='json')
def user_new(user):
# The role is queried from the database and assigned to the user object.
# If not available, 404 Not Found is returned.
user_role = Role.query.get_or_404(user.role_id)
user.role = user_role
# Now the user can be added to the database.
db.session.add(user)
try:
db.session.commit()
except SQLAlchemyError as exc:
# If an error occurs while adding to the database,
# 422 Unprocessable Entity is returned
db.session.rollback()
abort(422)
# Upon successful completion, the new user is returned
# with the status code 201 Created.
user_schema = UserSchema()
user_data = user_schema.dump(user)
return jsonify(data=user_data), 201
I'm developing a DB where you at first register as a regular User and subsequently become a Driver. I'd like to create a Driver object in SQL-Alchemy based on existing User object. How can I achieve that?
Suppose that the User table already has a user with ID=3. Now we would like to add a Driver based on that user.
Here are classes that I used:
# Base class
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.String(MAX_NAME_LENGTH), nullable=False)
last_name = db.Column(db.String(MAX_SURNAME_LENGTH), nullable=False)
email = db.Column(db.String(MAX_EMAIL_LENGTH), nullable=False, unique=True)
# Child class
class Driver(User):
id = db.Column(db.Integer, db.ForeignKey(User.__tablename__ + '.id'), primary_key=True)
photo_url= db.Column(db.String(MAX_URL_LENGTH), nullable=False)
# ...
# we can easily add a new User
user = User(first_name='Martin', last_name='Smith', email='m.smith#gmail.com')
db.session.add(user)
db.session.commit()
# But this code will create a new user and assign it to that driver instance!
# driver = Driver(# Will require fields of both User and Driver class #)
I expect to find some method for taking parent object and extending it with child's properties. Something like:
user_from_db = db.session.query(User).filter_by(User.id == 3).first()
driver = Driver(photo_url='/your_url/', based_on=user_from_db)
# And now driver has all properties of 'user_from_db'
# Furthermore, SQL-Alchemy would create a row only in `Driver` table
How can I accomplish that using SQL-Alchemy inheritance?
First I recommend making the following changes:
The autoincrement field is to leave the task of assigning an id to the database.
For all relationship (OneToMany, ManyToMany or OneToOne) you need to specify a db.relationship in the parent class. Read this.
And __tablename__ is optional, but sometimes flask throw an error if this is not assigned.
# Base class
class User(db.Model):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
first_name = db.Column(db.String(MAX_NAME_LENGTH), nullable=False)
last_name = db.Column(db.String(MAX_SURNAME_LENGTH), nullable=False)
email = db.Column(db.String(MAX_EMAIL_LENGTH), nullable=False, unique=True)
driver = db.relationship('Driver', backref='driver', lazy=True)
# Child class
class Driver(db.Model):
__tablename__ = 'driver'
id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True)
photo_url= db.Column(db.String(MAX_URL_LENGTH), nullable=False)
# ...
user = User(first_name='Martin', last_name='Smith', email='m.smith#gmail.com')
db.session.add(user) # first we create a user and add him to the current session
db.session.flush() # here we says that db.session.add(user) is a pending transaction. More info [here][1]
driver = Driver(id=user.id, photo_url='/your_url/')
# and we pass as driver id the id of user previously created
db.session.add(driver) # add driver to current session
db.session.commit() # commits those changes to the database.
The SQLAlchemy-Utils documentation for the EncryptedType column type has an example that looks something like this:
secret_key = 'secretkey1234'
# setup
engine = create_engine('sqlite:///:memory:')
connection = engine.connect()
Base = declarative_base()
class User(Base):
__tablename__ = "user"
id = sa.Column(sa.Integer, primary_key=True)
username = sa.Column(EncryptedType(sa.Unicode,
secret_key,
AesEngine,
'pkcs5'))
But what if I don't know what the secret key is before I define the User class? For example, what if I want to prompt the user to enter the secret key?
This is the last example in the docs that you linked to:
The key parameter accepts a callable to allow for the key to change
per-row instead of being fixed for the whole table.
def get_key():
return 'dynamic-key'
class User(Base):
__tablename__ = 'user'
id = sa.Column(sa.Integer, primary_key=True)
username = sa.Column(EncryptedType(
sa.Unicode, get_key))
I'm new with Flask and SQLALchemy.
I got a User instance user:
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
uname = db.Column(db.String(64))
avatar = db.Column(db.String(64))
...
and i got a json user_json:
{
'uname': 'John',
'avatar': 'http://example/john.jpg',
...
}
I want to each filed of user will automatically update from the json.
And this is what i have tried:
for key, value in user_json.items():
if key in self.__table__.columns:
self[key] = value
And, obviously, it does't work. :(
I know this has been answered before, but I have been through every answer and nothing either makes sense or I feel like my code is already doing what is being said in the answers.
So I have a model for terms and links, which has a many-to-many relationship.
class ProjectTerms(models.Model):
id = models.IntegerField(primary_key=True) # AutoField?
terms = models.CharField(max_length=100)
class Meta:
db_table = 'project_terms'
class ProjectLinks(models.Model):
id = models.IntegerField(primary_key=True) # AutoField?
links = models.CharField(max_length=100)
relations = models.ManyToManyField(ProjectTerms)
class Meta:
db_table = 'project_links'
class ProjectLinksRelations(models.Model):
id = models.IntegerField(primary_key=True) # AutoField?
project_terms = models.ForeignKey('ProjectTerms')
project_links = models.ForeignKey(ProjectLinks)
Script:
def create_models(my_term, my_link):
saved_term = ProjectTerms.objects.update_or_create(terms = my_term)
saved_link = ProjectLinks.objects.update_or_create(links = my_link)
ProjectLinksRelations.objects.update_or_create(project_terms=saved_term, project_links=saved_link)
A lot of places say that the model (both term and link here) has to be saved before being added to the connecting database.
I've also tried creating each term and link separately and saving them like this:
def create_models(my_term, my_link):
saved_term = ProjectTerms(terms = my_term)
saved_term.save()
saved_link = ProjectLinks(links = my_link)
saved_link.save()
relation = ProjectLinksRelations.objects.update_or_create(project_terms=saved_term, project_links=saved_link)
relation.save()
All you need for this is:
class ProjectTerms(models.Model):
terms = models.CharField(max_length=100)
class ProjectLinks(models.Model):
links = models.CharField(max_length=100)
relations = models.ManyToManyField(ProjectTerms)
The ID fields and the table for the ManyToManyField will be automatically created.
To set the ManyToMany relationship, use add:
def create_models(my_term, my_link):
(saved_term, created) = ProjectTerms.objects.update_or_create(terms = my_term)
(saved_link, created) = ProjectLinks.objects.update_or_create(links = my_link)
saved_link.relations.add(saved_term)
saved_link.save()