How to create an interface table in sqlAlchemy (relation many-to-many) - python-3.x

I'm using SqlAlchemy to interact with an existing Mariadb database. I need to access data organized in a many-to-many relationship. According to the source site documentation (sqlAlchemy), I created a multi-to-multiple relationship, but after entering the flask db migrate command, I get an error.
It should be noted that the tables of posts and categories have been created.
Documents for creating the interface table:
from sqlalchemy import Column, String, Text, Integer, Table, ForeignKey
from app import db
posts_categories = Table('posts_categories', db.metadata,
Column('post_id', Integer, ForeignKey('posts.id', ondelete='cascade')),
Column('category_id', Integer, ForeignKey('categories.id', ondelete='cascade'))
)
class Category(db.Model):
__tablename = 'categories'
id = Column(Integer, primary_key=True)
name = Column(String(128), nullable=False, unique=True)
description = Column(String(256), nullable=True, unique=False)
slug = Column(String(128), nullable=False, unique=True)
posts = db.relationship('Post', secondary=posts_categories, back_populates='categories')
class Post(db.Model):
__tablename = 'posts'
id = Column(Integer, primary_key=True)
title = Column(String(128), nullable=False, unique=True)
summary = Column(String(256), nullable=True, unique=False)
content = Column(Text, nullable=False, unique=False)
slug = Column(String(128), nullable=False, unique=True)
categories = db.relationship('Category', secondary=posts_categories, back_populates='posts')

Related

Updating data on linked models

