SQL Alchemy User <-> Customer relationship, many to one, returns wrong value - flask-sqlalchemy

I'm trying to create a many to one relationship for a database model where multiple users can exist under one customer.
My code all works within the app but recently I realized when I added a new user record, the relationship returns the wrong customer record based on the relationship.
class User(db.Model):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
customer_id = Column(Integer, ForeignKey('customer.id'),default=None)
aws_sub = Column(String(50),unique=True)
email = Column(String(120), unique=True)
status = Column(Boolean, unique=False,default=False)
access_token = Column(String(2500))
refresh_token = Column(String(2500))
id_token = Column(String(2500))
resend_code = Column(String(32))
date_created = Column(DateTime(timezone=True), server_default=func.now())
date_updated = Column(DateTime(timezone=True), onupdate=func.now())
customer = relationship("Customer", uselist=False, back_populates="users",foreign_keys=[customer_id],remote_side=customer_id)
class Customer(db.Model):
__tablename__ = 'customer'
id = Column(Integer, primary_key=True)
name = Column(String(50), unique=True)
#False is trial account
status = Column(Boolean, unique=False,default=False)
#primary_user = Column(Integer, ForeignKey('users.id'), unique=True)
date_created = Column(DateTime(timezone=True), server_default=func.now())
date_updated = Column(DateTime(timezone=True), onupdate=func.now())
setup_complete = Column(Boolean, unique=False,default=False)
setup_step = Column(Integer, default=1)
users = relationship("User", uselist=False, back_populates="customer")
tickets = relationship("Ticket", uselist=False, back_populates="customer")
api = relationship("API", backref="Customer")
I have two users in the database stored with this model and when I try to retrieve the record on 2nd user record, who does not have a customer_id set (NULL), it incorrectly retrieves the customer_id 1, which is for the first user. There is only one record in the customer table, which corresponds to the first user. The second user has no associated customer record. Shouldn't the customer relationship for the user return null or None?
I'm trying to avoid initializing the parts of the data that aren't needed until they are necessary so the logic only works if data is expected, otherwise it will implicitly fail. The reason I need users AND customers is because I want my application to eventually support multiple user accounts but initially most user accounts will be tied to one "customer" or company account.
I'm using current_user.customer is some of my views with Flask and Flask-login so I want to make sure my views handle null cases or "relationship not yet established".
Users data:
id
customer_id
aws_sub
email
status
access_token
refresh_token
id_token
resend_code
date_created
date_updated
1
1
q34tq34tq34t3q4t
jandoe#yahoo.com
True
xxx...
zzz...
yyy...
xxxx
2021-05-20 04:27:01
2021-05-30 14:37:04
2
NULL
q34tq34tq34t
jsmith#yahoo.com
True
xxx...
xxxxx...
yyy...
zzz
2021-06-04 16:50:00
2021-06-04 16:50:46
Customer table:
id
name
status
date_created
date_updated
setup_complete
setup_step
1
Contoso
False
2021-05-21 03:21:56
2021-05-25 02:16:42
True
NULL

I resolved this. I was passing back_populates on both sides of the model since this is a bi-directional many to one (User -> Customer) relationship. However it seems you can do this by only specifying relationship from one side using backref, in this case:
class Customer(db.Model):
__tablename__ = 'customer'
....
users = relationship("User", uselist=False, backref="customer")
I commented out the existing relationship I had in the many side (user model) and current_user.customer now returns null if the customer_id for that user is null. I remember seeing somewhere that back_populates was newer so I guess I assumed that it was preferred...

Related

Filter tables contain all value in another table

