Avoid having 2 identical entries in SQlAlchemy using Flask - python-3.x

I need to find a way to alert the user that what he's introducing already exists in the database, I have a Flask application and a SQLAlchemy database, I'm also using Flask-WTF,
I tried with a very precarious solution: I stored the data captured by the forms in variables and I was thinking of concatenating them and using a Query to search if they exist.
nombre1 = form.nombre_primero.data
nombre2 = form.nombre_segundo.data
Anyway I think this is not the most appropriate way to handle the situation.
does Flask has some way to do this? Or would you recommend me something?
I'd grateful if you could help me!

I would approach this by creating a composite unique constraint made of the select fields in the sqlalchemy model.
The table can be configured additionally via __table_args__ class property of the declarative base.
from app import db
from sqlalchemy import UniqueConstraint
class Role(db.Model):
id = db.Column(db.Integer, primary_key=True)
nombre_primero = db.Column(db.String(64))
nombre_segundo = db.Column(db.String(64))
__table_args__ = (
UniqueConstraint('nombre_primero', 'nombre_segundo', name='uix_1'),
)
You can write the data to the table and handle what exception is raised when there is a conflict.

Okay, so there is a simple way to solve this, at the table itself, you make a condition that rejects duplicate entries based on some condition which you define.
So one easy way you can do this is make a hybrid function.
Read more about Hybrid Attributes here.
from sqlalchemy.ext.hybrid import hybrid_property
Now where you make the model for your table,
eg:
class xyz(db.Model):
__tablename__ = 'xyz'
#tablevalues defined here
#hybrid_property
def xyz()
#make a method here which rejects duplicate entries.
Once you read the documentation you will understand how this works.
I cant directly solve your problem because there isn't much information you have provided. But in this way, you can check the entries and make some method EASILY where your data is checked to be unique in anyway you want.

Related

django remove m2m instance when there are no more relations

In case we had the model:
class Publication(models.Model):
title = models.CharField(max_length=30)
class Article(models.Model):
publications = models.ManyToManyField(Publication)
According to: https://docs.djangoproject.com/en/4.0/topics/db/examples/many_to_many/, to create an object we must have both objects saved before we can create the relation:
p1 = Publication(title='The Python Journal')
p1.save()
a1 = Article(headline='Django lets you build web apps easily')
a1.save()
a1.publications.add(p1)
Now, if we called delete in either of those objects the object would be removed from the DB along with the relation between both objects. Up until this point I understand.
But is there any way of doing that, if an Article is removed, then, all the Publications that are not related to any Article will be deleted from the DB too? Or the only way to achieve that is to query first all the Articles and then iterate through them like:
to_delete = []
qset = a1.publications.all()
for publication in qset:
if publication.article_set.count() == 1:
to_delete(publication.id)
a1.delete()
Publications.filter(id__in=to_delete).delete()
But this has lots of problems, specially a concurrency one, since it might be that a publication gets used by another article between the call to .count() and publication.delete().
Is there any way of doing this automatically, like doing a "conditional" on_delete=models.CASCADE when creating the model or something?
Thanks!
I tried with #Ersain answer:
a1.publications.annotate(article_count=Count('article_set')).filter(article_count=1).delete()
Couldn't make it work. First of all, I couldn't find the article_set variable in the relationship.
django.core.exceptions.FieldError: Cannot resolve keyword 'article_set' into field. Choices are: article, id, title
And then, running the count filter on the QuerySet after filtering by article returned ALL the tags from the article, instead of just the ones with article_count=1. So finally this is the code that I managed to make it work with:
Publication.objects.annotate(article_count=Count('article')).filter(article_count=1).filter(article=a1).delete()
Definetly I'm not an expert, not sure if this is the best approach nor if it is really time expensive, so I'm open to suggestions. But as of now it's the only solution I found to perform this operation atomically.
You can remove the related objects using this query:
a1.publications.annotate(article_count=Count('article_set')).filter(article_count=1).delete()
annotate creates a temporary field for the queryset (alias field) which aggregates a number of related Article objects for each instance in the queryset of Publication objects, using Count function. Count is a built-in aggregation function in any SQL, which returns the number of rows from a query (a number of related instances in this case). Then, we filter out those results where article_count equals 1 and remove them.

How to configure a read-only relationship using SQLAlchemy?

