web-dev-qa-db-fra.com

Flask-SQLAlchemy Query Join tables relationnelles

Je crée une application en utilisant Flask & SQLAlchemy. J'ai essentiellement 3 tables: utilisateurs, amitiés et bestFriends:

Un utilisateur peut avoir plusieurs amis mais un seul meilleur ami. Je veux donc que mon modèle soit relationnel. "Un à plusieurs" pour la relation entre "utilisateurs" et "amitiés" et "un à un" entre "utilisateurs" et "bestFriends".

Voici mon modèle:

from app import db
from sqlalchemy.orm import relationship, backref
from sqlalchemy import Table, Column, Integer, ForeignKey
from sqlalchemy.ext.declarative import declarative_base

class users(db.Model):

    __tablename__ = "Users"

    id = db.Column(db.Integer, primary_key=True)
    userName = db.Column(db.String, nullable=False)
    userEmail = db.Column(db.String, nullable=False)
    userPhone = db.Column(db.String, nullable=False)
    userPass = db.Column(db.String, nullable=False)

    friendsR = db.relationship('friendships', backref='friendships.friend_id', primaryjoin='users.id==friendships.user_id', lazy='joined')

    def __init__(self, userName, userEmail, userPhone, userPass):
        self.userName = userName
        self.userEmail = userEmail
        self.userPhone = userPhone
        self.userPass = userPass

    def __repr__(self):
        return '{}-{}-{}-{}'.format(self.id, self.userName, self.userEmail, self.userPhone)

class friendships(db.Model):

    __tablename__ = "Friendships"

    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, ForeignKey('Users.id'), nullable=False)
    friend_id = db.Column(db.Integer, ForeignKey('Users.id'), nullable=False)

    userR = relationship('users', foreign_keys='friendships.user_id')
    friendR = relationship('users', foreign_keys='friendships.friend_id')

    def __init__(self, user_id, friend_id):
        self.user_id = user_id
        self.friend_id = friend_id


    def __repr__(self):
        return '{}-{}-{}-{}'.format(self.user_id, self.friend_id)


class bestFriends(db.Model):

    __tablename__ = "BestFriends"

    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, ForeignKey('Users.id'), nullable=False)
    best_friend_id = db.Column(db.Integer, ForeignKey('Users.id'), nullable=False)

    user = relationship('users', foreign_keys='bestFriends.user_id')
    best_friend = relationship('users', foreign_keys='bestFriends.best_friend_id')


    def __init__(self, user_id, best_friend_id):

        self.user_id = user_id
        self.best_friend_id = best_friend_id


    def __repr__(self):
        return '{}-{}-{}-{}'.format(self.user_id, self.best_friend_id)

Je dois pouvoir interroger la liste des amis de l'utilisateur connecté ainsi que le meilleur ami de cet utilisateur en cas d'existence. Je dois également paginer le résultat:

Voici ma fonction app.py pour afficher les amis d'un utilisateur:

@app.route('/friendList<int:page>', methods=['GET', 'POST'])
@app.route('/friends')
def friendList(page=1):

if not session.get('logged_in'):
        return render_template('login.html')
    else:
        userID = session['user_id']

        userList = users.query.join(friendships).add_columns(users.id, users.userName, users.userEmail, friendships.user_id, friendships.friend_id).filter(users.id == friendships.friend_id).filter(friendships.user_id == userID).paginate(page, 1, False)

        return render_template(
            'friends.html', userList=userList)

Et ce serait le côté Jinja du code:

{% extends "layout.html" %}
{% block body %}

<div id="pagination">
{% if userList.has_prev %}
        <a href="{{ url_for('friendList', page=userList.prev_num) }}">Back</a>
{% endif %} 

{% if userList.has_next %}
        <a href="{{ url_for('friendList', page=userList.next_num) }}">Next</a>
{% endif %}
</div>

<div style="clear:both;"></div>

<div id="innerContent">
{% if userList %}
    {% for friends in userList %}
                    <div class="contentUsers">
                        {{ friends.userName }}
                    </div>

                    <br><br><br><br>

    {% endfor %}{% else %}<div>No friends</div>
{% endif %} 

</div>
  {% endblock %}

Et si je demande comme ça:

userList = db.session.query(users,friendships).filter(users.id == friendships.friend_id).filter(friendships.user_id == userID).paginate(page, 1, False)

Je reçois cette erreur:

InvalidRequestError: Could not find a FROM clause to join from. Tried joining to <class 'models.friendships'>, but got: Can't determine join between 'Users' and 'Friendships'; tables have more than one foreign key constraint relationship between them. Please specify the 'onclause' of this join explicitly.

Si je demande comme ça:

userList = users.query.join(friendships, users.id==friendships.user_id).add_columns(users.id, users.userName, users.userEmail, friendships.user_id, friendships.friend_id).filter(users.id == friendships.friend_id).filter(friendships.user_id == userID).paginate(page, 1, False)

J'obtiens l'erreur suivante:

TypeError: 'Pagination' object is not iterable

Je pense toujours que cette requête ultérieure est la bonne voie à suivre, mais je pense qu'il y a quelque chose de mal dans mes relations/clés étrangères entre les tables !!!

Si du côté Jinja j'ajoute les .items à la boucle:

{% if userList.items %}
{% for friends in userList.items %}

                <div class="contentUsers">
                    {{ friends.userName }}
                </div>

                <br><br><br><br>