Here's my user model,
class User(AbstractBaseUser, PermissionsMixin, Base):
user_id = models.AutoField(primary_key=True)
email = models.EmailField(db_index=True, max_length=100, unique=True)
is_advisor = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
is_active = models.BooleanField(default=False)
And here's the user profile
class UserProfile(Base):
profile_id = models.AutoField(primary_key=True)
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='user_profile')
first_name = models.CharField(null=True, blank=True, max_length=100)
last_name = models.CharField(null=True, blank=True, max_length=100)
thumbnail = models.ImageField()
Here's the routers,
router.register(r'user', UserViewSet),
router.register(r'user/profile', UserProfileViewSet, basename='UserProfile')
What will be the path to update the Profile for a specific user, say user_id 3. I'm new to django.
It depends on the lookup_field that you will set on the view UserProfileViewSet which as documented:
lookup_field - The model field that should be used to for performing object lookup of individual model instances
If you want to update it based on the related field UserProfile.user using the primary key of the other model User.user_id, then:
class UserProfileViewSet(viewsets.ModelViewSet):
queryset = UserProfile.objects.all()
serializer_class = UserProfileSerializer
lookup_field = 'user' # Or "user_id"
URL path would be:
http://127.0.0.1:8000/my_app/user/profile/123/
Where 123 is a user_id of a user.
If you want to update it based on the related field UserProfile.user but using another field on the related table e.g. User.username (just for the sake of example, let's say it is in your User model and is unique)
class UserProfileViewSet(viewsets.ModelViewSet):
queryset = UserProfile.objects.all()
serializer_class = UserProfileSerializer
lookup_field = 'user__username'
URL path would be:
http://127.0.0.1:8000/my_app/user/profile/john_lennon/
Where john_lennon is a username of a user.
For your consideration. If you want the id to be consistent between User and UserProfile in a way that user_id is just equivalent to profile_id, you might consider just making it the primary key of your UserProfile
class UserProfile(Base):
profile_id = models.OneToOneField(User, on_delete=models.CASCADE, related_name='user_profile', primary_key=True)
...
That way, your lookup_field can just be profile_id as it is just equivalent to user_id.

sqlalchemy.exc.ArgumentError while running migrations

I am facing an Argument error while trying to run migrations, but I have failed to figure out where the problem is, What am trying to implement is a relationship between two models. Below is my model class :
class TourPackages(db.Model):
__tablename__ = 'tour_package'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
description = db.Column(db.TEXT)
price = db.Column(db.Float)
destination = db.relationship('Destinations', backref='tourpackages', lazy=True)
capacity = db.Column(db.Integer)
#property
def serialize(self):
return {
'name': self.name,
'description': self.destination,
'price': self.price,
'destination': self.destination,
'capacity': self.capacity
}
class Destinations(db.Model):
__tablename__ = 'destination'
id = db.Column(db.Integer)
location = db.Column(db.String(50))
danger_type = db.Column(db.String(50))
When I run migrations with the command below:
flask db migrate -m "Initial migration."
I get this error :
sqlalchemy.exc.ArgumentError: Mapper mapped class Destinations->destination could not assemble any primary key columns for mapped table 'destination'
What am I doing wrong here
In your Destinations model, you need to change the id column to be a primary key
id = db.Column(db.Integer, primary_key=True)
and add a foreign key column for the relationship, again in Destinations:
tourpackages_id = db.Column(db.Integer, db.ForeignKey('tour_package.id'))
See the documentation.

Sqlalchemy get data from relationship

I have this models
class Post(Base):
__tablename__ = 'posts'
id = Column(Integer, primary_key=True)
content = Column(Text)
author = Column(Integer, ForeignKey('users.id'))
to_topic = Column(Integer, ForeignKey('topics.id'))
def __init__(self, content: str, author: int, to_topic: int) -> None:
self.content = content
self.author = author
self.to_topic = to_topic
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
username = Column(String(30))
email = Column(String(40))
password = Column(String(255))
registred_at = Column(DateTime, default=datetime.datetime.now)
last_logged_in = Column(DateTime)
last_login_attempt = Column(DateTime)
avatar = Column(Integer, ForeignKey('files.id'))
role = Column(String(20))
email_confirm = Column(Boolean, default=False)
children_forum = relationship('Forum')
children_topic = relationship('Topic')
children_post = relationship('Post')
And I am trying to get query that will contain Post.content, Post.author.username but how do i do this in sessions?
I tried
posts = db_session.query(Post, User).filter(
Post.to_topic == topic_id).with_entities(Post.content, Post.author.username)
but that doesnt work because author is just integer (id) so I expect that I need somehow get the author object and post object in one query, but I dont know how. In sql that would be easy just 2 queries but here I dont know how it´s done.
query_results = db_session.query(Post, User).\
join(User, Post.author == User.id).\
filter(
Post.to_topic == topic_id
).all()
I've never used with_entities, but I know this would give you a list of tuples where query_results[0] would be your Post instance, and query_results[1] would be your User instance.
EDIT: I believe you don't have to include the Post.author == User.id bit, but it's more legible if you're explicit with your joins.

Flask-SQLalchemy one-to-one relationship: associate child to already existing parent?

first time I use sqlalchemy and flask and I miss something
I'm trying to do a one-to-one relationship between two tables with Flask-SQLalchemy.
This is my first table (the parent)
class User(db.Model):
__tablename__ = 'users'
user_id = Column(Integer, primary_key=True, autoincrement=True)
user_email = Column(String(150), nullable=False)
user_password = Column(String(60), nullable=False)
child_employee = relationship('Employee', uselist=False, backref='users')
def __init__(self, user_email, user_password):
self.user_email = user_email
self.user_password = user_password
And the child
class Employee(User):
__tablename__ = 'employees'
employee_id = Column(Integer, primary_key=True, autoincrement=True)
user_id = Column(Integer, ForeignKey('users.user_id'), nullable=False)
employee_lastname = Column(String(30), nullable=False)
employee_firstname = Column(String(30), nullable=False)
employee_comment = Column(String)
def __init__(self, user_email, user_password, employee_lastname, employee_firstname, employee_comment=None):
super().__init__(user_email, user_password)
self.employee_lastname = employee_lastname
self.employee_firstname = employee_firstname
self.employee_comment = employee_comment
Know when I create an user, I have a user in my table "users" and nothing in my table "employees" : That make sense, perfect
Then if I create an employee, I have a new entry in my table "employees" and a new entry in my table "users". They are linked to each other : that make also sense, perfect
Now the question :
I created an user so there is no employee linked to it.
Now I want to create an employee and I want to link it to the already existing user, how can I do it ?
I tried
# creation of the new employee
new_employee = Employee(exisiting_user.user_email,
exisiting_user.user_password, "email", "lastname", "firstname")
#trying to associate the new employee to the already existing user
exisiting_user.child_employee = new_employee
# save the employee to the database
db.session.add(new_employee)
db.session.commit()
but I get this error
sqlalchemy.exc.IntegrityError: (psycopg2.IntegrityError) duplicate key value violates unique constraint "users_pkey"
DETAIL: Key (user_id)=(2) already exists.
[SQL: 'INSERT INTO users (user_id, user_email, user_password) VALUES (%(user_id)s, %(user_email)s, %(user_password)s)'] [parameters: {'user_id': 2, 'user_email': 'email', 'user_password': 'password'}]
Thanks
----------------First try to resolve it-------------------------
So as if I understand correctly, when I create an instance of "Employee" I also have an instance of the super class "User". Then whend I db.session.add(new_employee), SQLalchemy create an INSERT for the employee and then for the user, but the user already exist so I have the error.
I change my Employee table :
class Employee(User):
__tablename__ = 'employees'
employee_id = Column(Integer, ForeignKey(User.user_id), primary_key=True)
employee_lastname = Column(String(30), nullable=False)
employee_firstname = Column(String(30), nullable=False)
employee_comment = Column(String)
parent = relationship("User", back_populates="child_employee")
def __init__(self, employee_id, employee_lastname, employee_firstname, employee_comment=None):
self.employee_id = employee_id
self.employee_lastname = employee_lastname
self.employee_firstname = employee_firstname
self.employee_comment = employee_comment
and to add only an employee i'm doing
emp = Employee(1, "lastname", "firstname", "comment")
db.session.execute("INSERT INTO employees VALUES("emp.employee_id",'"emp.employee_lastname+"', '"+emp.employee_firstname+"', '"+emp.employee_comment+"')")
Because of the Id who is also a foreign key, the employee and the user are automatically linked
It works but I would prefer to use something more simple like db.session.add(emp), I still miss something

How can a check on foreign key be enforced before insert a new value in Flask SQLAlchemy?

I am building the following DB scheme in Flask-SQLAlchemy:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
app = Flask(__name__)
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'
db = SQLAlchemy(app)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(120), index=True, unique=True)
password_hash = db.Column(db.String(128))
posts = db.relationship('Post', backref='author', lazy='dynamic')
def __repr__(self):
return '<User {}>'.format(self.username)
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
body = db.Column(db.String(140))
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
def __repr__(self):
return '<Post {}>'.format(self.body)
db.drop_all()
db.create_all()
u = User(username='susan', email='susan#example.com')
db.session.add(u)
p = Post(body='this is my first post!', user_id=1)
db.session.add(p)
# Now I create a new post for a non-existing user and I'd expect an error...
p = Post(body='this is my second post!', user_id=2)
db.session.add(p)
db.session.commit()
As you can see I manually enter a new user and then enter two posts.
I would like that on the second post insert an error is thrown because user_id=2 is not existing in the database.
But instead the insert is successful and I also see the data into the DB:
sqlite> select * from post;
1|this is my post!|2018-07-09 16:13:16.947856|1
2|this is my post!|2018-07-09 16:13:16.948996|2
sqlite>
How can I enforce the constraint in order to make it throw an error?
Thanks for your help.
To resolve this situation, instead of assigning an id to a post object, it would be better and more ORM-like to associate a User with a Post. Also, by enforcing the NOT NULLability of the foreign key column, invalid numbers cannot be added.
For example:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
app = Flask(__name__)
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'
db = SQLAlchemy(app)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(120), index=True, unique=True)
password_hash = db.Column(db.String(128))
posts = db.relationship('Post', lazy='dynamic', back_populates='author')
def __repr__(self):
return '<User {}>'.format(self.username)
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
body = db.Column(db.String(140))
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
author_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
author = db.relationship('User')
def __repr__(self):
return '<Post {}>'.format(self.body)
db.drop_all()
db.create_all()
# Susan will be both created and added to the session
u1 = User(username='susan', email='susan#example.com')
db.session.add(u1)
# John will be created, but not added
u2 = User(username='john', email='john#example.com')
# Create a post by Susan
p1 = Post(body='this is my post!', author=u1)
# Add susan's post to the session
db.session.add(p1)
# Create a post by john, since john does not yet exist as a user, he is created automatically
p2 = Post(body='this is my post!', author=u2)
# Add john's post to the session
db.session.add(p2)
# After the session has everything defined, commit it to the database
db.session.commit()

Resources