FlaskSqlAlchemy multi join on multi table. ambiguous column name - python

I'm about a month into learning and working with SQLAlchemy. I've been working on troubleshooting this for several hours using previous posts on StackOverflow and reading SQLAlchemy docs. Even watched videos on Youtube.
Can anybody help me with creating a proper query? I think I need to alias the column name, but I haven't got that to work yet.
Thank you in advance
cdm_mapping = db.Table('cdm_mapping',
db.Column('product_id', db.ForeignKey('product.id'), primary_key=True),
db.Column('cdm_id', db.ForeignKey('CDM.id'), primary_key=True)
)
user_products = db.Table('user_products',
db.Column('user_id', db.ForeignKey('user.id'), primary_key=True),
db.Column('product_id', db.ForeignKey('product.id'), primary_key=True)
)
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
created_on = db.Column(db.DateTime, default=datetime.utcnow)
updated_on = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
username = db.Column(db.String(20), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
website = db.Column(db.String(120), nullable=True)
company = db.Column(db.String(120), nullable=True)
title = db.Column(db.String(60), nullable=True)
image_file = db.Column(db.String(25), nullable=False, default='default.jpg')
password = db.Column(db.String(60), nullable=False)
role = db.Column(db.String(60), nullable=True)
permission = db.Column(db.String(60), nullable=True)
status = db.Column(db.String(60), nullable=True)
products = db.relationship("Product", secondary=user_products, back_populates="users")
class Product(db.Model):
id = db.Column(db.Integer, primary_key=True)
created_on = db.Column(db.DateTime, default=datetime.utcnow)
updated_on = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
product_name = db.Column(db.String(120), unique=True, nullable=False)
product_shortname = db.Column(db.String(25), nullable=True)
homepage = db.Column(db.String(200), nullable=True)
rating = db.Column(db.Float, nullable=True)
review_count = db.Column(db.Integer, nullable=True)
github = db.Column(db.String(120), nullable=True)
description = db.Column(db.String(15000), nullable=True)
logo_file = db.Column(db.String(120), nullable=False, default='default.jpg')
# Relationships
categories = db.relationship("Category", secondary=product_category, back_populates="products")
features = db.relationship("Feature", secondary=product_feature, back_populates="products")
cdm_elements = db.relationship("CDM", secondary=cdm_mapping, back_populates="products")
vendor_id = db.Column(db.Integer, db.ForeignKey('vendor.id'), nullable=True)
users = db.relationship("User", secondary=user_products, back_populates="products")
class CDM(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(30), unique=True, nullable=False)
description = db.Column(db.String(15000), nullable=True)
# Relationship
products = db.relationship("Product", secondary=cdm_mapping, back_populates="cdm_elements")
This is the query I constructed dozens of different ways.
user_id = 1
def get_toolkit_in_cdm(user_id):
query = db.session.query(CDM.name, Product.product_name, User.username).join(CDM.products).join(User.products).filter(User.id == user_id).all()
for product, cdm, user in query:
print(product, cdm, user)
I get this error.
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) ambiguous column name: product.product_name

Related

relationship for flask_sqlalchemy python3.9+

I can't find a way to get all messages from the user via user.messages and the same via channel.messages. I keep getting an error with foreignkeys. What do I need to change to my relationships to make it work?
I would like to be able to:
user.messages and channel.messages
and as an extra (if at all possible):
message.user.username or message.user.id
my classes:
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
password = db.Column(db.String, nullable=False)
email = db.Column(db.String, unique=True, nullable=False)
admin = db.Column(db.Boolean, default=False)
mc_access = db.Column(db.Boolean, default=False)
pass_recov_code = db.Column(db.String, default=None)
all_messages = db.relationship("Message",
backref=db.backref("Message"),
primaryjoin="foreign(User.id) == remote(Message.sender_id)",
foreign_keys="[User.id, User.username]",
cascade="all, delete"
)
class Channel(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(20), unique=True, nullable=False)
uses_password = db.Column(db.Boolean, default=False)
password = db.Column(db.String)
online_users = db.Column(db.String, default=json.dumps([]))
messages = db.relationship("Message", cascade="all, delete", foreign_keys="[Channel.id]", backref=db.backref("channel"))
maker_id = db.Column(db.Integer, nullable=False)
class Message(db.Model):
id = db.Column(db.Integer, primary_key=True)
sender_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
sender_username = db.Column(db.String, db.ForeignKey("user.username"), nullable=False)
channel_id = db.Column(db.Integer, db.ForeignKey("channel.id"), nullable=False)
message = db.Column(db.String, default="", nullable=False)
send_on = db.Column(db.String, nullable=False)
edited = db.Column(db.Boolean, default=False)
Simply do the following:
class User(db.Model, UserMixin):
...
messages = db.relationship("Message", back_populates="user")
class Channel(db.Model):
...
messages = db.relationship("Message", back_populates="channel")
class Message(db.Model):
...
sender_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
channel_id = db.Column(db.Integer, db.ForeignKey("channel.id"), nullable=False)
user = db.relationship("User", back_populates="messages")
channel = db.relationship("Channel", back_populates="messages")
This way you can use user.messages, channel.messages, message.user.username and message.user.id.
Note: you can also use backref instead of back_populates, but I recommend using back_populates as it is more explicit.

