Flask-SQLAlchemy get rows with non-zero count - flask

I have the following two models in my database:
class Category(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), unique=True)
websites = db.relationship('Website', backref='category', lazy='dynamic')
class Website(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
category_id = db.Column(db.Integer, db.ForeignKey('category.id'))
url = db.Column(db.String(1000), nullable=False)
class_name = db.Column(db.String(50), nullable=False)
What I now want to do is write an expression for loading all the categories that have one or more websites associated with them. In SQL I can achieve this with a join and having, but I'm having trouble translating it into SQLAlchemy.
I first tried to import and use func, but then realized that it's not defined in Flask-SQLAlchemy. My next try was Category.query.having(Category.websites.count() > 1).all() but this gives me an error that AttributeError: Neither 'InstrumentedAttribute' object nor 'Comparator' object associated with Category.websites has an attribute 'count'. What's the right way to do this?
Any help will be appreiciated!

I believe you can still use join with group_by() and the having() func. Here's an example taken from the docs for having() that might be what you're looking for:
q = session.query(Website.id).\
join(Website.category_id).\
group_by(Category.id).\
having(func.count(Website.url) > 0)

Related

Relative Primary Key in Flask_SQLAlchemy

Context/Minimal Example: I'm relatively new to Database design and trying to design a small app in Flask/Flask_SQLAlchemy that tracks inventory.
I have a User table:
class Users(db.Model):
user_id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.String(25))
items = db.relationship('Item', lazy="dynamic")
and a Item table:
class Item(db.Model):
user_id = db.Column(db.Integer, db.ForeignKey('users.user_id'), index=True)
sku = db.Column(db.Integer, index=True, autoincrement=True)
name = db.String(10)
I would like to create a system where the Item.sku is unique... but only based on the user.id.
E.g. two users can have items of the same Sku, but one user may not have multiple items of the same sku. (And preferably have the sku automatically increment itself).
To me, this is a constraint that makes sense- sku+user_id should always be a unique combination, so I can save space and simplicity by using it as a primary key, as well as increasing the ?normalization? of the database.
However, I've spent a fair amount of time now reading and trying to figure out how to do this and I keep running into problems. Is there an easy way of accomplishing this, or is there something wrong with my logic that has lead to this design? Are there downsides to this I'm missing?
So far I've tried:
Setting both user_id and sku to primary_key=true
Setting them both to index=True (as you can see here)
Adding a table_args = db.PrimaryKeyConstraint (As discussed here https://www.reddit.com/r/flask/comments/g3tje5/composite_key_for_flasksqlalchemy/)
From what I've read the term of what I'm trying to accomplish here is a compound primary key, and that flask_sqlalchemy does support it, but with all of these I get exceptions that a constraint is failing or a parameter is missing.
Thanks for any help or advice you can provide.
Yes, a composite PK on (user_id, sku) will work, as in this example using vanilla SQLAlchemy ORM:
import sqlalchemy as db
from sqlalchemy.orm import declarative_base, relationship, Session
engine = db.create_engine("sqlite://")
Base = declarative_base()
class Users(Base):
__tablename__ = "users"
user_id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.String(25))
items = relationship('Item', lazy="dynamic")
class Item(Base):
__tablename__ = "item"
user_id = db.Column(db.Integer, db.ForeignKey('users.user_id'), primary_key=True)
sku = db.Column(db.Integer, index=True, primary_key=True)
name = db.String(10)
Base.metadata.create_all(engine)
with Session(engine) as sess:
gord = Users(first_name="Gord", items=[Item(sku=1)])
anne = Users(first_name="Anne", items=[Item(sku=1), Item(sku=2)])
sess.add_all([gord, anne])
sess.commit()
# okay so far
# now try to add a duplicate
gord.items.append(Item(sku=1))
sess.flush()
"""
sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) UNIQUE constraint failed: item.user_id, item.sku
[SQL: INSERT INTO item (user_id, sku) VALUES (?, ?)]
[parameters: (1, 1)]
"""

