sqlalchemy.exc.ArgumentError while running migrations - python-3.x

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.

Related

Flask/SQLAlchemy: Create relations between multiple databases without foreign key constraints?

A StackOverflow question/answer (sqlalchemy: create relations but without foreign key constraint in db?) looked interesting, but what if you have two databases and join through a secondary table?
FKs can't map to external databases by design, making this a more realistic case if you can't load the remote table in your database.
Below is a setup where if we didn't set the __bind_key__ and the tables shared a database, it would work. However, we do want the multiple database setup. To keep things easier both databases have a schema called api where tables are stored.
Default Database
class Store(db.Model):
'''api.stores'''
schema = 'api'
__tablename__ = 'stores'
__table_args__ = { 'schema': schema }
id = db.Column(db.Integer, primary_key=True)
product = db.relationship('Product'
secondary=Inventory.__table__,
back_populates='stores'
)
Bind Database (second_db)
class Product(db.Model):
'''second_db.api.products'''
schema = 'api'
__bind_key__ = 'second_db'
__tablename__ = 'products'
__table_args__ = { 'schema': schema }
id = db.Column(db.Integer, primary_key=True)
stores = db.relationship('Store',
secondary=Inventory.__table__,
back_populates='products'
)
class Inventory(db.Model):
'''second_db.api.inventory'''
schema = 'api'
__bind_key__ = 'second_db'
__tablename__ = 'inventory'
__table_args__ = { 'schema': schema }
product_id = db.Column(db.Integer, db.ForeignKey(f'{schema}.products.id'), primary_key=True)
# below won't work with multiple DBs as a FK cannot reference external databases
store_id = db.Column(db.Integer, db.ForeignKey(f'{schema}.stores.id'), primary_key=True)
Per the accepted answer it seems primaryjoin may be used to build out the association and its answer doesn't use a secondary, so I'm not quite sure where something like this would live in the example above.
a = relationship('A',
foreign_keys=[a_id],
primaryjoin='A.a_id == C.a_id'
)
Another answer (https://stackoverflow.com/a/56683671/10408280) demonstrates how one might reference the bind database, however what is the key to the default app database/engine set by SQLALCHEMY_DATABASE_URI?
id = db.Column(db.Integer, primary_key=True)
people_id = db.Column(db.Integer, db.ForeignKey('db1.people.id'))
dogs_id = db.Column(db.Integer, db.ForeignKey('db2.dogs.id'))

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.

How to create an interface table in sqlAlchemy (relation many-to-many)

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')

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

Resources