I have a question about how to configure a Many to Many relationship using SQAlchemy avoiding to cascade any operation to a read-only table. Here the example:
Secondary table to be used on a Many to Many relationship
map_setores_funcionalidades = db.Table("map_setores_funcionalidades",
Column("id_setor", Integer, ForeignKey("setores.id")),
Column("id_funcionalidade", Integer, ForeignKey("dominios.dm_funcionalidade.id")))
Main class
class Setor(db.Model):
__tablename__ = "setores"
id = Column(Integer, primary_key=True, autoincrement=True)
uuid = Column(String(36), unique=True, nullable=False, default=uuid4)
nome = Column(String(50), nullable=False, unique=True)
funcionalidades = relationship(Funcionalidade, secondary=map_setores_funcionalidades, lazy="select")
My problem occours when I receive a new 'Setor' object from my REST service to be persisted in database (so the whole object is not managed by the SQAlchemy), then some SQL Insert operations are created, the insert for the table 'setores' (Ok) and a second one for the table 'dominios.dm_funcionalidade' (I'd like to avoid this) for each object inside the List 'funcionalidades'.
It works only if I get all 'funcionalidades' objects from database first, then update the managed object to my 'Setor' and then persist, but I think it's an unnecessary overhead since I could just tell to SQLAlchemy that 'dominios.funcionalidade' Table is a read-only relationship.
So, what I would like to do:
Persist 'Setor' object;
Persist the secondary table 'map_setores_funcionalidades' with the mapping info;
Do nothig to 'dominios.dm_funcionalidade' table;
P.S.: My database user has only a read permission on 'dominios' database schema.
I hope I made my self clear. I tried to find something about it, using relationship properties like, cascade=False or viewonly=True, but none worked for me. Maybe something to configure the ForeignKey("dominios.dm_funcionalidade.id") making it read-only, avoiding all cascade operations to the table 'domninios.dm_funcionalidade'....

Flask sqlalchemy updating multiple fields in a row

Recently moved to flask from expressjs.
I am creating a flask app using flask flask-sqlalchemy flask-wtf
It is a form heavy application. I expect to have about 30-50 forms, with each form having 20-100 fields.
Client side forms are using flask-wtf
I am able to create models and able to create a crud functionality. The problem is that with each form I have to manually do
IN CREATE
[...]
# after validation
someItem = SomeModel(someField1=form.someField1.data, ..., somefieldN = form.someFieldN.data)
db.session.add(someItem)
db.session.commit()
IN UPDATE
[....]
queryItem = SomeModel.query.filter_by(id=item_id)
queryItem.somefield1 = form.someField1.data
[...]
queryItem.somefieldN = form.someFieldN.data
db.session.commit()
As apparent, with lots of forms, it gets very tedious. Is there a way to
If you are able to suggest a library that will do this
I have searched online for the last few hours. The closest I got to was to create a dictionary and then pass it like
someDict = {'someField1': form.someField1.data, ....}
SomeModel.query.filter_by(id=item.id).update(someDict)
As you can see it is equally tedious
I am hoping to find a way to pass the form data directly to SomeModel for creating as well as updating.
I previously used expressjs + knex and I was simply able to pass req.body after validation, to knex.
Thanks for your time
Use 'populate_obj' (note: model field names must match form fields)
Create record:
someItem = SomeModel()
form.populate_obj(someItem)
db.session.add(someItem)
db.session.commit()
Update record:
queryItem = SomeModel.query.filter_by(id=item_id)
form.populate_obj(queryItem)
db.session.commit()

Dynamically change schema of DB SQLAlchemy instance

I have a Flask App which uses multiple schemas on the same MySQL database. Each schema has the same tables with the same structure and it represents a different "instance" used by the app for different accounts connecting to the application.
Is it possible to dynamically tell the db object which schema to use?
In order to follow SO rules I will also paste here the relevant part of the Flask-SQLAlchemy documentation on the topic.
Multiple Databases with Binds
Starting with 0.12 Flask-SQLAlchemy can
easily connect to multiple databases. To achieve that it preconfigures
SQLAlchemy to support multiple “binds”.
What are binds? In SQLAlchemy speak a bind is something that can
execute SQL statements and is usually a connection or engine. In
Flask-SQLAlchemy binds are always engines that are created for you
automatically behind the scenes. Each of these engines is then
associated with a short key (the bind key). This key is then used at
model declaration time to assocate a model with a specific engine.
If no bind key is specified for a model the default connection is used
instead (as configured by SQLALCHEMY_DATABASE_URI).
Example Configuration
The following configuration declares three
database connections. The special default one as well as two others
named users (for the users) and appmeta (which connects to a sqlite
database for read only access to some data the application provides
internally):
SQLALCHEMY_DATABASE_URI = 'postgres://localhost/main'
SQLALCHEMY_BINDS = {
'users': 'mysqldb://localhost/users',
'appmeta': 'sqlite:////path/to/appmeta.db'
}
Creating and Dropping Tables
The create_all() and drop_all() methods by default operate on all declared binds, including the
default one. This behavior can be customized by providing the bind
parameter. It takes either a single bind name, 'all' to refer to
all binds or a list of binds. The default bind
(SQLALCHEMY_DATABASE_URI) is named None:
>>> db.create_all()
>>> db.create_all(bind=['users'])
>>> db.create_all(bind='appmeta')
>>> db.drop_all(bind=None)
Referring to Binds
If you declare a model you can specify the bind to use with the bind_key attribute:
class User(db.Model):
__bind_key__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True)
Internally the bind key is stored in the table’s info dictionary as
'bind_key'. This is important to know because when you want to create
a table object directly you will have to put it in there:
user_favorites = db.Table('user_favorites',
db.Column('user_id', db.Integer, db.ForeignKey('user.id')),
db.Column('message_id', db.Integer, db.ForeignKey('message.id')),
info={'bind_key': 'users'}
)
If you specified the bind_key on your models you can use them
exactly the way you are used to. The model connects to the specified
database connection itself.
Here's link to Official Documentation