i have 2 tables Product and user and each table have list of permissions i need to list to the user all product such that user have all the product permissions
check the code representation:
Product Model:
class Product(TimeStampedModel, SoftDeletableModel):
title = models.CharField(max_length=255, unique=True)
permission = models.ManyToManyField(Permission, related_name="Permissions")
and this is the user model:
class User(AbstractBaseUser, SoftDeletableModel):
display_name = models.CharField(max_length=64, blank=True)
permission = models.ManyToManyField(Permission, related_name="Permissions")
lets say we have the following data:
user1 permissions A1,A2,C1,C2,C3
user2 permissions A1,A2,B1,B2,C1,C2,C3
product has permissions A1,B1,C1
user1 can not see the product "does not have B1 permission"
user2 can see the product
i tried the following:
Products.objects.filter(permission__in=user.permission.values("id"))
also tried this sql query:
Select * from products p
Inner join productspermession pp
On p.id = pp.product_id
Inner join userpermessions up on pp.permession_id = up.permession_id
where up.user_id = 1
i solved this issue by check what Products that the user can not access and exclude product_ids from the Product filtering query
Define new Model to represent the product-permission relation "i could not directly access the ManyToMany Model"
this is the new Model representation:
class ProductPermission(models.Model):
product = models.ForeignKey("Product", related_name="product_permission", on_delete=models.CASCADE)
permission = models.ForeignKey(
DisclosureCenter, related_name="product_permission", on_delete=models.CASCADE
)
class Product(TimeStampedModel, SoftDeletableModel):
title = models.CharField(max_length=255, unique=True)
permissions = models.ManyToManyField(Permission, through="ProductPermission")
and in the filter part i do the following
excluded_products = ProductPermission.objects.filter(~Q(disclosure_center__in=user.permissions.values("id"))).all()
excluded_product_ids = [excluded_product.product_id for excluded_product in excluded_products]
products = Product.filter(is_removed=False).exclude(id__in= excluded_product_ids)

Understanding odd results when paginating double outerjoins

I'm working with Flask and SQLAlchemy and I stumble on behavior I do not understand. When I build a query with .outerjoin() and .paginate() it all works well until today when I created a query with double outerjoin related from one another like on the simplified example code bellow (db is reference to SQLAlchemy). Class First has one-to-many relation with Second and Second is one-to-one with Third.
For testing purpose I have prepared three search queries. First two search_1 and search_2 works well all the time. But search_3 works only until there are two Second records related to the same First record. When there is more than one Second related to First then query returns mostly lower number of records (but not as low as when using .join instead of .outerjoin) and in some cases even higher then the number of records in First table. What's strange number of records is changing even when using different sorting order (always by columns of First model).
class First(db.Model):
__tablename__ = 'first'
id = db.Column(db.Integer, primary_key=True)
date_create = db.Column(db.DateTime, default=datetime.utcnow)
date_update = db.Column(db.DateTime)
class Second(db.Model):
__tablename__ = 'second'
id = db.Column(db.Integer, primary_key=True)
first_id = db.Column(db.Integer, db.ForeignKey('first.id'))
third_id = db.Column(db.Integer, db.ForeignKey('third.id'))
class Third(db.Model):
__tablename__ = 'third'
id = db.Column(db.Integer, primary_key=True)
date_create = db.Column(db.DateTime, default=datetime.utcnow)
# prepare base query to reuse later
outer_base = First.query \
.outerjoin(Second, Second.first_id == First.id) \
.outerjoin(Third, Third.id == Second.third_id) \
.order_by(First.id.asc())
# works well
search_1 = First.query.order_by(First.id.asc()).paginate(1, 10, False)
# works well
search_2 = outer_base.all()
# odd as hell...
search_3 = outer_base.paginate(1, 10, False)
I just want to be able to filter First records by value from Third if there is any relation created with the use of Second table. Can anyone please explain me what am I missing? Maybe the double outerjoin can be achieved differently to work with pagination?

Django query set filter

