2017-09-02 5 views
0

Je cherchais des moyens d'implémenter la clé étrangère de CONTRAINTE DE CONTRAINTE sur SUPPRIMER CASCADE dans le modèle UsersAccessMapping ci-dessous dans SQLAlchemy avec le conducteur de PyMySQL et MariaDB 10.0 avec InnoDB dans la base de données.Flask-SQLAlchemy comment faire une contrainte clé étrangère avec cascade dans MySQL (InnoDB)?

Python = 3.5.2 
SQLAlchemy = 1.1.13 
Flask-SQLAlchemy = 2.2 

SQL:

CREATE TABLE Users (
    UserID int AUTO_INCREMENT, 
    Name varchar(200) NOT NULL, 
    Email varchar(200), 
    Username varchar(200) NOT NULL, 
    Password text NOT NULL, 
    Created datetime, 
    Updated datetime, 
    PRIMARY KEY (UserID) 
); 

CREATE TABLE UsersAccessLevels (
    UsersAccessLevelID int AUTO_INCREMENT, 
    LevelName varchar(100) NOT NULL, 
    AccessDescription text, 
    PRIMARY KEY (UsersAccessLevelID) 
); 

CREATE TABLE UsersAccessMapping (
    UsersAccessMappingID int AUTO_INCREMENT, 
    UserID int NOT NULL, 
    UsersAccessLevelID int NOT NULL, 
    PRIMARY KEY (UsersAccessMappingID), 
    CONSTRAINT fk_useraccess FOREIGN KEY (UserID) 
     REFERENCES Users(UserID) ON DELETE CASCADE, 
    CONSTRAINT fk_useraccess_level FOREIGN KEY (UsersAccessLevelID) 
     REFERENCES UsersAccessLevels(UsersAccessLevelID) ON DELETE CASCADE 
); 

Ce que j'ai dans mon models.py maintenant:

from app import db 


class Users(db.Model): 
    """All users' information is stored here""" 
    __tablename__ = "Users" 
    UserID = db.Column(db.Integer(), primary_key=True) 
    Name = db.Column(db.String(200), nullable=False) 
    Email = db.Column(db.String(200)) 
    Username = db.Column(db.String(200), nullable=False) 
    Password = db.Column(db.Text, nullable=False) 
    Created = db.Column(db.DateTime) 
    Updated = db.Column(db.DateTime) 


class UsersAccessLevels(db.Model): 
    """This defines the various access levels users can have""" 
    __tablename__ = "UsersAccessLevels" 
    UsersAccessLevelID = db.Column(db.Integer, primary_key=True) 
    LevelName = db.Column(db.String(100), nullable=False) 
    AccessDescription = db.Column(db.Text) 


class UsersAccessMapping(db.Model): 
    """Each users' access level is defined here""" 
    __tablename__ = "UsersAccessMapping" 
    UsersAccessMappingID = db.Column(db.Integer, primary_key=True) 
    UserID = db.Column(db.Integer, nullable=False) 
    UsersAccessLevelID = db.Column(db.Integer, nullable=False) 
    __table_args__ = (
     db.ForeignKeyConstraint(
      ["fk_useraccess", "fk_useraccess_level"], 
      ["Users.UserID", "UsersAccessLevels.UsersAccessLevelID"], 
      ondelete="CASCADE" 
     ) 
    ) 

Il y a quelque chose de mal avec les table_args syntaxe, mais je n'ai pas été en mesure pour trouver des exemples sur comment cela devrait être. J'en ai trouvé un qui était très similaire, mais en ce que le troisième paramètre était un dict vide. Cependant, je veux utiliser le ondelete = "CASCADE". Comment cela serait-il ajouté?

Lors de l'exécution du python3 manage.py db init, il jette ceci:

File "/srv/vortech-backend/venv/lib/python3.5/site-packages/sqlalchemy/ext/declarative/base.py", line 196, in _scan_attributes 
    "__table_args__ value must be a tuple, " 
sqlalchemy.exc.ArgumentError: __table_args__ value must be a tuple, dict, or None 