{% endfor %}{% else %}<div>No friends</div>

{% fin si %}

Il ne boucle pas du tout et affiche simplement la déclaration else de "no friends"

13
Al Ex Tsm

Le message d'erreur vous indique que SQLAlchemy ne peut pas déterminer comment joindre les deux tables users et friendships, car plusieurs clés étrangères les relient. Vous devez définir explicitement la condition de jointure.

Essayer:

userList = users.query\
    .join(friendships, users.id==friendships.user_id)\
    .add_columns(users.userId, users.name, users.email, friends.userId, friendId)\
    .filter(users.id == friendships.friend_id)\
    .filter(friendships.user_id == userID)\
    .paginate(page, 1, False)
40
Matt Healy

Ok, après avoir dormi et vu la suggestion de Matthewh, j'ai presque trouvé la solution finale:

Mon modele:

from app import db
from sqlalchemy.orm import relationship, backref
from sqlalchemy import Table, Column, Integer, ForeignKey
from sqlalchemy.ext.declarative import declarative_base

class users(db.Model):
    __tablename__ = "Users"

    id = db.Column(db.Integer, primary_key=True)
    userName = db.Column(db.String, nullable=False)
    userEmail = db.Column(db.String, nullable=False)
    userPhone = db.Column(db.String, nullable=False)
    userPass = db.Column(db.String, nullable=False)

    def __init__(self, userName, userEmail, userPhone, userPass):
        self.userName = userName
        self.userEmail = userEmail
        self.userPhone = userPhone
        self.userPass = userPass

    def __repr__(self):
        return '{}-{}-{}-{}'.format(self.id, self.userName, self.userEmail, self.userPhone)


 class friendships(db.Model):
    __tablename__ = "Friendships"

    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('Users.id'), nullable=False)
    friend_id = db.Column(db.Integer, db.ForeignKey('Users.id'), nullable=False)
    userR = db.relationship('users', foreign_keys='friendships.user_id')
    friendR = db.relationship('users', foreign_keys='friendships.friend_id')

    def __init__(self, user_id, friend_id):
        self.user_id = user_id
        self.friend_id = friend_id

    def __repr__(self):
        return '{}-{}-{}-{}'.format(self.user_id, self.friend_id)


class bestFriends(db.Model):
    __tablename__ = "BestFriends"

    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('Users.id'), nullable=False)
    best_friend_id = db.Column(db.Integer, db.ForeignKey('Users.id'), nullable=False)
    user = db.relationship('users', foreign_keys='bestFriends.user_id')
    best_friend = db.relationship('users', foreign_keys='bestFriends.best_friend_id')

    def __init__(self, user_id, best_friend_id):
        self.user_id = user_id
        self.best_friend_id = best_friend_id

    def __repr__(self):
        return '{}-{}-{}-{}'.format(self.user_id, self.best_friend_id)

Ma fonction app.py (montre les amis de l'utilisateur connecté):

@app.route('/friendList<int:page>', methods=['GET', 'POST'])
@app.route('/friends')
  def friendList(page=1):
  if not session.get('logged_in'):
     return render_template('login.html')
  else:
     userID = session['user_id']
     userList = users.query.join(friendships, users.id==friendships.user_id).add_columns(users.id, users.userName, users.userEmail, friendships.id, friendships.user_id, friendships.friend_id).filter(friendships.friend_id == userID).paginate(page, 1, False)
     return render_template('friends.html', userList=userList)

Et le côté Jinja de 'friends.html':

{% extends "layout.html" %}

{% block body %}
    <div id="pagination">
        {% if userList.has_prev %}
            <a href="{{ url_for('friendList', page=userList.prev_num)}}">Back</a>
        {% endif %}

        {% if userList.has_next %}
            <a href="{{ url_for('friendList', page=userList.next_num)}}">Next</a>
        {% endif %}
    </div>

    <div style="clear:both;"></div>

    <div id="innerContent">
    {% if userList.items %}
        {% for friends in userList.items %}
            <div class="contentUsers">
                {{ friends.userName }}
            </div>
            <br><br><br><br>
        {% endfor %}
        {% else %}
            <div>No friends</div>
        {% endif %}
    </div>
{% endblock %}

Cela me donne un objet (amis dans userList.items) comme ceci:

([email protected], 2, u'Carlos', u'[email protected]', 2, 2, 1)

Je m'attendais à ceci: | users.id | users.userName | users.userEmail | users.userPhone | friends.id | friends.user_id (les amis) | friendss.friend_id (l'utilisateur connecté) |

J'ai donc les doutes/questions suivants:

Je ne comprends pas complètement la structure de l'objet résultant de la requête:

- identifiant, nom et adresse e-mail répétés - quel est le "u" devant le deuxième nom et l'adresse e-mail

Je ne comprends pas complètement la structure du modèle de relations:

  • pourquoi les éléments suivants NE sont PAS requis dans la classe "utilisateurs" du modèle de base de données:

    #friendsR = db.relationship('friendships', backref='friendships.friend_id', primaryjoin='users.id==friendships.user_id', lazy='joined')
    

Est-ce que mon modèle de base de données est correctement défini concernant le modèle de relation standard tel que publié dans cette réponse ou devrais-je en améliorer le fonctionnement ??

1
Al Ex Tsm