So I wrote a referral system using FlaskSQLAlchemy, and when I test it, it sometimes works, and sometimes doesn't and I find it very confusing, any help would be appreciated.
Here's the models:
class User(TimestampMixin, UserMixin, db.Model):
# __tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
role_id = db.Column(db.Integer, db.ForeignKey('role.id'))
public_id = db.Column(db.String(MaxLengths.public_id))
email = db.Column(db.String(MaxLengths.email), unique=True, nullable=False)
password = db.Column(db.String(MaxLengths.password))
reset_password_code = db.Column(db.String(MaxLengths.reset_password_code))
attempt_reset_password = db.Column(db.Boolean(), default=False)
is_email_verified = db.Column(db.Boolean(), default=False)
is_age_verified = db.Column(db.Boolean(), default=False)
is_newsletter_registered = db.Column(db.Boolean(), default=True)
balance = db.Column(db.Float(), default=0.0)
lifetime_balance = db.Column(db.Float(), default=0.0)
reviews = db.relationship('Review', backref='user')
ref_code = db.Column(db.String(MaxLengths.ref_code))
# NOTE: Ref relationships here
referred_by_user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
referred_by_user = db.relationship(
'User', foreign_keys=[referred_by_user_id], uselist=False
)
referred_to_user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
referred_to_users = db.relationship(
'User', foreign_keys=[referred_to_user_id], post_update=True)
discount_code_used_by_id = db.Column(
db.Integer, db.ForeignKey('discount_code.id'))
profile_pic = db.Column(db.Boolean(), default=False)
id_verification_pics = db.Column(db.JSON(), default=[])
orders = db.relationship('Orders', backref='user')
login_attempts = db.relationship('LoginAttempt', backref='user')
last_seen = db.Column(ArrowType, default=arrow.utcnow())
# TODO: Update this upon placing an order
total_spent = db.Column(db.Float(), default=0.0)
billing_address_id = db.Column(db.Integer, db.ForeignKey('address.id'))
shipping_address_id = db.Column(db.Integer, db.ForeignKey('address.id'))
billing_address = db.relationship(
'Address', foreign_keys=[billing_address_id], uselist=False)
shipping_address = db.relationship(
'Address', foreign_keys=[shipping_address_id], uselist=False)
review_votes = db.relationship('ReviewVote', backref='user')
consumer_id = db.Column(db.String(MaxLengths.consumer_id))
class Orders(TimestampMixin, db.Model):
'''
Orders database model and custom order related functions
'''
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
status = db.Column(db.String(MaxLengths.order_status),
default="Order Pending")
dest_address_id = db.Column(db.Integer, db.ForeignKey('address.id'))
from_address_id = db.Column(db.Integer, db.ForeignKey('address.id'))
dest_address = db.relationship('Address', foreign_keys=[
dest_address_id], uselist=False)
from_address = db.relationship('Address', foreign_keys=[
from_address_id], uselist=False)
products = db.relationship('Product', backref='orders')
subtotal = db.Column(db.Float())
shipping_fee = db.Column(db.Float(), nullable=False)
tax_cost = db.Column(db.Float())
tax_percent = db.Column(db.Float(), nullable=False)
total = db.Column(db.Float())
discount_code = db.relationship(
'DiscountCode', uselist=False)
discount_code_amount = db.Column(db.Float)
user_balance_applied = db.Column(db.Float)
currency = db.Column(db.String(MaxLengths.currency), default="CAD")
date = db.Column(ArrowType, default=arrow.utcnow()
) # datetime.datetime.now()
# TODO: This should exist in the interac_payment obj/json
is_payment_recieved = db.Column(
db.Boolean(), nullable=False, default=False)
interac_payment = db.Column(db.JSON(), default={})
order_number = db.Column(db.String(MaxLengths.order_number), default='')
tracking_number = db.Column(db.String(MaxLengths.tracking_number))
tracking_info = db.Column(db.JSON)
ref_record = db.relationship(
'ReferralRecord', backref='order', uselist=False)
def apply_ref_bonus(self, rate=1.5):
if self.is_payment_recieved:
# user = self.get_user()
if not self.user:
print("No user found for this order")
if not self.user.referred_by_user:
print('This user wasn\'t referred by anyone')
print(self.user.referred_by_user)
if self.user and self.user.referred_by_user:
cashback = (self.subtotal * (rate / 100))
record = ReferralRecord(
referred_by_user_email=self.user.referred_by_user.email,
referred_to_user_email=self.user.email,
cashback_amount=cashback)
self.user.referred_by_user.balance += cashback
self.user.referred_by_user.lifetime_balance += cashback
self.ref_record = record
db.session.add(record)
db.session.add(self.user.referred_by_user)
db.session.add(self.user)
db.session.commit()
class DiscountCode(TimestampMixin, db.Model):
'''
:param id: int primary_key
:param code: str discount code
:param amount: float numeric representation of discount
:param is_percent_off: bool amount is treated as a % discount if True
:param minimum_subtotal: float minimum order subtotal for discount code to be valid for a given order
:param expiration_date: datetime() the date a code is no longer valid
:param uses: int the number of times the code has been used
:param max_uses: int max number of times a code can be used before becoming invalid
:param used_by: a list of User objects who have used this code (for analytical purposes)
'''
id = db.Column(db.Integer(), primary_key=True)
code = db.Column(db.String(MaxLengths.discount_code),
unique=True, nullable=False)
amount = db.Column(db.Float(), nullable=False)
is_percent_off = db.Column(db.Boolean(), default=False)
minimum_subtotal = db.Column(db.Float(), default=0.0)
# TODO: add default via arrow.shift()
start_date = db.Column(ArrowType)
expiration_date = db.Column(ArrowType)
uses = db.Column(db.Integer(), default=0)
max_uses = db.Column(db.Integer(), default=0)
order_id = db.Column(db.Integer, db.ForeignKey('orders.id'))
And here's the test case:
def referr(ref_by_user, ref_to_user):
ref_by_user.referred_to_users.append(ref_to_user)
ref_to_user.referred_by_user = ref_by_user
db.session.add(ref_by_user)
db.session.add(ref_to_user)
db.session.commit()
#app.cli.command('test-ref')
def testref():
db.drop_all()
db.create_all()
user1 = User(email='test1#example.com')
user2 = User(email='test2#example.com')
user3 = User(email='test3#example.com')
order = Orders(shipping_fee=4.00, tax_percent=0.5, subtotal=15.00, total=18.50, is_payment_recieved=True, interac_payment={})
db.session.add(user1)
db.session.add(user2)
db.session.add(user3)
db.session.add(order)
db.session.commit()
# user1.referr(user2)
# user1.referr(user3)
# referr(user1, user2)
# referr(user1, user3)
user1.referred_to_users.append(user2)
user1.referred_to_users.append(user3)
user2.referred_by_user = user1
user3.referred_by_user = user1
print("This should be == 0 - ", user1.balance)
user2.orders.append(order)
# order.user = user2
db.session.add(user1)
db.session.add(user2)
db.session.add(user3)
db.session.add(order)
db.session.commit()
# user2.apply_ref_bonus_from_order(order)
order.apply_ref_bonus()
db.session.commit()
print("This should be > 0 - ", user1.balance)
print(user1.referred_to_users)
However sometimes I get this output:
This should be == 0 - 0.0
This should be > 0 - 0.22499999999999998
[<User 'test2#example.com'>, <User 'test3#example.com'>]
and other times I get this output:
This should be == 0 - 0.0
This user wasn't referred by anyone
None
This should be > 0 - 0.0
[<User 'test2#example.com'>, <User 'test3#example.com'>]
I get different output with the same code, although whenever I try to use a referr() wrapper from either the User model or as a standalone function, I ALWAYS get the latter output. Inconsistent output happens when I don't referr a user via a function & I'm honestly really confused about the whole thing
I wasn't specifying a remote_side param
# NOTE: Ref relationships here
referred_by_user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
referred_by_user = db.relationship(
'User', foreign_keys=[referred_by_user_id], uselist=False,
remote_side=[id] # This Solved the Issue
)
referred_to_user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
referred_to_users = db.relationship(
'User', foreign_keys=[referred_to_user_id], post_update=True)
I am asking fask_sqlalchemy to create table for me. But I am getting a NoReferencedTable Error.
Here is the code for Models:
from db import db
class Artist(db.Model):
__tablename__ = 'artists'
ArtistId = db.Column(db.Integer, primary_key=True)
Name = db.Column(db.NVARCHAR(120))
class Album(db.Model):
___tablename__ = 'albums'
AlbumId = db.Column(db.Integer, primary_key=True)
Title = db.Column(db.NVARCHAR(160), nullable=False)
ArtistId = db.Column(db.ForeignKey('artists.ArtistId'), nullable=False, index=True)
artist = db.relationship('Artist')
class Employee(db.Model):
__tablename__ = 'employees'
EmployeeId = db.Column(db.Integer, primary_key=True)
LastName = db.Column(db.NVARCHAR(20), nullable=False)
FirstName = db.Column(db.NVARCHAR(20), nullable=False)
Title = db.Column(db.NVARCHAR(30))
ReportsTo = db.Column(db.ForeignKey('employees.EmployeeId'), index=True)
BirthDate = db.Column(db.DateTime)
HireDate = db.Column(db.DateTime)
Address = db.Column(db.NVARCHAR(70))
City = db.Column(db.NVARCHAR(40))
State = db.Column(db.NVARCHAR(40))
Country = db.Column(db.NVARCHAR(40))
PostalCode = db.Column(db.NVARCHAR(10))
Phone = db.Column(db.NVARCHAR(24))
Fax = db.Column(db.NVARCHAR(24))
Email = db.Column(db.NVARCHAR(60))
parent = db.relationship('Employee', remote_side=[EmployeeId])
class Genre(db.Model):
__tablename__ = 'genres'
GenreId = db.Column(db.Integer, primary_key=True)
Name = db.Column(db.NVARCHAR(120))
class MediaType(db.Model):
__tablename__ = 'media_types'
MediaTypeId = db.Column(db.Integer, primary_key=True)
Name = db.Column(db.NVARCHAR(120))
class Playlist(db.Model):
__tablename__ = 'playlists'
PlaylistId = db.Column(db.Integer, primary_key=True)
Name = db.Column(db.NVARCHAR(120))
tracks = db.relationship('Track', secondary='playlist_track')
class Customer(db.Model):
__tablename__ = 'customers'
CustomerId = db.Column(db.Integer, primary_key=True)
FirstName = db.Column(db.NVARCHAR(40), nullable=False)
LastName = db.Column(db.NVARCHAR(20), nullable=False)
Company = db.Column(db.NVARCHAR(80))
Address = db.Column(db.NVARCHAR(70))
City = db.Column(db.NVARCHAR(40))
State = db.Column(db.NVARCHAR(40))
Country = db.Column(db.NVARCHAR(40))
PostalCode = db.Column(db.NVARCHAR(10))
Phone = db.Column(db.NVARCHAR(24))
Fax = db.Column(db.NVARCHAR(24))
Email = db.Column(db.NVARCHAR(60), nullable=False)
SupportRepId = db.Column(db.ForeignKey('employees.EmployeeId'), index=True)
employee = db.relationship('Employee')
class Invoice(db.Model):
__tablename__ = 'invoices'
InvoiceId = db.Column(db.Integer, primary_key=True)
CustomerId = db.Column(db.ForeignKey('customers.CustomerId'), nullable=False, index=True)
InvoiceDate = db.Column(db.DateTime, nullable=False)
BillingAddress = db.Column(db.NVARCHAR(70))
BillingCity = db.Column(db.NVARCHAR(40))
BillingState = db.Column(db.NVARCHAR(40))
BillingCountry = db.Column(db.NVARCHAR(40))
BillingPostalCode = db.Column(db.NVARCHAR(10))
Total = db.Column(db.Numeric(10, 2), nullable=False)
customer = db.relationship('Customer')
class Track(db.Model):
__tablename__ = 'tracks'
TrackId = db.Column(db.Integer, primary_key=True)
Name = db.Column(db.NVARCHAR(200), nullable=False)
AlbumId = db.Column(db.ForeignKey('albums.AlbumId'), index=True)
MediaTypeId = db.Column(db.ForeignKey('media_types.MediaTypeId'), nullable=False, index=True)
GenreId = db.Column(db.ForeignKey('genres.GenreId'), index=True)
Composer = db.Column(db.NVARCHAR(220))
Milliseconds = db.Column(db.Integer, nullable=False)
Bytes = db.Column(db.Integer)
UnitPrice = db.Column(db.Numeric(10, 2), nullable=False)
album = db.relationship('Album')
genre = db.relationship('Genre')
media_type = db.relationship('MediaType')
class InvoiceItem(db.Model):
__tablename__ = 'invoice_items'
InvoiceLineId = db.Column(db.Integer, primary_key=True)
InvoiceId = db.Column(db.ForeignKey('invoices.InvoiceId'), nullable=False, index=True)
TrackId = db.Column(db.ForeignKey('tracks.TrackId'), nullable=False, index=True)
UnitPrice = db.Column(db.Numeric(10, 2), nullable=False)
Quantity = db.Column(db.Integer, nullable=False)
invoice = db.relationship('Invoice')
track = db.relationship('Track')
The errror that I get is
sqlalchemy.exc.NoReferencedTableError
sqlalchemy.exc.NoReferencedTableError: Foreign key associated with
column 'tracks.AlbumId' could not find table 'albums' with which to
generate a foreign key to target column 'AlbumId'
The albums table is there and it has AlbumId column as well.
I don't understand this error.
Need some help understanding what is causing this error.
i think you made a mistake in your Album class. You can't have
db.relationship(...)
and
db.Column(db.ForeignKey(...))
in the same class.
I think, your Artist and Album classes should look like this:
class Artist(db.Model):
__tablename__ = 'artists'
ArtistId = db.Column(db.Integer, primary_key=True)
Name = db.Column(db.NVARCHAR(120))
Albums = db.relationship('Album', backref="artist"))
# Instead of 'Albums', you can name it whatever you want.
# It's just a way to access albums from an artist.
class Album(db.Model):
___tablename__ = 'albums'
AlbumId = db.Column(db.Integer, primary_key=True)
Title = db.Column(db.NVARCHAR(160), nullable=False)
ArtistId = db.Column(db.ForeignKey('artists.ArtistId'), nullable=False, index=True)
Thanks to this, you will be able to access albums from an artist within the Artiste class and vice-versa.
Thereby, if you want to access albums from an artist, you can do something like this:
artist = Artist(...)
artist.Albums
# It will automatically return albums from this artist
and vice-versa.
I suggest you to watch these two videos :
https://www.youtube.com/watch?v=juPQ04_twtA (One-to-Many relationship)
https://www.youtube.com/watch?v=OvhoYbjtiKc (Many-to-Many relationship)
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.
I have the following badge (achievement) system database structure:
class Base(db.Model):
__abstract__ = True
id = db.Column(db.Integer, primary_key=True)
date_created = db.Column(db.DateTime, default=db.func.current_timestamp())
date_modified = db.Column(db.DateTime, default=db.func.current_timestamp(),
onupdate=db.func.current_timestamp())
class User(UserMixin, Base):
__tablename__ = 'users'
username = db.Column(db.String(20), nullable=False, unique=True)
email = db.Column(db.String(50), nullable=False, unique=True)
password = db.Column(db.String(200), nullable=False)
name = db.Column(db.String(30), nullable=False)
badges = db.relationship('UserBadge', backref='ubadge',
lazy='dynamic')
class Badge(Base):
__tablename__ = 'badges'
name = db.Column(db.String(35), unique=True)
description = db.Column(db.String(300))
imgfile = db.Column(db.String(80))
badges = db.relationship('UserBadge', backref='badge',
lazy='dynamic')
def __repr__(self):
return '<Achievement: {} - {}>'.format(self.name, self.description)
class UserBadge(Base):
__tablename__ = 'userbadges'
user_id = db.Column(db.Integer(), db.ForeignKey('users.id'))
badge_id = db.Column(db.Integer(), db.ForeignKey('badges.id'))
def __repr__(self):
return '<Achievement: {} - {}>'.format(self.user_id, self.badge_id)
So i can return all the badges by a specific user, using:
ubadges = UserBadge.query.filter_by(user_id=user.id).all()
It returns:
[<Achievement: 1 - 1>]
But instead of 1 (user_id) and 1 (badge_id) i want to show the users.name and badges.name. How can i access those attributes?
In your UserBadge class, just use:
def __repr__(self):
return '<Achievement: {} - {}>'.format(self.ubadge.name, self.badge.name)
It has both properties because you set them up using backref in the other classes.
PS: You might need to change the User backref to user, and then use self.user.name int he function above