Flask - relationship with two tables

I'm trying to create a one to many relationship between two tables and I am having some issues with that.
Tables code
class Movies(db.Model):
__tablename__ = 'movies'
id = db.Column(db.String(300), primary_key=True)
cat_id = db.relationship('Category', backref='movie')
title = db.Column(db.String(250), nullable=False)
link = db.Column(db.String(250), nullable=False)
duration = db.Column(db.String(250), nullable=False)
thumb_large = db.Column(db.String(250), nullable=False)
thumb = db.Column(db.String(250), nullable=False)
embed = db.Column(db.String(250), nullable=False)
tags = db.Column(db.String(250), nullable=False)
published = db.Column(db.String(250), nullable=False)
class Category(db.Model):
__tablename__ = 'category'
id = db.Column(db.String(300), db.ForeignKey('movies.cat_id'), primary_key=True)
category = db.Column(db.String(30), nullable=False)
I'm trying to migrate it but it give me this error sqlalchemy.exc.NoReferencedColumnError: Could not initialize target column for ForeignKey 'movies.cat_id' on table 'category': table 'movies' has no column named 'cat_id'
I can't understand why this is happening if I already set the relationship.
Any idea what should I do?
I think that should fix it.
In my opinion the documentation of SQLAlchemy regarding relationships is very helpful. You can find this here.
class Movie(Model):
__tablename__ = 'movies'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(255), nullable=False, unique=True, index=True)
link = db.Column(db.String(255), nullable=False)
duration = db.Column(db.String(255), nullable=False)
thumb_large = db.Column(db.String(255), nullable=False)
thumb = db.Column(db.String(255), nullable=False)
embed = db.Column(db.String(255), nullable=False)
tags = db.Column(db.String(255), nullable=False)
published = db.Column(db.String(255), nullable=False)
cat_id = db.Column(db.Integer, ForeignKey('categories.id'))
category = db.relationship('Category', backref='movies')
class Category(Model):
__tablename__ = 'categories'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(31), nullable=False, unique=True, index=True)
Here is my clinic relationship between the patient and patient-detail, this might help you, and it does work for me.
class Patient(db.Model, UserMixin):
__bind_key__ = 'patient'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(10), nullable=False)
number = db.Column(db.String(11), unique=False, nullable=False)
gender = db.Column(db.String(10), nullable=False)
birth = db.Column(db.String(20), nullable=False)
IDcard = db.Column(db.String(12), nullable=False)
age = db.Column(db.String(3), unique=False, nullable=False)
create = db.Column(db.DateTime, nullable=False, default=datetime.now)
details = db.relationship('Detail', backref='patient', lazy=True)
class Detail(db.Model, UserMixin):
__bind_key__ = 'detail'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(20), unique=False, nullable=False)
Symptom = db.Column(db.String(100), nullable=False)
Initial_diagnosis = db.Column(db.String(50), nullable=False)
Preliminary_treatment_plan = db.Column(db.String(100), nullable=False)
Check_result = db.Column(db.String(50), nullable=False)
Patient_reason = db.Column(db.String(100), unique=False, nullable=False)
Doctor_name = db.Column(db.String(20), unique=False, nullable=False)
Date_of_diagnosis = db.Column(db.DateTime, nullable=False, default=datetime.now)
detail = db.Column(db.Integer, db.ForeignKey('patient.id'), nullable=False)
date_add = db.Column(db.DateTime, nullable=False, default=datetime.now)

