Key error, SQLAlchemy Many to Many

Django, Flask, Bottle, WSGI, CGI…
ahupfer
User
Beiträge: 23
Registriert: Mittwoch 15. August 2018, 07:24

Hallo Zusammen,

Ich stehe schon seit Stunden am folgenden Code. Auch die wirklich gute Doku hilft mir leider hier nicht weiter. Ich beschäftige mich erst seit Kurzem mit Flask, ich denke ich mache an einem unbekannten Ort ein grundsätzlicher Fehler.
Die fertige App soll den Benutzern beim Erstellen von Meeting-Protokollen helfen. Dazu können die User einer Organisation zugehören und dort eine bestimmte Rolle übernehmen.
In meiner models.py steht Folgendes:

Code: Alles auswählen

from app import db

class Role(db.Model):
    user_id = db.Column(db.ForeignKey('user.id'), primary_key=True)
    org_id = db.Column(db.ForeignKey('organization.id'), primary_key=True)
    role = db.Column(db.String(32))
    user = db.relationship('User', back_populates='organizations')
    organization = db.relationship('Organization', back_populates='users')


class Organization(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    org_name = db.Column(db.String(64), index=True, unique=True)
    users = db.relationship('Role', back_populates='organization')

    def __repr__(self):
        return '<Organization {}>'.format(self.org_name)


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(118))
    organizations = db.relationship('Role', back_populates='user')

    def __repr__(self):
        return '<User {}>'.format(self.username)
Wenn ich folgende Befehle in der Python Konsole eingebe bekomme ich einen Fehler:

Code: Alles auswählen

u1 = User.query.get(1)
o1 = Organization.query.get(1)
u1.organizations.append(u1)
Fehler:

Code: Alles auswählen

Traceback (most recent call last):
  File "/usr/lib/python3.9/code.py", line 90, in runcode
    exec(code, self.locals)
  File "<input>", line 1, in <module>
  File "/home/andreas/PycharmProjects/flaskProject/venv/lib/python3.9/site-packages/sqlalchemy/orm/collections.py", line 1169, in append
    item = __set(self, item, _sa_initiator)
  File "/home/andreas/PycharmProjects/flaskProject/venv/lib/python3.9/site-packages/sqlalchemy/orm/collections.py", line 1134, in __set
    item = executor.fire_append_event(item, _sa_initiator)
  File "/home/andreas/PycharmProjects/flaskProject/venv/lib/python3.9/site-packages/sqlalchemy/orm/collections.py", line 753, in fire_append_event
    return self.attr.fire_append_event(
  File "/home/andreas/PycharmProjects/flaskProject/venv/lib/python3.9/site-packages/sqlalchemy/orm/attributes.py", line 1429, in fire_append_event
    value = fn(state, value, initiator or self._append_token)
  File "/home/andreas/PycharmProjects/flaskProject/venv/lib/python3.9/site-packages/sqlalchemy/orm/attributes.py", line 1766, in emit_backref_from_collection_append_event
    child_impl = child_state.manager[key].impl
KeyError: 'user'
Ich hoffe ihr könnt mir irgendwie auf die Sprünge helfen!

Vielen Dank und Grüsse aus der Schweiz
Andreas
Benutzeravatar
sparrow
User
Beiträge: 4195
Registriert: Freitag 17. April 2009, 10:28

Was du suchst ist eine many-to-many Relation.

Und musst du bei many-to-one nicht auch die ID des ForeignKey in der Relation speichern?
ahupfer
User
Beiträge: 23
Registriert: Mittwoch 15. August 2018, 07:24

Ich habe mich an diese Dokumentation gehalten:

http://docs.sqlalchemy.org/en/latest/or ... ny-to-many

Bin verwirrt
Grüsse
Andreas
Benutzeravatar
sparrow
User
Beiträge: 4195
Registriert: Freitag 17. April 2009, 10:28

Und wo ist dein association table?
ahupfer
User
Beiträge: 23
Registriert: Mittwoch 15. August 2018, 07:24

Ich benutze "laut Dokumentation" ;-) ein "Association Object", in meinem Fall Role. Wahrscheinlich verstehe ich einfach etwas falsch?

