In my application, a 'set' can have a number of 'products' associated with it. Products listed against a set must have quantities defined. For this many-to-many relationship I have followed the SQLAlchemy documentation to use an association table with an additional column (quantity).
I am trying to create a form where the user can assign products and quantities against a given set. Both the sets and products already exist in the database. The data from the form are:
set.id
product.id
quantity
This works to create a new association (e.g. set 1 is 'linked' to product 3 with quantity=XYZ) but I get an integrity error when I try to update an existing record.
I can manually add a relationship/record (dummy data) or within the Flask view function as follows:
s = Set.query.get(2)
p = Product.query.get(3)
a = Set_Product_Association(set=s, product=p, quantity=23)
db.session.add(a)
db.session.commit()
Updating the record (different quantity) manually as follows works:
s.products[0].quantity = 43
db.session.add(s)
db.session.commit()
However when I use the code from the first block instead (with the aim to update the quantity field for a given, existing set and product ID), i.e.:
a = Set_Product_Association(set=s, product=p, quantity=43)
I get an integrity error
sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) UNIQUE constraint failed: set_product_association.set_id, set_product_association.product_id [SQL: 'INSERT INTO set_product_association (set_id, product_id, quantity) VALUES (?, ?, ?)'] [parameters: (2, 3, 43)]
I assume this is to tell me that I'm trying to append a new record rather than updating the existing one.
How should I approach this? The 'manual' method works but relies on working out the correct index in the list (i.e. for the correct product.id).
Curiously, if I use form.popluate_obj(set) in my Flask view function to process the form data as described in my question here, I can update fields but not create new 'associations'. Unfortunately, I don't know what goes on behind the scenes there....
My models are defined like so:
class Set_Product_Association(db.Model):
__tablename__ = 'set_product_association'
set_id = db.Column(db.Integer, db.ForeignKey('sets.id'), primary_key=True)
product_id = db.Column(db.Integer, db.ForeignKey('products.id'), primary_key=True)
quantity = db.Column(db.Integer)
product = db.relationship("Product", back_populates="sets")
set = db.relationship("Set", back_populates="products")
class Set(db.Model):
__tablename__ = 'sets'
id = db.Column(db.Integer, primary_key=True)
products = db.relationship("Set_Product_Association",
back_populates="set")
class Product(db.Model):
__tablename__= 'products'
id = db.Column(db.Integer, primary_key=True)
part_number = db.Column(db.String(100), unique=True, nullable=False)
sets = db.relationship("Set_Product_Association",
back_populates="product")
Edit:
I've also tried reversing the operation as suggested here:
s = Set.query.get(2)
a = Set_Product_Association()
a.quantity = 43
a.product = Product.query.get(3)
a.set = s
db.session.commit()
But I still get an error:
sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) UNIQUE constraint failed: set_product_association.set_id, set_product_association.product_id [SQL: 'INSERT INTO set_product_association (set_id, product_id, quantity) VALUES (?, ?, ?)'] [parameters: (2, 3, 43)]
You get an integrity error because you are trying to create a new object with the same primary keys.
This:
a = Set_Product_Association(set=s, product=p, quantity=43)
Does not update, but create.
If you want to update the actual row in the table, you need to update the existing one:
assoc = Set_Product_Association.query.filter_by(set=s, product=p).first()
assoc.quantity = 43
db.session.commit()
Also, from the documentation it is advised to not use a model but an actual table.
Related
Let say I have two tables
Order table
class Order(db.Model):
id = db.Column(db.Integer, primary_key=True)
deliveries = db.relationship("Delivery", back_populates="order")
Delivery table
class Delivery(db.Model):
id = db.Column(db.Integer, primary_key=True)
status= db.Column(db.String(20))
order_id = db.Column(db.Integer, db.ForeignKey('order.id'), nullable=False)
order = db.relationship("Order", back_populates="deliveries")
One order may have many deliveries. The status field of delivery table has following possible values ["Planned", "Failed", "Delivered"]
Question:
How can make sure that one order does not have two planned deliveries ?
Tips:
I know I can implemt unique constraint on multiple tables like adding below line to delivery schema
__table_args__ = (db.UniqueConstraint('order_id', 'status', name='_order_status_uc'))
But by doing so I will prevent one order from having two failed deliveries.
Any help will be greatly appreciate as I want to enforce this constraint directly in the db.
Update (given it is PostgreSQL)
Given you use PostgreSQL, the most straightforward solution is to use Partial Unique Index:
CREATE UNIQUE INDEX _order_status_uc ON delivery (order_id) WHERE status = 'Planned';
If you use SQLAlchemy to create the index, the below should be equivalent:
class Delivery(Base):
# ...
__table_args__ = (
Index(
'_order_status_uc',
'order_id',
unique=True,
postgresql_where=(status == 'Planned'),
),
)
Original answer
Assuming the RDBMS you are using supports this, one implementation idea would be to do as follows:
create a computed column on delivery table which would have value of order_id for "Planned" status and NULL otherwise
create a UNIQUE Constraint on this computed column.
This assumes the following regarding used RDBMS:
Computed columns are not only supported but also can be part of the index/constraint.
the implementation of UNIQUE index is such that multiple NULL values are allowed.
Please see PostgreSQL unique constraint null: Allowing only one Null article for more background.
You could query your delivery model with the current order id and status planned, if there is a result, raise validation.
planned_delivery_exist_boolean = db.session.query(Delivery).filter(Delivery.order_id==order_id).filter(Delivery.status=="Planned").first()
if planned_delivery_exist_boolean:
# trigger validation error
I am currently trying to build a query which
give me for a one-to-many sqlalchemy query in flask both my result filters grouped and then says how many individual entries there are for it
Following is my database model to illustrate the question:
class cellphone(db.Model):
__tablename__ = 'cellphone'
id = db.Column(db.Integer, primary_key = True)
number = db.Column(db.String(30), unique=True)
sms = db.relationship('sms_accounting', backref="cellphone", lazy='dynamic')
class sms_accounting(db.Model):
__tablename__ = 'sms_accounting'
id = db.Column(db.Integer, primary_key = True)
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
cellphone_id = db.Column(db.Integer, db.ForeignKey('cellphone.id'))
What I want to do now is find out how many SMS were sent within X days per number.
Filtering and grouping I managed to do, but to calculate the sum per device correctly is not possible.
def sms_count():
search_peroid='90' #time to fetch events in days
period_start = datetime.utcnow() - timedelta(hours=int(search_peroid))
phone_count = sms_accounting.query.filter(sms_accounting.timestamp.between(period_start, \
datetime.utcnow() )).group_by(sms_accounting.cellphone_id).all()
I found some examples for func.count, but unfortunately none of them works. This already starts with the usage,
AttributeError: BaseQuery object has no attribute 'func'
even though it was imported especially.
from sqlalchemy.sql.functions import func
Forgive me if I am wrong.
As an option, you could try executing an SQL Query through Flask.
db.session.execute('select number, count(sms_accounting.id) from cellphone join sms_accounting on sms_accounting.cellphone_id = cellphone.id');
You can easily add the time based filter using where.
Regarding the AttributeError, are you sure you are using the 'func' method properly? The correct usage can be found on this unrelated answer at https://stackoverflow.com/a/4086229/4854064. It might be that you accidentally called func as a method of the query object.
I have two tables in many to many relationship:
class Association(db.Model):
__tablename__ = 'association'
club_id = db.Column(db.Integer, db.ForeignKey('clubs.id'), primary_key=True)
student_id = db.Column(db.Integer, db.ForeignKey('students.id'), primary_key=True)
joined_date = db.Column(db.String)
assoc_student = db.relationship("Student")
class Club(db.Model):
__tablename__ = 'clubs'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String)
location = db.Column(db.String)
club_assoc = db.relationship("Association")
class Student(db.Model):
__tablename__ = 'students'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
age = db.Column(db.String)
gender = db.Column(db.String)
Questions:
1) What is the difference between these two queries?
students = db.session.query(Association).filter_by(club_id='1')
students = Association.query.filter_by(club_id='1')
They seem to give the same result!
2) I'm trying to get a list of students with certain age but this following query doesn't work:
db.session.query(Association).filter_by(Association.club_id=='1', Association.assoc_student.age=='15')
But I get this error:
AttributeError: Neither 'InstrumentedAttribute' object nor 'Comparator' object associated with Association.assoc_student has an attribute 'age'
That's why I'm using this one:
db.session.query(Student).join(Association).filter(Association.club_id=='1', Student.age=='15')
Is there a better way to do this without "join"? Maybe with using "backref"!?
1) What is the difference between these two queries?
They do almost the same thing. Former is the way to query objects provided with SQLAlchemy (library Flask uses to access database).
Latter is the convenient way to query models added by Flask-SQLAlchemy library. It makes your queries more readable + extends query with few useful methods. Take a look at source of the flask_sqlalchemy.BaseQuery class to see them: get_or_404(), first_or_404() and paginate().
Usually you want to use latter method to query objects.
2) I'm trying to get a list of students with certain age but this following query doesn't work.
There are two things here:
Be aware about the difference between filter() and filter_by() methods. In your example you try to use filter_by() with SQL expressions instead of kwargs, which is incorrect.
When you're using filter() you can't specify columns over a relationships (like Association.assoc_student.age). The only allowed format is ModelName.column_name. That's why it fails.
Is there a better way?
Your second approach is absolutely correct and fine to use. I don't think there is a better way to do it. Alternatively you can use code below to avoid importing db (if you define query in another file):
Student.query.join(Association).filter(Association.club_id == '1', Student.age == '15')
My Goal
I want to record the order of manys upon insert of data to my table (e.g. Clump-see tables below). The orderinglist module is really great, but how do i apply it to the intermediary table (named clump_syntaxs) between my many-to-many? anyone done this before and have a good example?
problem re-stated
How do i apply ordering upon insert to my many to many. Everything I try using the intermediary table-clump_syntaxs table crashes (sorry for the weird names!).
The following code (reduced for brevity) works! except that it only allows for a syntax to have a unique position (instead of a position for every Clump instance), and I am guessing I need the position variable to be on the clump_syntaxs table.all tables are sqlite
my intermediary table
from sqlalchemy.ext.orderinglist import ordering_list
clump_syntaxs = db.Table('clump_syntaxs',
db.Column('syntax_id', db.Integer, db.ForeignKey('syntax.id')),
db.Column('clump_id', db.Integer, db.ForeignKey('clump.id')),
)
add a clump and order syntax tables
class Clump(db.Model):
id = db.Column(db.Integer, primary_key=True)
syntaxs = db.relationship('Syntax', secondary=clump_syntaxs,
backref=db.backref('clumps', lazy='dynamic'),order_by="Syntax.position",
collection_class=ordering_list('position'))
class Syntax(db.Model):
id = db.Column(db.Integer, primary_key=True)
jobs = db.relationship('Jobs',lazy='dynamic', backref='jobhistory')
position = db.Column(db.Integer)
#Jobs table not included
Yes, you should move position field to the intermediary table ClumpSyntax, and take advantage of association_proxy() in Clump table.
import sqlalchemy.ext.associationproxy import association_proxy
class Syntax(db.Model):
id = db.Column(db.Integer, primary_key=True)
jobs = db.relationship('Jobs',lazy='dynamic', backref='jobhistory')
#position = db.Column(db.Integer) # moved to ClumpSyntax
#Jobs table not included
class ClumpSyntax(db.Model):
syntax_id = db.Column('syntax_id', db.Integer, db.ForeignKey('syntax.id'))
syntax = relationship(Syntax)
clump_id = db.Column('clump_id', db.Integer, db.ForeignKey('clump.id'))
position = db.Column(db.Integer)
# this constructor is very necessary !
def __init__(self, syntax =None):
self.syntax = syntax
class Clump(db.Model):
id = db.Column(db.Integer, primary_key=True)
_syntaxs = db.relationship('ClumpSyntax',
order_by=[ClumpSyntax.position],
collection_class=ordering_list('position'))
syntaxs = association_proxy('_syntaxs','syntax')
My similar request was satisfied quite well by this way, based on the article this and this. You can test it by code like below:
session= some_code_to_get_db_session()
syn1= Syntax()
syn2= Syntax()
syn3= Syntax()
session.add(syn1)
session.add(syn2)
session.add(syn3)
clump= Clump()
session.add(clump)
clump.syntaxs.append(syn1)
clump.syntaxs.append(syn2)
clump.syntaxs.append(syn3)
session.commit()
session.query(ClumpSyntax).count() # print out 3
session.query(Syntax).count() # print out 3
I have a problem. I have to do this query:
#app.route('/api/subscriptions/<string:id>', methods=('DELETE',))
#decorators.login_required
def delete_subscription(id):
dbsession = DBSession()
session = Session()
favorit = (dbsession.query(StudentsFavorites)
.filter(Exams.number == str(id))
.filter(StudentsFavorites.exam_id)
.filter(Students.id == StudentsFavorites.student_id)
.filter(Students.id == str(session.get_user_id()))
.delete() )
dbsession.flush()
return jsonify(error=False)
But when I do this query I get this exception:
OperationalError: (OperationalError) no such column: exams.number u'DELETE FROM students_favorites WHERE exams.number = ? AND students_favorites.exam_id AND students.id = students_favorites.student_id AND students.id = ?' ('123123123', 'a24213')
The tables are very big and got lots of information, so i can't post all of it. But this query works:
#app.route('/api/subscriptions/<string:id>', methods=('PUT',))
#decorators.login_required
def add_subscription(id):
dbsession = DBSession()
session = Session()
examID = (dbsession.query(Exams.id)
.filter(Exams.number == id).first()
)
favorit=StudentsFavorites(student_id=session.get_user_id(), exam_id=examID.id)
dbsession.add(favorit)
dbsession.flush()
return jsonify(error=False)
Short view to the table:
table: Exams
rows: id, number (number is the id i put into the function)
table: StudentsFavorites
rows: student_id, exams_id
table: Students
rows: id
I really didn't understand, why he didn't find the number row in the exception.
EDIT:
Database StudentsFavorites:
class StudentsFavorites(Base):
"""N:M resolve model for the exams to the semester.
"""
__tablename__ = "students_favorites"
student_id = Column(Unicode(255), ForeignKey("students.id"), primary_key=True, autoincrement=False)
exam_id = Column(Integer, ForeignKey("exams.id"), primary_key=True, autoincrement=False)
created_at = Column(DateTime, nullable = False, default = datetime.now)
student = relationship("Students", uselist = False, lazy="joined")
exam = relationship("Exams", uselist=False, lazy="joined")
Something like this? I tried this:
(dbsession.query(StudentsFavorites)
.filter(StudentsFavorites.exam.id == str(id))
.filter(StudentsFavorites.student.id == str(session.get_user_id()))
.delete()
)
But got the error, that id didn't exist in exams / student
You have two cases of the same problem. Your query has information for StudentFavorites which means it knows about StudentFavorites.student_id and StudentFaovrites.exams_id. It doesn't know anything about Students.id, Exames.id and Exames.number. In order for you to query a StudentFavorites object and have it know about those other values you're going to have to perform a sql join.
Join's can be a bit of a pain in the ass to get working in sqlalchemy (well... in regular sql as well). Since I don't know what your table schema is I can't talk about that but the view should look something like this.
#app.route('/api/subscriptions/<string:id>', methods=('DELETE',))
#decorators.login_required
def delete_subscription(id):
dbsession = DBSession()
session = Session()
favorit = (dbsession.query(StudentsFavorites)
.join(Exames)
.join(students)
.filter(Exams.number == str(id))
.filter(StudentsFavorites.exam_id)
.filter(Students.id == StudentsFavorites.student_id)
.filter(Students.id == str(session.get_user_id()))
.delete() )
dbsession.flush()
return jsonify(error=False)
Alternatively, you can look into setting up Foreign key relationships in your table statements if you use the ORM to create your tables
The reason your second example works is because you're specifying a query over an exam table and only using values found in that table.
Response to Edit:
Right now your table relationships aren't set up correctly. Specifically the sections: Many To Many and Deleting Rows from the Many to Many Table
This example code is explained in much more (and better) detail in the posted link but the basic idea is that you have a associate_table (in your case StudentFavorites) contains foreign keys which have a relationship which is specified in one or more of your other tables. I personally advise that you go with the table example and not the object example.
association_table = Table('association', Base.metadata,
Column('left_id', Integer, ForeignKey('left.id')),
Column('right_id', Integer, ForeignKey('right.id'))
)
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship("Child",
secondary=association_table,
backref="parents")
class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)