How to attach User model with two different Models so that the properties of those models can be accessed via current_user

I am building a online quiz app where both teacher and students can login. Teachers can create quizzes and students can run those quizzes. How can I attach both Teacher and Student model with User model so that properties like Teacher_Name or Student_Class etc can be accessed via current_user?
This is not a problem regarding user role. Because using flask-security I can do that pretty easily but providing role means grouping users by their access level but not actually identifying them. I mean if a student logs in I need know who is this particular student represented by the current logged in user. Only then I can store his/her result with his/her record.
Followings are my sqlalchemy models...
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(120), unique=True, nullable=False)
password = db.Column(db.String(60), nullable=False)
class Student(db.Model):
__tablename__ = 'student'
id = db.Column(db.Integer, primary_key=True)
cls = db.Column(db.String(4), nullable=False, default='V')
sec = db.Column(db.String(1), nullable=False, default='A')
roll = db.Column(db.Integer, nullable=False, default=1)
name = db.Column(db.String(24), nullable=False)
dob = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
sex = db.Column(db.Enum(Gender), nullable=False, default=Gender.male)
results = db.relationship('Result', backref='student', lazy=True)
__table_args__ = (db.UniqueConstraint("cls", "roll", name="cls_roll"),)
class Teacher(db.Model):
__tablename__ = 'teacher'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(24), nullable=False)
subject = db.Column(db.String(4), nullable=False)
quiz_ques = db.Table('quiz_ques', db.Column('quiz_id', db.Integer, db.ForeignKey('quiz.id'), nullable=False),
db.Column('mcq_id', db.Integer, db.ForeignKey('mcq.id'), nullable=False),
db.PrimaryKeyConstraint('quiz_id', 'mcq_id'))
class Quiz(db.Model):
__tablename__ = 'quiz'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(50), nullable=False, unique=True)
subject = db.Column(db.String(4), nullable=False)
marks = db.Column(db.Integer, nullable=False, default=1)
time_limit = db.Column(db.Integer, nullable=False, default=20)
questions = db.relationship('MCQ', secondary=quiz_ques, lazy='subquery', backref=db.backref('quizzes', lazy=True))
results = db.relationship('Result', backref='quiz', lazy=True)
class MCQ(db.Model):
__tablename__ = 'mcq'
id = db.Column(db.Integer, primary_key=True)
subject = db.Column(db.String(4), nullable=False)
topic = db.Column(db.String(150), nullable=False)
question = db.Column(db.String(255), nullable=False)
answers = db.Column(db.Text(), nullable=False)
class Result(db.Model):
__tablename__ = 'result'
id = db.Column(db.Integer, primary_key=True)
date_created = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
marks_obtained = db.Column(db.Integer, nullable=False, default=0)
response = db.Column(db.Text(), nullable=True)
stud_id = db.Column(db.Integer, db.ForeignKey('student.id'), nullable=False)
quiz_id = db.Column(db.Integer, db.ForeignKey('quiz.id'), nullable=False)
The code should allow me to do something like this -
current_user.student.name = 'John Doe'
or
current_user.teacher.subject = 'Math'

Order of declaring classes in SqlAlchemy

