Was ist der effizienteste Weg, um eine Liste in den Django-Modellen zu speichern?


146

Derzeit habe ich viele Python-Objekte in meinem Code, die den folgenden ähneln:

class MyClass():
  def __init__(self, name, friends):
      self.myName = name
      self.myFriends = [str(x) for x in friends]

Jetzt möchte ich daraus ein Django-Modell machen, bei dem self.myName ein Zeichenfolgenfeld und self.myFriends eine Liste von Zeichenfolgen ist.

from django.db import models

class myDjangoModelClass():
    myName = models.CharField(max_length=64)
    myFriends = ??? # what goes here?

Da die Liste eine so häufige Datenstruktur in Python ist, habe ich erwartet, dass es ein Django-Modellfeld dafür gibt. Ich weiß, dass ich eine ManyToMany- oder OneToMany-Beziehung verwenden kann, aber ich hatte gehofft, diese zusätzliche Indirektion im Code zu vermeiden.

Bearbeiten:

Ich habe diese verwandte Frage hinzugefügt , die die Leute vielleicht nützlich finden.


1
@drozzy: Nun, ich hätte wahrscheinlich einen anderen Ausdruck verwenden können, aber im Grunde meinte ich, ich möchte eine Liste von Zeichenfolgen übergeben und eine Liste von Zeichenfolgen zurückbekommen. Ich möchte keine Reihe von Friend-Objekten erstellen und für jedes von ihnen inst.myFriends.add (friendObj) aufrufen. Nicht, dass es so schwer wäre, aber ...
trauern Sie

Antworten:


77

Wäre diese Beziehung nicht besser als eine Eins-zu-Viele-Fremdschlüsselbeziehung zu einer FriendsTabelle auszudrücken? Ich verstehe, dass dies myFriendsnur Zeichenfolgen sind, aber ich würde denken, dass ein besseres Design darin besteht, ein FriendModell zu erstellen und MyClasseine Fremdschlüsselbeziehung zur resultierenden Tabelle zu haben.


15
Dies ist wahrscheinlich das, was ich am Ende tun werde, aber ich hatte wirklich gehofft, dass die zugrunde liegende Struktur dafür eingebaut worden wäre. Ich denke, ich bin zu faul.
Trauer

Elegant und am schönsten erklärt.
Tessaracter


129

"Vorzeitige Optimierung ist die Wurzel allen Übels."

Lassen Sie uns dies vor diesem Hintergrund tun! Sobald Ihre Apps einen bestimmten Punkt erreicht haben, ist es sehr häufig, dass Daten denormalisiert werden. Richtig gemacht, können zahlreiche teure Datenbanksuchen auf Kosten eines etwas höheren Verwaltungsaufwands eingespart werden.

Um einen listFreundesnamen zurückzugeben, müssen wir eine benutzerdefinierte Django-Feldklasse erstellen, die beim Zugriff eine Liste zurückgibt.

David Cramer hat in seinem Blog einen Leitfaden zum Erstellen eines SeperatedValueField veröffentlicht. Hier ist der Code:

from django.db import models

class SeparatedValuesField(models.TextField):
    __metaclass__ = models.SubfieldBase

    def __init__(self, *args, **kwargs):
        self.token = kwargs.pop('token', ',')
        super(SeparatedValuesField, self).__init__(*args, **kwargs)

    def to_python(self, value):
        if not value: return
        if isinstance(value, list):
            return value
        return value.split(self.token)

    def get_db_prep_value(self, value):
        if not value: return
        assert(isinstance(value, list) or isinstance(value, tuple))
        return self.token.join([unicode(s) for s in value])

    def value_to_string(self, obj):
        value = self._get_val_from_obj(obj)
        return self.get_db_prep_value(value)

Die Logik dieses Codes befasst sich mit der Serialisierung und Deserialisierung von Werten aus der Datenbank nach Python und umgekehrt. Jetzt können Sie unser benutzerdefiniertes Feld in der Modellklasse einfach importieren und verwenden:

from django.db import models
from custom.fields import SeparatedValuesField 

class Person(models.Model):
    name = models.CharField(max_length=64)
    friends = SeparatedValuesField()