Run filter on sqlalchemy Model relationship before passing to Marshmellow

I have two sqlalchemy models with a one-to-many relationship using lazy="dynamic like so:
class ProjectModel(db.Model):
__tablename__ = "projects"
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(1000), nullable=False, unique=True)
jobs = db.relationship("JobModel", backref="project", cascade="all,delete", lazy="dynamic")
class JobModel(db.Model):
id = db.Column(db.Integer, primary_key=True)
When I deserialize a project to serve on my viewhandler, I want to run a filter on the jobs, something like so (which results in an error):
project = db.session.query(ProjectModel.first())
project.jobs = project.jobs.filter(JobModel.id == "36")
project_dump = ProjectSchema().dump(project)
The filter itself works fine, but I can't replace the jobs key in the Model object, is there another way I can run this filter so it applies when passed into a Marshmellow schema?

Flask Admin One to One Relationship and Edit Form

I am building an admin dashboard for my web app using Flask-Admin. For the user/address relationship, I am using a one to one relationship. On the user edit form, I'd like to be able to edit the individual components of the address (i.e. street address, city or zip) similar to what inline_models provides. Instead, flask-admin generates a select field and only allows me to select a different addresses.
I tried using inline_models = ['address'] in the UserModelView definition. However, I got the address object not iterable error due to the user/address relationship being configured to uselist=False. Switching uselist to True would affect other parts of my code, so I'd prefer to leave it as False.
From looking in flask-admin/contrib/sqla/forms, within the function get_forms, its being assigned a one to many tag which is what drives the use of a select field.
Before diving in further, I figured it best to see if anyone else has come across this or has a recommended fix/workaround.
models.py
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64))
address = db.relationship("Address", backref="user",
cascade="all, delete-orphan", lazy=False,
uselist=False, passive_deletes=True)
class Address(db.Model):
id = db.Column(db.Integer, primary_key=True)
line1 = db.Column(db.String(128))
zip = db.Column(db.String(20), index=True)
city = db.Column(db.String(64), index=True, nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey("user.id",
ondelete="CASCADE"))
admin.py
class UserModelView(ModelView):
column_list = [User.username, 'address']
form_columns = (User.username, 'address')
admin = Admin(name='Ask', template_mode='bootstrap3')
admin.add_view(UserModelView(User, db.session))
You can create 2 relations
# Relation for flask admin inline model
address_cms_relationsip = db.relationship(
"Address", backref="user", cascade="all, delete-orphan", lazy=False,
uselist=True, passive_deletes=True)
address_relationship = db.relationship(
"Address", cascade="all, delete-orphan", lazy=False,
uselist=False, passive_deletes=True)
#property
def address(self):
return self.address_relationship
In your code you can use property address
user: User # some User object
user.address.city

Flask, NoForeignKeysError despite model table correctly set unless otherwise