I'm sory for this newby question, but I can't figure it out for a couple days and it is driving me nuts.
I'm building a small project in order to learn flask, SqlAlchemy and Postrges.
I have major problems with declaring classes in SqlAlchemy. I have already simplified models by removing all many-to-many relationships. However, now I have new problems even with one-to many relationships, although I think I have tried all the possible options. Maybe there's a typo that I keep on overlooking, or I don't grasp something fundamental. Please let me know...
So, I have classes declared as follows in my models.py:
#login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key = True)
username = db.Column(db.String(20), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password = db.Column(db.String(60), nullable=False)
date_registered = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
role = db.Column(db.Integer, nullable=False)
role_exp_date = db.Column(db.DateTime)
#o2o
personal_datas = db.relationship('PersonalData', uselist=False, backref='user', lazy=True)
persons = db.relationship('Person', uselist=False, backref='user', lazy=True)
#o2m
posts = db.relationship('Post', backref='author', lazy=True)
comments = db.relationship('PostComment', backref='author', lazy=True)
projects_owned = db.relationship('ConstrProject', backref='owner', lazy=True)
attachments = db.relationship('Attachment', backref='author', lazy=True)
def __repr__(self):
return f"{self.username} ({self.email})"
class PersonalData(db.Model):
id = db.Column(db.Integer, primary_key = True)
date_birth = db.Column(db.DateTime)
image_file = db.Column(db.String(20), nullable=False, default='default.jpg')
interests = db.Column(db.Text)
experties = db.Column(db.Text) #Потом сделать отдельную таблицу...
#o2o
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
#o2m
class Person(db.Model):
id = db.Column(db.Integer, primary_key = True)
first_name = db.Column(db.String(30), nullable=False)
middle_name = db.Column(db.String(40), nullable=False)
last_name = db.Column(db.String(60), nullable=False)
email = db.Column(db.String(120))
license = db.Column(db.String(120))
address = db.Column(db.String(240))
telephone = db.Column(db.String(30))
#o2o
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
#o2m
signers = db.relationship('Signer', backref='person', lazy=True)
def __repr__(self):
return f"{self.last_name.Capitalize} {self.first_name[0].Upper}. {self.middle_name[0].Upper}."
class ConstrProject(db.Model):
__tablename__ = 'constrproject'
id = db.Column(db.Integer, primary_key = True)
name = db.Column(db.String(120), nullable=False, default='New Project')
full_title = db.Column(db.Text, default='New Project')
notes = db.Column(db.Text)
public = db.Column(db.Boolean, default=True) #? check expamples
date_created = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
date_last_edit = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
document_template = db.Column(db.Integer, nullable=False, default=1) #later to m2m
print_settings = db.Column(db.Integer, nullable=False, default=1) #later to m2m
address = db.Column(db.String(240))
#o2m
documents = db.relationship('Document', backref='project', lazy=True)
#m2o
owner_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) #+ #default = CurrentUser
developer_id = db.Column(db.Integer, db.ForeignKey('company.id'))
main_contractor_id = db.Column(db.Integer, db.ForeignKey('company.id'))
architect_id = db.Column(db.Integer, db.ForeignKey('company.id'))
subcontractor_id = db.Column(db.Integer, db.ForeignKey('company.id'))
other_id = db.Column(db.Integer, db.ForeignKey('company.id'))
developer = db.relationship('Company', foreign_keys=[developer_id], back_populates='constr_projects_developed')
main_contractor = db.relationship('Company', foreign_keys=[main_contractor_id], back_populates='constr_projects_main_contracts')
architect = db.relationship('Company', foreign_keys=[architect_id], back_populates='constr_projects_architect')
subcontractor = db.relationship('Company', foreign_keys=[subcontractor_id], back_populates='constr_projects_subcontracts')
other = db.relationship('Company', foreign_keys=[other_id], back_populates='constr_projects_other')
tech_control_reps_id = db.Column(db.Integer, db.ForeignKey('signer.id'), nullable=False)
main_contractor_reps_id = db.Column(db.Integer, db.ForeignKey('signer.id'), nullable=False)
architect_reps_id = db.Column(db.Integer, db.ForeignKey('signer.id'), nullable=False)
subcontractor_reps_id = db.Column(db.Integer, db.ForeignKey('signer.id'), nullable=False)
other_reps_id = db.Column(db.Integer, db.ForeignKey('signer.id'), nullable=False)
tech_control_reps = db.relationship('Signer', foreign_keys=[tech_control_reps_id], back_populates='tech_control_projects')
main_contractor_reps = db.relationship('Signer', foreign_keys=[main_contractor_reps_id], back_populates='main_contractor_projects')
architect_reps = db.relationship('Signer', foreign_keys=[architect_reps_id], back_populates='architect_projects')
subcontractor_reps = db.relationship('Signer', foreign_keys=[subcontractor_reps_id], back_populates='subcontractor_projects')
other_reps = db.relationship('Signer', foreign_keys=[other_reps_id], back_populates='others_projects')
def __repr__(self):
return f"Site: {self.name}, (id{self.id})"
class Signer(db.Model):
id = db.Column(db.Integer, primary_key = True)
decree = db.Column(db.String(120))
job_title = db.Column(db.String(120))
date_duty_start = db.Column(db.DateTime)
date_duty_end = db.Column(db.DateTime)
#o2m
person_id = db.Column(db.Integer, db.ForeignKey('person.id'), nullable=False)
company_id = db.Column(db.Integer, db.ForeignKey('company.id'), nullable=False)
#m2o
tech_control_projects = db.relationship('ConstrProject', back_populates='tech_control_reps')
main_contractor_projects = db.relationship('ConstrProject', back_populates='main_contractor_reps')
architect_projects = db.relationship('ConstrProject', back_populates='architect_reps')
subcontractor_projects = db.relationship('ConstrProject', back_populates='subcontractor_reps')
others_projects = db.relationship('ConstrProject', back_populates='other_reps')
def __repr__(self):
return f"{self.job_title} as per {self.decree}." #название компании как подтянуть
class Company(db.Model):
id = db.Column(db.Integer, primary_key = True)
name = db.Column(db.String(60))
full_title = db.Column(db.String(240))
tin = db.Column(db.Integer)
kpp = db.Column(db.Integer)
ogrn = db.Column(db.Integer)
email = db.Column(db.String(120))
address = db.Column(db.String(240))
telephone = db.Column(db.String(30))
#o2m
license_number = db.Column(db.String(40), nullable = False)
license_date_issued = db.Column(db.DateTime)
license_category = db.Column(db.String(120), default = '2nd')
license_issued_by = db.Column(db.String(120))
license_issued_by_tin = db.Column(db.Integer)
license_issued_by_kpp = db.Column(db.Integer)
license_issued_by_ogrn = db.Column(db.Integer)
signers = db.relationship('Signer', backref='company', lazy=True)
constr_projects_developed = db.relationship('ConstrProject', back_populates='developer')
constr_projects_main_contracts = db.relationship('ConstrProject', back_populates='main_contractor')
constr_projects_architect = db.relationship('ConstrProject', back_populates='architect')
constr_projects_subcontracts = db.relationship('ConstrProject', back_populates='subcontractor')
constr_projects_other = db.relationship('ConstrProject', back_populates='other')
def __repr__(self):
return f"{self.name}"
class Post(db.Model):
id = db.Column(db.Integer, primary_key = True)
title = db.Column(db.String(100), nullable=False)
date_posted = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
content = db.Column(db.Text, nullable=False)
#o2m
comments = db.relationship('PostComment', backref='Post', lazy=True)
#m2o
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
def __repr__(self):
return f"Post('{self.title}', '{self.date_posted}')"
class PostComment(db.Model):
id = db.Column(db.Integer, primary_key = True)
date_posted = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
content = db.Column(db.Text, nullable=False)
#m2o
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
post_id = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=False)
def __repr__(self):
return f"Comment('{self.id}', '{self.date_posted}')"
class Document(db.Model):
id = db.Column(db.Integer, primary_key = True)
type = db.Column(db.String(60), nullable=False, default='АОСР')
date_last_edit = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
notes = db.Column(db.Text)
public = db.Column(db.Boolean, default=True)
number = db.Column(db.String(20), nullable=False)
date = db.Column(db.DateTime, default=datetime.utcnow)
job_name = db.Column(db.Text) #? обязательный? на каком этапе делать проверку?
job_place = db.Column(db.String(200))
date_job_start = db.Column(db.DateTime, default=datetime.utcnow)
date_job_end = db.Column(db.DateTime, default=datetime.utcnow)
regulations = db.Column(db.Text)
next_job_allowed = db.Column(db.String(240))
attachments_user_defined = db.Column(db.Text)
#o2m
attachments = db.relationship('Attachment', backref='document', lazy=True)
#m2o
project_id = db.Column(db.Integer, db.ForeignKey('constrproject.id'), nullable=False)
author_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
#m2m
arch_docs = db.Column(db.Text)
building_materials = db.Column(db.Text)
work_drawings = db.Column(db.Text)
def __repr__(self):
return f"АОСР ('{self.number}', '{self.job_name} {self.job_place}', '{self.project}' )"
class Attachment(db.Model):
id = db.Column(db.Integer, primary_key = True)
type_of_document = db.Column(db.String(60), nullable=False, default="QAC")
number = db.Column(db.String(50), nullable=False)
date = db.Column(db.DateTime)
date_valid_start = db.Column(db.DateTime)
date_valid_end = db.Column(db.DateTime)
contents = db.Column(db.Text)
type_of_file = db.Column(db.String(10), nullable=False, default = 'jpg')
image_file = db.Column(db.String(20), nullable=False)
#m2o
author_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
document_id = db.Column(db.Integer, db.ForeignKey('document.id'), nullable=False)
def __repr__(self):
if self.text:
return f'{self.text}'
return f'+{self.type_of_document} id{self.id} ({self.type_of_file})'
I cannot understand why, when I try to create an instance of a "Document"
I get errors like this:
sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join
condition between parent/child tables on relationship
Signer.tech_control_projects - there are multiple foreign key paths
linking the tables. Specify the 'foreign_keys' argument, providing a
list of those columns which should be counted as containing a foreign
key reference to the parent table.
or
sqlalchemy.exc.InvalidRequestError: When initializing mapper
Mapper|ConstrProject|constrproject, expression 'tech_control_reps_id'
failed to locate a name ("name 'tech_control_reps_id' is not
defined"). If this is a class name, consider adding this
relationship() to the class
after both dependent classes have been defined.
I haven't even tried to create these classes. Adding "foreign_keys" doesn't seem to help either. all relationships declared as strings. I tried to use lambda's also without success.
Nevertheless moving the order of class declearations alters the error messages i get...
I cannot find any good examples (resources) of more complicated databases with multiple many-to-many and one-to-many relationships in Each class. Usually examples are very basic and obvious.
So I would really appreciate if you post links to such projects or tutorials.
I wanted to learn some of it myself so I created a working example with one-to-one and multiple one-to-many relationships based on your code (User, Person, Post and Comment classes). I hope it will be a good (simple but not trivial) example for you.
'''SQLAlchemy one-to-one and one-to-many SSCCE'''
import sqlalchemy
import sqlalchemy.ext.declarative
from passlib.hash import pbkdf2_sha256
Base = sqlalchemy.ext.declarative.declarative_base()
class User(Base):
__tablename__ = 'users'
user_id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
username = sqlalchemy.Column(sqlalchemy.String(20), unique=True, nullable=False)
email = sqlalchemy.Column(sqlalchemy.String(120), unique=True, nullable=False)
password = sqlalchemy.Column(sqlalchemy.String(100), nullable=False)
registered = sqlalchemy.Column(
sqlalchemy.DateTime(timezone=True),
nullable=False,
server_default=sqlalchemy.func.now()
)
#o2o
person = sqlalchemy.orm.relationship(
'Person',
uselist=False,
back_populates='user',
lazy='joined'
)
#o2m
posts = sqlalchemy.orm.relationship('Post', back_populates='user')
comments = sqlalchemy.orm.relationship('Comment', back_populates='user')
def __repr__(self):
return f'{self.username} ({self.email})'
class Person(Base):
__tablename__ = 'persons'
person_id = sqlalchemy.Column(
sqlalchemy.Integer,
sqlalchemy.ForeignKey('users.user_id'),
primary_key=True
)
first_name = sqlalchemy.Column(sqlalchemy.String(30), nullable=False)
middle_name = sqlalchemy.Column(sqlalchemy.String(40), nullable=False)
last_name = sqlalchemy.Column(sqlalchemy.String(60), nullable=False)
#o2o
user = sqlalchemy.orm.relationship('User', back_populates='person', lazy='joined')
def __repr__(self):
return (
f'{self.last_name.upper()}'
f' {self.first_name[:1].upper()}.'
f' {self.middle_name[:1].upper()}.'
)
class Post(Base):
__tablename__ = 'posts'
post_id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
title = sqlalchemy.Column(sqlalchemy.String(100), nullable=False)
posted = sqlalchemy.Column(
sqlalchemy.DateTime(timezone=True),
nullable=False,
server_default=sqlalchemy.func.now()
)
content = sqlalchemy.Column(sqlalchemy.Text, nullable=False)
#o2m
comments = sqlalchemy.orm.relationship('Comment', back_populates='post')
#m2o
user_id = sqlalchemy.Column(
sqlalchemy.Integer,
sqlalchemy.ForeignKey('users.user_id'),
nullable=False
)
user = sqlalchemy.orm.relationship('User', uselist=False, back_populates='posts', lazy='joined')
def __repr__(self):
return f'Post({self.title!r}, {self.posted!r})'
class Comment(Base):
__tablename__ = 'comments'
comment_id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
posted = sqlalchemy.Column(
sqlalchemy.DateTime(timezone=True),
nullable=False,
server_default=sqlalchemy.func.now()
)
content = sqlalchemy.Column(sqlalchemy.Text, nullable=False)
#m2o
user_id = sqlalchemy.Column(
sqlalchemy.Integer,
sqlalchemy.ForeignKey('users.user_id'),
nullable=False
)
user = sqlalchemy.orm.relationship(
'User',
uselist=False,
back_populates='comments',
lazy='joined'
)
post_id = sqlalchemy.Column(
sqlalchemy.Integer,
sqlalchemy.ForeignKey('posts.post_id'),
nullable=False
)
post = sqlalchemy.orm.relationship(
'Post',
uselist=False,
back_populates='comments',
lazy='joined'
)
def __repr__(self):
return f'Comment({self.comment_id!r}, {self.posted!r})'
def main():
engine = sqlalchemy.create_engine(
'postgresql+psycopg2:///stack',
echo=True,
server_side_cursors=True,
use_batch_mode=True
)
Base.metadata.create_all(engine)
Session = sqlalchemy.orm.sessionmaker(bind=engine)
session = Session()
session.commit()
try:
the_user = session.query(User).filter(User.username == 'example').one()
except sqlalchemy.orm.exc.NoResultFound:
the_user = User(
username='example',
email='example#example.com',
password=pbkdf2_sha256.hash('correct horse battery staple')
)
the_user.person = Person(first_name='Ex', middle_name='', last_name='Ample')
session.add(the_user)
print(the_user)
print(the_user.person)
if not the_user.posts:
the_user.posts.append(Post(title='First post', content='Lorem ipsum'))
session.commit()
print(the_user.posts[0])
if not the_user.posts[0].comments:
the_user.posts[0].comments.append(Comment(content='Me too', user=the_user))
session.commit()
print(the_user.posts[0].comments[0])
if __name__ == '__main__':
main()
Several comments:
I don't think it is easy to have foreign-keys both ways for a mandatory one-to-one relationship.
I made both User and Person use the same id numbers, as they are one-to-one.
I made the PostgreSQL now() as the default value for timestamps, instead of client-side utcnow.
I made the timestamps use "timestamp with timezone" type - the "timestamp" type (without timezone) is an abomination.
"user" is a bad name for a table, as this is also a keyword in PostgreSQL, so I changed it to "users". Other tables also changed to plural form for consistency.
A secure password storage is a must.
I used back_populates consistently, as it is more explicit and works better with static code analyzers than backref.

Flask SQLalchemy order_by value joined from two Tables

Currently I cant figure out how to perform a proper join in SQLalchemy, I have two tables User and Roomeach room is submited by a user and that is why each room has a users_id as foreign key.
User has an attribute has_payed.
What I want is to query all rooms which are in a certain city (works fine) and then order these results by the User.has_payed value. It shall show first all rooms which belong to users who have has_payed == True.
What I am currently doing:
Room.query.join(Room.users_id).order_by(User.has_payed)
That are my tables:
class User(UserMixin, Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
username = Column(Text, nullable=False, unique=True)
password = Column(Text, nullable=False)
has_payed = Column(Boolean, default=False)
vorname = Column(Text, nullable=True)
nachname = Column(Text, nullable=True)
email_verified = Column(Boolean, default=False)
handynummer = Column(Text, nullable=True)
email = Column(Text, unique=True)
# foreign key
addresses = relationship('Room', back_populates="users")
class Room(Base):
__tablename__ = 'zimmer'
id = Column(Integer, primary_key=True)
art = Column(Text, nullable=False)
personen = Column(Integer, nullable=False)
preis = Column(Integer, nullable=False)
infofeld = Column(Text, nullable=False)
land = Column(Text, nullable=False)
bundesland = Column(Text, nullable=False)
stadt = Column(Text, nullable=False)
plz = Column(Text, nullable=False)
strasse = Column(Text, nullable=False)
hausnr = Column(Text, nullable=True)
zimmer_lat = Column(Float, nullable=False)
zimmer_lng = Column(Float, nullable=False)
# foreign key
users_id = Column(Integer, ForeignKey('users.id'))
users = relationship("User", back_populates="addresses")
It seems that this solved the issue, but I dont know how it knows that it has to join on Room.users_id == User.id
Room.query.join(User).order_by(desc(User.has_payed))

Categories

Resources