Flask-migrate: change model attributes and rename corresponding database columns

I have a bit of experience with Flask but not very much with databases (Flask-migrate / alembic / SqlAlchemy).
I'm following this tutorial and things are working quite alright.
I have a User model like this:
# user_model.py
from app import DB
... other imports
class User(UserMixin, DB.Model):
__tablename__ = 'users'
id = DB.Column(DB.Integer, primary_key=True)
username = DB.Column(DB.String(64), index=True, unique=True)
email = DB.Column(DB.String(120), index=True, unique=True)
password_hash = DB.Column(DB.String(128))
I can then initialize the db, do migrations, upgrades etc.
The problem started when I wanted to change that id attribute, which in Python is not a great variable name choice. Let's say I want to rename that to user_id instead.
Now obviously the db already exists and there is some data inside. I thought maybe by some kind of magic from Flask-Migrate/Alembic just modifying the User class would work. That is just change the id line above to:
user_id = DB.Column(DB.Integer, primary_key=True)
If I do this and run flask db migrate I get:
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.autogenerate.compare] Detected added column 'users.user_id'
INFO [alembic.autogenerate.compare] Detected removed column 'users.id'
So actually Alembic detects this as a column being removed and a new one added, which I suppose makes sense.
But this in fact doesn't work if I run flask db upgrade. I get the following error:
ERROR [alembic.env] (sqlite3.OperationalError) Cannot add a NOT NULL
column with default value NULL [SQL: 'ALTER TABLE users ADD COLUMN
user_id INTEGER NOT NULL']
The error is quite clear. The point is that I don't want to add a new column, I just want to rename an existing one.
Looking around I also tried to modify the script.py handling the upgrade to use the alter_column method:
def upgrade():
${upgrades if upgrades else "pass"}
# just added this line below
op.alter_column('users', 'id', nullable=False, new_column_name='user_id')
However this also doesn't seem to work (I get the same error as above).
So the question boils down to a very simple one: how do I rename a database columns in a Flask app using Flask-Migrate? Or in other words, if I wish to modify the attributes of a given model, what do I have to do so that the corresponding column names in the database are correctly renamed?
To simply rename a column in an alembic script (which is the same as flask-migrate), what you do is correct:
op.alter_column('users', 'id', nullable=False, new_column_name='user_id')
The problem comes from, in my opinion, that you need also to change its constraint as primary key:
op.drop_constraint('user_pkey', 'users', type_='primarykey')
op.create_primary_key('user_pkey', 'users', ['user_id'])
You may need to adjust the name of the primary key you re-create depending of your database type (It works like this for me with PostgreSQL)
Autogenerated alembic scripts should always be reviewed, quite often they do not do what we want if it's not for simple changes.
Note: If your column id was used as a foreign key, you may also want to change the foreign key constraints in other tables.
Alter Primary Key in Alembic describes the same kind of problem.

Resources