Grüsse
Andreas
Benutzeravatar
sparrow
User
Beiträge: 4195
Registriert: Freitag 17. April 2009, 10:28

Kannst du mir mal den Teil der Dokumentation zeigen, wo so eine Beziehung ohne Table beschrieben wird?
Benutzeravatar
sparrow
User
Beiträge: 4195
Registriert: Freitag 17. April 2009, 10:28

Und wenn du das hier meinst:
In dem Beispiel können ja beliebige Parents beliebie Children haben. Und um die zu verbinden braucht man eine Association Table, die diese Beziehung abbildet.

Wenn bei dir also n User m Roles haben - wo ist die Relation, die das darstellt?
ahupfer
User
Beiträge: 23
Registriert: Mittwoch 15. August 2018, 07:24

Klar hier:
https://docs.sqlalchemy.org/en/14/orm/b ... ion-object

Der Vorteil ist, dass ich dem association-object noch eine Rollenbezeichnung definieren kann.

Grüsse
Andreas
Benutzeravatar
sparrow
User
Beiträge: 4195
Registriert: Freitag 17. April 2009, 10:28

Grundsätzlich glaube ich, dass du da ein Problem hast, das in Relationen auszudrücken.

So kann zwar ein User beliebe Rollen haben - aber keine zwei User können die selbe Rolle haben. Das klingt nicht richtig.
Ich würde eher davon ausgehen, dass eine Organisation Rollen hat, und die werden Benutzern zugewiesen.
Hier sehe ich zumindest nicht, warum das eine many-to-many-Relation sein sollte.

Falls ich mich täusche - und das glaube ich nicht - bringt dich hier deine Namensgebung durcheinander und zeigt explizit,dass richtige Namensgebung wichtig ist.
Ich würde die Verbindung mit "Role" nicht an den Namen "users" binden.
Insbesonder versuchst du User.organizations einen "User" anzuhängen. Der Name ist auch hier falsch gewählt, denn hinter "organiszations" verbirgt sich "Role". Also musst du da auch ein Role-Objekt appenden.

Aber wie gesagt, ich denke, das ist von den Relationen her falsch gedacht.
Zuletzt geändert von sparrow am Sonntag 13. Februar 2022, 21:37, insgesamt 1-mal geändert.
ahupfer
User
Beiträge: 23
Registriert: Mittwoch 15. August 2018, 07:24

Ein User kann einer oder mehreren Organisationen zugehören. Eine Organisation kann mehrere User haben. Also eine "many to many" Beziehung zwischen User und Organisation.
Darum gibt es eine Tabelle dazwischen Rolle (role) als "Association-Table". So habe ich das mal gelernt?

Grüsse
Andreas
Benutzeravatar
sparrow
User
Beiträge: 4195
Registriert: Freitag 17. April 2009, 10:28

Dann musst du das aber richtig benennen. Wenn etwas auf "Role" zeigt, dann sind das "roles" nicht "users" oder "organisations".
Und dann wird auch klar, dass man da keine User Objekte appenden kann sondern nur Objekte vom Typ "Role".
ahupfer
User
Beiträge: 23
Registriert: Mittwoch 15. August 2018, 07:24

Aber ich habe meine Klassen genau so aufgebaut wie in der Dokumentation. Wobei meine Role Klasse der Association Klasse entspricht. Demnach wären meine User Klasse die Parent Klasse in der Dokumentation bzw. die Organization würde der Child Klasse entsprechen.

Grüsse
Andreas
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das ist zwar richtig, aber durch deine hoch konfuse Benennung verschleierst du, dass du ein Role-Objekt an die Eigenschaft organizations anfügen musst. Du versuchst es aber direkt mit einer Organisation, und das geht notwendigerweise schief. Hat sparrow auch schon gesagt.
ahupfer
User
Beiträge: 23
Registriert: Mittwoch 15. August 2018, 07:24

sparrow hat geschrieben: Sonntag 13. Februar 2022, 21:40 Dann musst du das aber richtig benennen. Wenn etwas auf "Role" zeigt, dann sind das "roles" nicht "users" oder "organisations".
Und dann wird auch klar, dass man da keine User Objekte appenden kann sondern nur Objekte vom Typ "Role".
Das heisst in meinen Klassen User und Organization müsste es heissen?

Code: Alles auswählen