I'm trying to create a query set that filters all the cars hired by a user.
the car hire model has a foreignkey which stores the user's ID when a car is hired
My current solution is like this, where I get current users ID and then try filtering the Cars database against the user's ID.
view.py:
def view_hire(request):
current_users_id = request.user.id
car_hired = Cars_hired.objects.filter(user_id__in=current_users_id)
args =
{
'car_hired': car_hired,
}
Models
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
def __str__(self):
return str(self.user.id)
class car_hired(models.Model):
car = models.ForeignKey('Car', on_delete=models.CASCADE)
customer = models.ForeignKey('UserProfile', on_delete=models.CASCADE)
start_date = models.DateField(help_text='Date of booking')
end_time = models.DateField(help_text='Date of booking')
def __str__(self):
return str(self.id)
However, I can't seem to get this to work and I am getting the error "'int' object is not iterable"
I would like to create a query set that returns the row of data that matches the query
for example, if the current user's id is 10 I would like to get all the cars hired by the user. All the data stored in the rows where car_hirer_is == 10
This part is causing the error:
...(user_id__in=current_users_id)
When you add the __in part it makes django think that current_users_id is an iterable but current_users_id = request.user.id is a single id. To make it work change it to:
def view_hire(request):
current_users_id = request.user.id
car_hired = Cars_hired.objects.filter(customer__user_id=current_users_id)
I would also reconsider the names of the variables (current_user_id instead of current_users_id and cars_hired instead of car_hired).

SQLAlchemy query.filter returned no FROM clauses due to auto-correlation

I'm a beginner SQLAlchemy user frustrated with the extensive documentation.
I have a newsfeed that is updated when a new revision is made to some content object. content always has at least one revision. content is related to 1 or more topics through an association table. I'm given a set of topic.ids T, and would like to show the N most recent "approved" revisions belonging to a row in content that has at least one topic in T. ("approved" is just an enum attribute on revision)
Here are the models and the relevant attributes:
class Revision(Model):
__tablename__ = 'revision'
class Statuses(object): # enum
APPROVED = 'approved'
PROPOSED = 'proposed'
REJECTED = 'rejected'
values = [APPROVED, PROPOSED, REJECTED]
id = Column(Integer, primary_key=True)
data = Column(JSONB, default=[], nullable=False)
content_id = db.Column(db.Integer, db.ForeignKey('content.id'), nullable=False)
class Content(Model):
__tablename__ = 'content'
id = Column(Integer, primary_key=True)
topic_edges = relationship(
'TopicContentAssociation',
primaryjoin='Content.id == TopicContentAssociation.content_id',
backref='content',
lazy='dynamic',
cascade='all, delete-orphan'
)
revisions = relationship(
'Revision',
lazy='dynamic',
backref='content',
cascade='all, delete-orphan'
)
class TopicContentAssociation(Model):
__tablename__ = 'topic_content_association'
topic_id = Column(Integer, ForeignKey('topic.id'), primary_key=True)
content_id = Column(Integer, ForeignKey('content.id'), primary_key=True)
class Topic(Model):
__tablename__ = 'topic'
id = Column(Integer, primary_key=True)
Here's what I've got so far:
revisions = session.query(Revision).outerjoin(Content).outerjoin(Topic).filter(
~exists().where(
and_(
Topic.id.in_(T),
Revision.status == Revision.Statuses.APPROVED
) )
).order_by(Revision.ts_created.desc()).limit(N)
and this error is happening:
Select statement returned no FROM clauses due to auto-correlation; specify correlate(<tables>) to control correlation manually.:
SELECT *
FROM topic, revision
WHERE topic.id IN (:id_1, :id_2, :id_3...)
AND revision.status = :status_1
The interesting part is that if I remove the and_ operator and the second expression within it (lines 3, 5, and 6), the error seems to go away.
BONUS: :) I would also like to show only one revision per row of content. If somebody hits save a bunch of times, I don't want the feed to be cluttered.
Again, I'm very new to SQLAlchemy (and actually, relational databases), so an answer targeted to a beginner would be much appreciated!
EDIT: adding .correlate(Revision) after the .where clause fixes things, but I'm still working to figure out exactly what is going on here.
This is a very late response, but I am replying in case someone finds this topic.
Your second expression within the exist statement (Revision.status == Revision.Statuses.APPROVED) calls the table Revision for a second time in your query. The first time you call this table is by writing "session.query(Revision)"
If we "translate" your exists statement in PostgreSQL it would be:
EXISTS (SELECT 1
FROM Revision
WHERE topic.id IN (:id_1, :id_2, :id_3...)
AND revision.status = :status_1
)
Calling the same table twice (FROM Revision) in the same query is not allowed unless you use an alias. So, you can create an alias of the desired table, using the aliased() function and solve your problem. Your code should be fine like this:
from sqlalchemy.orm import aliased
aliasRev = aliased(Revision)
revisions = session.query(Revision).outerjoin(Content).outerjoin(Topic).filter(
~exists().where(
and_(
Topic.id.in_(T),
aliasRev.status == Revision.Statuses.APPROVED
) )
).order_by(Revision.ts_created.desc()).limit(N)