8
+1 für eine gute Antwort, aber wir machen schon so etwas. Es quetscht wirklich alle Werte in eine Zeichenfolge und teilt sie dann auf. Ich hatte wohl eher auf ein ListofStringsField gehofft, das tatsächlich die separate Tabelle erstellt und die Fremdschlüssel automatisch erstellt. Ich bin mir nicht sicher, ob das in Django möglich ist. Wenn dies der Fall ist und ich eine Antwort finde, werde ich sie auf stackoverflow veröffentlichen.
Trauer

2
Wenn dies der Fall ist, suchen Sie nach dem Django-Denorm von initcrash. Sie finden es auf github: github.com/initcrash/django-denorm/tree/master
jb.

3
+1. Aber mögliche Probleme mit Kommas in Strings. Was ist mit Serialisierung und Deserialisierung von JSON?
Sbeliakov

Der Versuch, dies zu einem vorhandenen Modell hinzuzufügen, my_vals = SeparatedValuesField(blank=True, default="")aber IntegrityError aufgrund von NULL-Werten zu erhalten. Wird das Standardargument nicht korrekt übergeben?
John Lehmann

1
Beachten Sie, dass in Django 2.1 das to_pythonLesen nicht mehr aufgerufen wird. Damit dies funktioniert, müssen Sie hinzufügen: def from_db_value(self, value, expression, connection, context): return self.to_python(value)
Theadriangreen

46

Eine einfache Möglichkeit, eine Liste in Django zu speichern, besteht darin, sie einfach in eine JSON-Zeichenfolge zu konvertieren und diese dann als Text im Modell zu speichern. Sie können die Liste dann abrufen, indem Sie die (JSON-) Zeichenfolge wieder in eine Python-Liste konvertieren. Hier ist wie:

Die "Liste" würde in Ihrem Django-Modell wie folgt gespeichert:

class MyModel(models.Model):
    myList = models.TextField(null=True) # JSON-serialized (text) version of your list

In Ihrer Ansicht / Controller-Code:

Speichern der Liste in der Datenbank:

import simplejson as json # this would be just 'import json' in Python 2.7 and later
...
...

myModel = MyModel()
listIWantToStore = [1,2,3,4,5,'hello']
myModel.myList = json.dumps(listIWantToStore)
myModel.save()

Abrufen der Liste aus der Datenbank:

jsonDec = json.decoder.JSONDecoder()
myPythonList = jsonDec.decode(myModel.myList)

Konzeptionell ist hier was los:

>>> myList = [1,2,3,4,5,'hello']
>>> import simplejson as json
>>> myJsonList = json.dumps(myList)
>>> myJsonList
'[1, 2, 3, 4, 5, "hello"]'
>>> myJsonList.__class__
<type 'str'>
>>> jsonDec = json.decoder.JSONDecoder()
>>> myPythonList = jsonDec.decode(myJsonList)
>>> myPythonList
[1, 2, 3, 4, 5, u'hello']
>>> myPythonList.__class__
<type 'list'>

8
Leider hilft Ihnen dies nicht, die Liste mit django admin
GreenAsJade

25

Wenn Sie Django> = 1.9 mit Postgres verwenden, können Sie die Vorteile von ArrayField nutzen

Ein Feld zum Speichern von Datenlisten. Die meisten Feldtypen können verwendet werden. Sie übergeben einfach eine andere Feldinstanz als base_field. Sie können auch eine Größe angeben. ArrayField kann verschachtelt werden, um mehrdimensionale Arrays zu speichern.

Es ist auch möglich, Array-Felder zu verschachteln:

from django.contrib.postgres.fields import ArrayField
from django.db import models

class ChessBoard(models.Model):
    board = ArrayField(
        ArrayField(
            models.CharField(max_length=10, blank=True),
            size=8,
        ),
        size=8,
    )

Wie bereits erwähnt, ist es auch möglich, Elemente direkt abzufragen. Dokumentation Referenz


2
Ein großer Vorteil davon ist, dass Sie die Elemente direkt aus dem Array-Feld abfragen können.
Thane Brimhall

@ThaneBrimhall du hast recht. Vielleicht sollte ich die Antwort mit diesen Informationen aktualisieren. Vielen Dank
Wolendranh

Leider keine Lösung für MySQL
Joel G Mathew

Es sollte erwähnt werden, dass dies nur mit PostGres funktioniert.
Theadriangreen


15

Da dies eine alte Frage ist und sich die Django-Techniken seitdem erheblich geändert haben müssen, spiegelt diese Antwort Django Version 1.4 wider und ist höchstwahrscheinlich für Version 1.5 anwendbar.