...
users = db.relationship('Role', back_populates='role')
...
rganizations = db.relationship('Role', back_populates='role')
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Müssen muss nicht. Es ist schon richtig. Nur konfus. Und deine Änderung verbessert das nicht. Users haben roles. Organizations haben roles. Roles haben organizations und users. Und so muss das konsistent benannt werden. Du nennst aber deine Rollen auf den Usern “organizations”. Das ist Quatsch. Und verführt dich dazu, da wirklich eine Organisation anfügen zu wollen, obwohl es eben eine Role sein muss.
ahupfer
User
Beiträge: 23
Registriert: Mittwoch 15. August 2018, 07:24

Was mich echt verwirrt ist das Beispiel in der Dokumentation! Egal, ich werde heute Abend nochmals darüber nachdenken ;-) und euch zurückmelden ob ich es jetzt kapiert habe!
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das Beispiel ist klar. Wenn du das nimmst, und konsistent umbenennst, geht dir vielleicht dein Fehler auf. Und schau mal auf dessen Code Beispiel:

Code: Alles auswählen

# create parent, append a child via association
p = Parent()
a = Association(extra_data="some data")
a.child = Child()
p.children.append(a)
Das Association Objekt ist laut deiner eigenen Aussage bei dir die Rolle. Aber du erzeugst keine Rolle. Du holst eine Organisation & willst die hinzufügen! Statt die wie hier in ein Role-Objekt zu packen, und DAS an den User zu pappen.
Benutzeravatar
sparrow
User
Beiträge: 4195
Registriert: Freitag 17. April 2009, 10:28

Auch die Benennung in dem Beispiel der Dokumentation finde ich nicht glücklich.

Code: Alles auswählen

from app import db

class Role(db.Model):
    user_id = db.Column(db.ForeignKey('user.id'), primary_key=True)
    org_id = db.Column(db.ForeignKey('organization.id'), primary_key=True)
    name = db.Column(db.String(32))
    user = db.relationship('User', back_populates='roles')
    organization = db.relationship('Organization', back_populates='roles')


class Organization(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), index=True, unique=True)
    roles = db.relationship('Role', back_populates='organization')

    def __repr__(self):
        return '<Organization {}>'.format(self.name)


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(118))
    roles = db.relationship('Role', back_populates='user')

    def __repr__(self):
        return '<User {}>'.format(self.username)
So in etwa würde ich anfangen.
ahupfer
User
Beiträge: 23
Registriert: Mittwoch 15. August 2018, 07:24

Code: Alles auswählen

class Participant(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), index=True, nullable=False)
    email = db.Column(db.String(64), index=True, unique=True, nullable=False)
    meetingParticipantRoles = db.relationship('MeetingParticipantRole', backref='participant', lazy=True)


class MeetingParticipantRole(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(32), index=True)
    participant_id = db.Column(db.Integer, db.ForeignKey('participant.id'))
    meeting_id = db.Column(db.Integer, db.ForeignKey('meeting.id'))


class Meeting(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    subject = db.Column(db.String(32), index=True, nullable=False)
    date = db.DateTime(db.DateTime)
    time_begin = db.DateTime(db.DateTime)
    time_end = db.DateTime(db.DateTime)
    meetingParticipantRoles = db.relationship('MeetingParticipantRole', backref='meeting', lazy=True)
Ich habe die ganze Struktur ein wenig umgestellt und die Models oben funktionieren gut. Unter dem Link habe ich grob mein UML-Diagramm aufgebaut.
Wenn ich jetzt noch zusätzliche Methoden in den Klassen einfügen will, soll ich dies hier in der Models Datei tun, also die Methode direkt hier in die Klasse einfügen?
Wie ihr sehen könnt, erbt die Klasse Participant eigentlich von der Klasse User, wie bildet man solche Vererbungen in Flask ab?

Achtung, das Diagramm wird dann noch vollständig auf Englisch übersetzt.
https://lucid.app/lucidchart/f194c2ec-3 ... 003d28119e

Danke für eure Hilfe
Grüsse
Andreas
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das Diagramm ist nicht zu sehen ohne einen Login zu kreieren. Wozu ich zumindest nicht bereit bin. Da müsste dann ein Bild her.
Antworten