SQLAlchemy: select all posts that has tags in [..] (many-to-many)

I have Users, Interests and Events.
User has (many-to-many) interests. Event has (many-to-many) interests. That's why I have two "intermediate" tables: user_to_interest and event_to_interest.
I want to somehow select all events that has interests from user's interests list (in other words, all events that has tags IN [1, 144, 4324]).
In SQL I'd do that ~like this:
SELECT DISTINCT event.name FROM event JOIN event_to_interest ON event.id = event_to_interest.event_id WHERE event_to_interest.interest_id IN (10, 144, 432)
How should I do that through SQLAlchemy? (I'm using Flask-SQLAlchemy if necessary)
Assuming you have a (simplified) model like below:
user_to_interest = Table('user_to_interest', Base.metadata,
Column('id', Integer, primary_key=True),
Column('user_id', Integer, ForeignKey('user.id')),
Column('interest_id', Integer, ForeignKey('interest.id'))
)
event_to_interest = Table('event_to_interest', Base.metadata,
Column('id', Integer, primary_key=True),
Column('event_id', Integer, ForeignKey('event.id')),
Column('interest_id', Integer, ForeignKey('interest.id'))
)
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String)
class Event(Base):
__tablename__ = 'event'
id = Column(Integer, primary_key=True)
name = Column(String)
class Interest(Base):
__tablename__ = 'interest'
id = Column(Integer, primary_key=True)
name = Column(String)
users = relationship(User, secondary=user_to_interest, backref="interests")
events = relationship(Event, secondary=event_to_interest, backref="interests")
Version-1: you should be able to do simple query on list of interest_ids, which will generate basically the SQL statement you desire:
interest_ids = [10, 144, 432]
query = session.query(Event.name)
query = query.join(event_to_interest, event_to_interest.c.event_id == Event.id)
query = query.filter(event_to_interest.c.interest_id.in_(interest_ids))
However, if there are events which have two or more of the interests from the list, the query will return the same Event.name multiple times. You can work-around it by using distinct though: query = session.query(Event.name.distinct())
Version-2: Alternatively, you could do this using just relationships, which will generate different SQL using sub-query with EXISTS clause, but semantically it should be the same:
query = session.query(Event.name)
query = query.filter(Event.interests.any(Interest.id.in_(interest_ids)))
This version does not have a problem with duplicates.
However, I would go one step back, and assume that you do get interest_ids for particular user, and would create a query that works for a user_id (or User.id)
Final Version: using any twice:
def get_events_for_user(user_id):
#query = session.query(Event.name)
query = session.query(Event) # #note: I assume name is not enough
query = query.filter(Event.interests.any(Interest.users.any(User.id == user_id)))
return query.all()
One can agrue that this creates not so beautiful SQL statement, but this is exactly the beauty of using SQLAlchemy which hides the implementation details.
Bonus: you might actually want to give higher priority to the events which have more overlapping interests. In this case the below could help:
query = session.query(Event, func.count('*').label("num_interests"))
query = query.join(Interest, Event.interests)
query = query.join(User, Interest.users)
query = query.filter(User.id == user_id)
query = query.group_by(Event)
# first order by overlaping interests, then also by event.date
query = query.order_by(func.count('*').label("num_interests").desc())
#query = query.order_by(Event.date)