Leere ForeignKeys bei Django mit MySQL

Django, Flask, Bottle, WSGI, CGI…
Antworten
Nerpheus
User
Beiträge: 7
Registriert: Donnerstag 11. Februar 2021, 13:59

Hallo zusammen,

ich arbeite gerade an meiner ersten Webanwendung mit Django und stolpere nun über die Anbindung an meine bestehende MySQL-Datenbank. Ich bekomme es nicht hin, dass in Django meine Foreign Keys automatisch ausgefüllt werden.

Meine bestehende Datenbank hat aktuell 2 Tabellen:
  • Country
  • League
Jede Liga aus League wird genau einem Land aus Country zugeordnet.
Jedes Land aus Country kann wiederum mehreren Ligen aus League zugeordnet sein.

Folgende Models habe ich mittels inspectdb generiert:

Code: Alles auswählen

from django.db import models


class Country(models.Model):
    name = models.CharField(unique=True, max_length=45)
    isocode = models.CharField(unique=True, max_length=10)

    def __str__(self):
        return "{} ({})".format(self.name, self.isocode)

    class Meta:
        managed = False
        db_table = 'Country'


class League(models.Model):
    name = models.CharField(max_length=45)
    country = models.ForeignKey(to='Country', on_delete=models.CASCADE, db_column='country')
    shortcut = models.CharField(unique=True, max_length=45)

    def __str__(self):
        return "{} {} ({})".format(self.name, self.country_id, self.shortcut)

    class Meta:
        managed = False
        db_table = 'League'
        unique_together = (('name', 'country'),)
Mit

Code: Alles auswählen

League.objects.all()
bekomme ich zwar alle Objekte aus League ausgegeben, wenn ich aber filtern möchte mit

Code: Alles auswählen

League.objects.filter(country_id="1")
, dann bekomme ich nur ein leeres QuerySet. Es funktioniert nur, wenn ich

Code: Alles auswählen

League.objects.filter(country_id="0")
nutze. Dann gibt er mir jedoch wieder alle Objekte zurück. Entsprechend sind auch alle QuerySets leer, wenn ich mir zu jedem Country die jeweilige Leagues anzeigen lassen möchte.

Ich habe das Gefühl, dass die ForeignKeys nicht richtig ausgefüllt werden. Wenn ich nämlich im Backend der Anwendung nachschaue, ist das Feld Country in der Tabelle League leer.

Hat jemand eine Idee, wo mein Fehler liegen könnte?
Benutzeravatar
__blackjack__
User
Beiträge: 13061
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Wie sehen denn die Definitionen der Datenbanktabellen aus? Und kann man wirklich Zeichenketten mit Ziffern drin als IDs angeben?
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Nerpheus
User
Beiträge: 7
Registriert: Donnerstag 11. Februar 2021, 13:59

Hier kommen die Tabellen-Definitionen:

Code: Alles auswählen

CREATE TABLE `Country` (
 `id` int NOT NULL AUTO_INCREMENT,
 `name` varchar(45) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL,
 `isocode` varchar(10) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL,
 PRIMARY KEY (`id`),
 UNIQUE KEY `iso-code` (`isocode`),
 UNIQUE KEY `countryName` (`name`)
)

Code: Alles auswählen

CREATE TABLE `League` (
 `id` int NOT NULL AUTO_INCREMENT,
 `name` varchar(45) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL,
 `country` varchar(45) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL,
 `shortcut` varchar(45) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL,
 PRIMARY KEY (`id`),
 UNIQUE KEY `leagueShortcut` (`shortcut`),
 UNIQUE KEY `Kombination` (`name`,`country`),
 KEY `leagueCountry` (`country`),
 KEY `leagueName` (`name`,`country`),
 CONSTRAINT `League_ibfk_1` FOREIGN KEY (`country`) REFERENCES `Country` (`name`) ON DELETE CASCADE ON UPDATE CASCADE
)
Und kann man wirklich Zeichenketten mit Ziffern drin als IDs angeben?
In diesem Fall macht es tatsächlich keinen Unterschied. Weder Zeichenkette mit Ziffern noch Ziffern als Int klappen. Die QuerySets sind immer leer mit Ausnahme der 0.
Benutzeravatar
sparrow
User
Beiträge: 4183
Registriert: Freitag 17. April 2009, 10:28

Warm verwendest du "managed=False"?
Wer hat die Tabellen angelegt? Warum lässt du das nicht Django machen? Das Tabellenschema der Datenbank ist offensichtlich seltsam bis falsch. Die Modelle haben IDs, als Foreign-Key wird aber ein Char-Field verwendet. Warum denn bitte das?
Nerpheus
User
Beiträge: 7
Registriert: Donnerstag 11. Februar 2021, 13:59