Django verwendet standardmäßig relationale Datenbanken. du solltest sie benutzen. Ordnen Sie Freundschaften mithilfe von ManyToManyField Datenbankbeziehungen (Fremdschlüsseleinschränkungen) zu. Auf diese Weise können Sie RelatedManager für Freundeslisten verwenden, die intelligente Abfragesätze verwenden. Sie können alle verfügbaren Methoden wie filteroder verwenden values_list.

Verwenden von ManyToManyFieldBeziehungen und Eigenschaften:

class MyDjangoClass(models.Model):
    name = models.CharField(...)
    friends = models.ManyToManyField("self")

    @property
    def friendlist(self):
        # Watch for large querysets: it loads everything in memory
        return list(self.friends.all())

Sie können auf folgende Weise auf die Freundesliste eines Benutzers zugreifen:

joseph = MyDjangoClass.objects.get(name="Joseph")
friends_of_joseph = joseph.friendlist

Beachten Sie jedoch, dass diese Beziehungen symmetrisch sind: Wenn Joseph ein Freund von Bob ist, dann ist Bob ein Freund von Joseph.


9
class Course(models.Model):
   name = models.CharField(max_length=256)
   students = models.ManyToManyField(Student)

class Student(models.Model):
   first_name = models.CharField(max_length=256)
   student_number = models.CharField(max_length=128)
   # other fields, etc...

   friends = models.ManyToManyField('self')

8

Denken Sie daran, dass dies schließlich in einer relationalen Datenbank enden muss. Die Verwendung von Beziehungen ist also der übliche Weg, um dieses Problem zu lösen. Wenn Sie unbedingt darauf bestehen, eine Liste im Objekt selbst zu speichern, können Sie sie beispielsweise durch Kommas trennen, in einer Zeichenfolge speichern und dann Zugriffsfunktionen bereitstellen, mit denen die Zeichenfolge in eine Liste aufgeteilt wird. Damit sind Sie auf eine maximale Anzahl von Zeichenfolgen beschränkt und verlieren effiziente Abfragen.


3
Ich bin damit einverstanden, dass die Datenbank sie als Beziehung speichert. Ich hatte gehofft, dass die Django-Modelle diesen Teil bereits für mich abstrahiert haben. Von der App-Seite werde ich es immer als eine Liste von Zeichenfolgen behandeln wollen.
Trauer



3

Speichern einer Liste von Zeichenfolgen im Django-Modell:

class Bar(models.Model):
    foo = models.TextField(blank=True)

    def set_list(self, element):
        if self.foo:
            self.foo = self.foo + "," + element
        else:
            self.foo = element

    def get_list(self):
        if self.foo:
            return self.foo.split(",")
        else:
            None

und Sie können es so nennen:

bars = Bar()
bars.set_list("str1")
bars.set_list("str2")
list = bars.get_list()
if list is not None:
    for bar in list:
        print bar
else:
    print "List is empty."      

2

Meine Lösung könnte sein, dass sie jemandem hilft:

import json
from django.db import models


class ExampleModel(models.Model):
    _list = models.TextField(default='[]')

    @property
    def list(self):
        return json.loads(self._list)

    @list.setter
    def list(self, value):
        self._list = json.dumps(self.list + value)

1

Durch die Verwendung einer Eins-zu-Viele-Beziehung (FK von Freund zu übergeordneter Klasse) wird Ihre App skalierbarer (da Sie das Friend-Objekt trivial um zusätzliche Attribute erweitern können, die über den einfachen Namen hinausgehen). Und das ist der beste Weg


3
Das ist keine Skalierbarkeit, das ist Erweiterbarkeit. Oft geht einer auf Kosten des anderen. Wenn Sie in diesem Fall wissen, dass Sie immer eine Liste von Zeichenfolgen benötigen, können Sie einen teuren Join vermeiden und so Ihren Code skalierbarer machen (dh leistungsfähiger durch die Denormalisierung).
Dustin Rasener

Das Obige mit ein paar Einschränkungen: 1) Sie wissen, dass Sie niemals Daten abfragen möchten und 2) Speicher ist immer noch billiger als Rechenleistung und Speicher (wer weiß, vielleicht ändert sich dies mit Quantencomputern)
Dustin Rasener
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.