I thought I correctly define my data models where i want to link Community with their agenda however I am having the following error.
Can you please give a hint?
NoForeignKeysError: Could not determine join condition between parent/child tables on relationship CSCommunity.agenda - there are no foreign keys linking these tables. Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint, or specify a 'primaryjoin' expression.
on the following code.
class Agenda(db.Model):
__tablename__ = 'communityagenda'
id = db.Column(db.Integer, primary_key=True)
csc = db.Column(db.Integer, db.ForeignKey('scorecards.id'))
cscommunity = db.Column(db.Integer, db.ForeignKey('cscommunities.id'))
meeting_date = db.Column(db.DateTime)
notes = db.Column(db.Text)
class CSCommunity(db.Model):
__tablename = 'cscommunities'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(150))
key_leaders = db.Column(db.Text)
subgroup = db.Column(db.Text)
#---------------------------
region = db.Column(db.Integer, db.ForeignKey('regions.id'))
district = db.Column(db.Integer, db.ForeignKey('districts.id'))
subdistrict = db.Column(db.Integer, db.ForeignKey('subdistricts.id'))
village = db.Column(db.Integer, db.ForeignKey('villages.id'))
agenda = db.relationship('Agenda', backref='cscommunity', cascade='all, delete-orphan', lazy='dynamic')
faclilitators = db.relationship('Facilitators', backref='cscommunities', cascade='all, delete-orphan', lazy='dynamic')
There are couple of issues with this code (if we skip the fact that regions, districts, subdistricts, villages and Facilitators are not defined in your snippet):
__tablename = 'cscommunities' should be __tablename__ = 'cscommunities' (notice you're missing second '__')
you created backref='cscommunity' (relationship) which conflicts with Agenda.cscommunity (column). Rename your backref.
I'm getting no errors after fixing both (SQLAlchemy==1.0.4)

SQLAlchemy Join two tables in (1 to 0..1 ) relationship

I have these two tables in (1 to 0..1) relationship:
models.py
# --------------------------------------------------
class Person(db.Model):
__tablename__ = 'person'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
address = db.Column(db.String, nullable=False)
# --------------------------------------------------
class Gift(db.Model):
__tablename__ = 'gift'
id = db.Column(db.Integer, primary_key=True)
person_id = db.Column(db.Integer, db.ForeignKey('person.id'))
person = relationship("Person", uselist=False, backref="gift")
gift_idea = db.Column(db.String, nullable=False)
# --------------------------------------------------
Questions:
Is the structure of my one to one tables correct? I know that in the SQLAlchemy documentation, the relationship code line (person = relationship...) is in the parent class but I need it to be in the child class, is that OK?!
My goal is to display (gift_idea from Gift table) and (person_name, person_address from Person table), if there's a gift_idea for that person. In my search I found people use query.join to get that and I have tried many codes but this following statement at least get me half the content I
need:
views.py
a = session.query(Gift).join(Person)
Could you please assist me with my questions? Thank you for any effort.
As per sqlalchemy One to One relationship pattern definition ...
One To One is essentially a bidirectional relationship with a scalar
attribute on both sides. To achieve this, the uselist flag indicates
the placement of a scalar attribute instead of a collection on the
“many” side of the relationship
In your case you've disabled "uselist" for Gifts by giving "uselist=False" (Actually by default it gets disabled). But you didn't disabled "uselist" for Persons. because of this Person can still hold list of Gifts (One person - Many Gifts).
You can get one to one mapping in two ways here..
1: By using backref function which provides arguments for the reverse side. keep your person as it is.
class Gift(db.Model):
__tablename__ = 'gift'
id = db.Column(db.Integer, primary_key=True)
person_id = db.Column(db.Integer, db.ForeignKey('person.id'))
person = relationship("Person", backref=backref("gift", uselist=False))
gift_idea = db.Column(db.String, nullable=False)
2: Adding scalar attribute on both the sides instead of using backref property.
class Gift(db.Model):
__tablename__ = 'gift'
id = db.Column(db.Integer, primary_key=True)
person_id = db.Column(db.Integer, db.ForeignKey('person.id'))
gift_idea = db.Column(db.String, nullable=False)
person = relationship("Person")
# -----------------------------------------------
class Person(db.Model):
__tablename__ = 'person'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
address = db.Column(db.String, nullable=False)
gift = relationship("Gift", uselist=False)
Solution for Question-2:
#val: Gave you straightforward solution for this.. you could also try using lazy loading instead of clearly writing everything over orm query.
list_of_gift_object = session.query(Gift).join(Person).all()
may be you can iterate over the list and use Gift person attribute to get associated person data.
for gift in list_of_gift_object:
print gift.gift_idea, gift.person.name, gift.person.address
In order to define proper 1-to-[0..1] relationship from the child side, you must make sure that the backref has uselist=False set, while the uselist from child to parent will be False by default:
person = db.relationship(
"Person",
backref=db.backref("gift", uselist=False),
)
Then the query is very straightforward to do:
a = (session
.query(
Gift.gift_idea,
Person.name.label("person_name"),
Person.address.label("person_address"),
)
# .select_from(Gift) # #note: optional, but will need it if the first column in the `.query(...)` above will be from table `Person`
.join(Person)
)