Warm verwendest du "managed=False"?
Damit Django die Tabellennamen übernimmt. Django soll auch keine Daten einfügen, sondern lediglich lesen und ausgeben. Das Befüllen mit Daten erfolgt an anderer Stelle.
Wer hat die Tabellen angelegt? Warum lässt du das nicht Django machen?
Die Tabellen wurden von mir angelegt und beinhalten (in der größten Tabelle) mittlerweile rund 600.000 Datensätze. Deswegen würde ich die Datenbank ungern erneut anlegen.
Ich fürchte allerdings, dass ich da nicht drumherum kommen werde, wenn dort bereits der Fehler liegt.
Das Tabellenschema der Datenbank ist offensichtlich seltsam bis falsch. Die Modelle haben IDs, als Foreign-Key wird aber ein Char-Field verwendet. Warum denn bitte das?
Wenn ich beispielsweise eine neue Liga in die Tabelle League einfüge, dann trage ich den Namen, das Land und den Shortcut ein. MySQL soll gleichzeitig prüfen, ob das Land bereits in der Tabelle Country vorhanden ist. Wenn das der Fall ist, wird direkt die Verknüpfung erstellt. Wenn nicht, dann soll die Liga nicht eingefügt werden.
Alternativ müsste ich anstelle des Ländernamens immer die Länder-ID in League eintragen. Oder habe ich da grundsätzlich einen Denkfehler?
Benutzeravatar
sparrow
User
Beiträge: 4183
Registriert: Freitag 17. April 2009, 10:28

Du solltest dich in das Thema Normalisierung von Datenbanken einlesen.

Aus meiner Erfahrung kann ich nur eins sagen: Arbeite bei Fremschlüsseln immer mit IDs. Nicht mit irgendwelchen sonstigen, im Moment irgendwie dienlich erscheinenden Feldern.
Du zeigst mit deinem Fremdschlüssel Country.name, nicht auf Country.id. Was machst du denn jetzt, wenn ich der Name des Landes ändert? Willst du das dann in allen Relationen ändern? Kommt nicht vor? Dann hoffen wir, dass du zum Beispiel die "Republik Zaire" nicht in deiner Liste hattest, als ds 1997 den Namen zur "Demokratischen Republik Kongo" änderte.
Deshalb immer auf IDs verweisen, damit man gar nicht in das Problem läuft.

Und wenn du da was "per Hand" einträgst, dann muss das Tool, über das du einträgst, entsprechend dafür sorgen, dass die Zuweisung korrekt funktioniert. Sprich du gibst "Kongo" ein und beim Eintrag in die Datenbank muss dann eben geprüft werden, wie die korrekte country.id für Kongo ist.

Zu deinem Problem mit Django:
Das kennt natürlich die Problematik. Und das geht natürlich davon aus, dass das Datenbankschema nicht kaputt ist. Was du tust ist zu sagen: Ich habe hier ein Feld mit einem Fremdschlüssel - und der zeigt auf die Relation "country". Und woher weiß Django jetzt, dass du eben nicht - wie eigentlich üblich - die ID der Tabelle verwendest, sondern irgend ein anderes Feld aus der Relation?
Deshalb musst du ihm sagen, dass dein Datenbankschema da speziell ist.

Code: Alles auswählen

country = models.ForeignKey(to='Country', to_field='name', on_delete=models.CASCADE, db_column='country')
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

sparrow hat geschrieben: Mittwoch 19. Januar 2022, 10:12 Was machst du denn jetzt, wenn ich der Name des Landes ändert? Willst du das dann in allen Relationen ändern? Kommt nicht vor? Dann hoffen wir, dass du zum Beispiel die "Republik Zaire" nicht in deiner Liste hattest, als ds 1997 den Namen zur "Demokratischen Republik Kongo" änderte.
Deshalb immer auf IDs verweisen, damit man gar nicht in das Problem läuft.

Code: Alles auswählen

postgres=# create table countries (name text primary key);
CREATE TABLE
postgres=# create table leagues (name text, country text references countries (name) on update cascade, primary key (name, country));
CREATE TABLE
postgres=# insert into countries (name) values ('Republik Zaire');
INSERT 0 1
postgres=# insert into leagues (name, country) values ('my league', 'Republik Zaire');
INSERT 0 1
postgres=# update countries set name = 'Demokratischen Republik Kongo' where name = 'Republik Zaire';
UPDATE 1
postgres=# select * from leagues ;
   name    |            country            
-----------+-------------------------------
 my league | Demokratischen Republik Kongo
(1 row)
Kann MySQL auch hab ich aber grad nicht zur Hand.

Das ist natürlich nicht empfehlenswert weil die Operation O(n) ist und mit Normalisierung man dies auch mit O(1) erreichen kann. Also bitte nicht kopieren, wenn man die Möglichkeit hat es besser zu tun.
Nerpheus
User
Beiträge: 7
Registriert: Donnerstag 11. Februar 2021, 13:59

Code: Alles auswählen

country = models.ForeignKey(to='Country', to_field='name', on_delete=models.CASCADE, db_column='country')
Das war der entscheidende Hinweis. Vielen Dank dafür!

Allerdings bin ich danach bereits auf ein anderes Problem gestoßen, sodass ich mich nun auch auf vermehrten Hinweis dazu entschieden habe, die Datenbank nochmal komplett sauber über Django aufzubauen. Dabei werde ich die FKs dann auch auf die IDs zeigen lassen.
Was machst du denn jetzt, wenn ich der Name des Landes ändert? Willst du das dann in allen Relationen ändern?
Die hat aktuell eigentlich gut geklappt. Hatte es teilweise mit Teamnamen, die sich ab und zu ändern. Dort muss ich auch lediglich in der Tabelle "Teams" den Namen ändern und es ändert sich in der kompletten Tabelle. Das scheint irgendwie zu funktionieren, obwohl ich auch hier die FKs auf den Namen habe zeigen lassen.

Vielen Dank nochmal für die schnelle Hilfestellung!
Antworten