J'ai essayé de changer ondelete="cascade" à un dict {"ondelete": "cascade"}, mais cela ne fonctionne pas non plus. Il donne la même erreur que ci-dessus.

Mise à jour: Le problème était que le onDelete est censé être en dehors du tuple, comme ceci:

__table_args__ = (
    db.ForeignKeyConstraint(
     ["fk_useraccess", "fk_useraccess_level"], 
     ["Users.UserID", "UsersAccessLevels.UsersAccessLevelID"] 
    ), 
    ondelete="CASCADE" 
) 

Cependant, avec ce changement il y a encore une erreur de syntaxe, comme ondelete="CASCADE" n'est pas défini. La modification à un dict {"ondelete": "cascade"} lance ceci:

File "/srv/vortech-backend/venv/lib/python3.5/site-packages/sqlalchemy/sql/base.py", line 282, in _validate_dialect_kwargs 
    "named <dialectname>_<argument>, got '%s'" % k) 
TypeError: Additional arguments should be named <dialectname>_<argument>, got 'ondelete' 
+0

Fait intéressant, dans http://docs.sqlalchemy.org/en/latest/core/constraints.html#on-update-and-on-delete, il montre exactement la façon dont je l'ai eu à l'origine (ce qui ne fonctionne pas). –

+0

Vous avez lié à des documents de base sur comment passer une contrainte de clé étrangère en tant qu'argument à un constructeur 'Table'. Il est très différent de la définition de classe Déclarative que vous avez, qui devrait avoir une séquence ou un mappage d'arguments comme '__table_args__', si vous le transmettez (comme l'indique l'erreur). Normalement, un tuple ou un dict est passé. Vous avez passé un seul 'ForeignKeyConstraint' à la place, parce que vous avez oublié d'ajouter une virgule après lui, vous avez donc une expression dans les parenthèses redondantes. Et l'argument mot-clé ondelete appartiendrait à l'appel 'ForeignKeyConstraint', pas à l'extérieur de celui-ci. –

+0

Beaucoup plus faux aussi avec la première tentative, comme essayer de créer une clé étrangère composite à 2 tables différentes. –

Répondre

0

Bon, après quelques tests et de la lecture, la réponse est que SQLAlchemy réalise une série interne pour y parvenir. Donc, cela va obtenir le même résultat que le SQL:

from app import db # The value is from: db = SQLAlchemy(app) 


class Users(db.Model): 
    """All users' information is stored here""" 
    __tablename__ = "Users" 
    UserID = db.Column(db.Integer(), primary_key=True) 
    Name = db.Column(db.String(200), nullable=False) 
    Email = db.Column(db.String(200)) 
    Username = db.Column(db.String(200), nullable=False) 
    Password = db.Column(db.Text, nullable=False) 
    Created = db.Column(db.DateTime) 
    Updated = db.Column(db.DateTime) 


class UsersAccessLevels(db.Model): 
    """This defines the various access levels users can have""" 
    __tablename__ = "UsersAccessLevels" 
    UsersAccessLevelID = db.Column(db.Integer, primary_key=True) 
    LevelName = db.Column(db.String(100), nullable=False) 
    AccessDescription = db.Column(db.Text) 


class UsersAccessMapping(db.Model): 
    """Each users' access level is defined here""" 
    __tablename__ = "UsersAccessMapping" 
    UsersAccessMappingID = db.Column(db.Integer, primary_key=True) 
    UserID = db.Column(
     db.Integer, db.ForeignKey("Users.UserID", ondelete="CASCADE"), nullable=False 
    ) 
    UsersAccessLevelID = db.Column(
     db.Integer, 
     db.ForeignKey("UsersAccessLevels.UsersAccessLevelID", ondelete="CASCADE"), 
     nullable=False 
    ) 

Les contraintes et telles sont automagicalement avec les paramètres db.ForeignKey() dans la définition de la colonne. Il n'a pas besoin d'être fait directement sur la table, comme dans SQL.

Les noms des clés étrangères semblent également être générés automatiquement par SQLAlchemy. Voici à quoi ça ressemble dans la base de données:

enter image description here