Qual è il modo più efficiente per memorizzare un elenco nei modelli Django?


146

Attualmente ho molti oggetti Python nel mio codice simili ai seguenti:

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

Ora voglio trasformarlo in un modello Django, dove self.myName è un campo stringa e self.myFriends è un elenco di stringhe.

from django.db import models

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

Dato che l'elenco è una struttura di dati così comune in Python, mi aspettavo che ci fosse un campo modello Django per questo. So di poter usare una relazione ManyToMany o OneToMany, ma speravo di evitare quella indiretta aggiuntiva nel codice.

Modificare:

Ho aggiunto questa domanda correlata , che le persone potrebbero trovare utili.


1
@drozzy: Beh, probabilmente avrei potuto usare una frase diversa, ma fondamentalmente quello che intendevo dire era che volevo passare un elenco di stringhe e recuperare un elenco di stringhe. Non voglio creare un mucchio di oggetti Friend e chiamare inst.myFriends.add (friendObj) per ognuno di essi. Non che sarebbe così difficile, ma ...
addolorati il

Risposte:


77

Questa relazione non sarebbe meglio espressa come una relazione di chiave esterna uno-a-molti a una Friendstabella? Capisco che myFriendssono solo stringhe ma penso che un design migliore sarebbe quello di creare un Friendmodello e MyClasscontenere una realizzazione di chiave esterna nella tabella risultante.


15
Questo è probabilmente quello che finirò per fare, ma speravo davvero che la struttura sottostante sarebbe stata integrata. Immagino di essere pigro.
addolorarsi il

Elegante e meravigliosamente spiegato.
Tessaracter,


129

"L'ottimizzazione precoce è la radice di tutti i mali."

Con questo in mente, facciamolo! Una volta che le tue app raggiungono un certo punto, la denormalizzazione dei dati è molto comune. Fatto correttamente, può salvare numerose costose ricerche nel database al costo di un po 'più di pulizie.

Per restituire uno listdei nomi degli amici, dovremo creare una classe Django Field personalizzata che restituirà un elenco quando vi si accede.

David Cramer ha pubblicato una guida per la creazione di SeperatedValueField sul suo blog. Ecco il codice:

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)

La logica di questo codice riguarda la serializzazione e la deserializzazione dei valori dal database a Python e viceversa. Ora puoi facilmente importare e utilizzare il nostro campo personalizzato nella classe del modello:

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

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

8
+1 per un'ottima risposta, ma stiamo già facendo qualcosa del genere. Sta davvero schiacciando tutti i valori in una stringa e poi li divide. Immagino che sperassi in qualcosa di più simile a ListofStringsField, che in realtà costruisce la tabella separata e rende automaticamente le chiavi esterne. Non sono sicuro che ciò sia possibile in Django. Se lo è, e trovo una risposta, la posterò su StackOverflow.
addolorarsi il

2
In tal caso, stai cercando il django-denorm di initcrash. Lo troverai su github: github.com/initcrash/django-denorm/tree/master
jb.

3
+1. Ma possibili problemi con le virgole nelle stringhe. Che dire di serializzare e deserializzare da JSON?
sbeliakov,

Sto provando ad aggiungere questo al modello esistente con my_vals = SeparatedValuesField(blank=True, default="")ma ottenendo IntegrityError a causa di NULL. L'argomento predefinito non viene passato correttamente?
John Lehmann,

1
Si noti che in Django 2.1 to_pythonnon viene più chiamato in lettura. Quindi per fare questo lavoro devi aggiungere: def from_db_value(self, value, expression, connection, context): return self.to_python(value)
theadriangreen

46

Un modo semplice per memorizzare un elenco in Django è semplicemente convertirlo in una stringa JSON e quindi salvarlo come testo nel modello. È quindi possibile recuperare l'elenco convertendo la stringa (JSON) in un elenco Python. Ecco come:

La "lista" verrebbe memorizzata nel tuo modello Django in questo modo:

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

Nel tuo codice vista / controller:

Memorizzare l'elenco nel database:

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()

Recupero dell'elenco dal database:

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

Concettualmente, ecco cosa sta succedendo:

>>> 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
Sfortunatamente questo non ti aiuta a gestire l'elenco usando django admin
GreenAsJade

25

Se stai usando Django> = 1.9 con Postgres puoi usufruire dei vantaggi di ArrayField

Un campo per la memorizzazione di elenchi di dati. È possibile utilizzare la maggior parte dei tipi di campo, è sufficiente passare un'altra istanza di campo come campo_base. Puoi anche specificare una taglia. ArrayField può essere nidificato per memorizzare array multidimensionali.

È anche possibile nidificare i campi dell'array:

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,
    )

Come menzionato da @ thane-brimhall, è anche possibile interrogare direttamente gli elementi. Riferimenti alla documentazione


2
Il grande vantaggio di questo è che puoi interrogare gli elementi direttamente dal campo dell'array.
Thane Brimhall,

@ThaneBrimhall hai ragione. Forse dovrei aggiornare la risposta con queste informazioni. Grazie
wolendranh,

Purtroppo, nessuna soluzione per mysql
Joel G Mathew,

Va detto che questo funziona solo con PostGres.
theadriangreen,


15

Poiché questa è una vecchia domanda e le tecniche di Django devono essere cambiate in modo significativo da allora, questa risposta riflette Django versione 1.4 ed è probabilmente applicabile per la v 1.5.

Django usa di default database relazionali; dovresti farne uso. Mappare le amicizie alle relazioni di database (vincoli di chiave esterna) con l'uso di ManyToManyField. In questo modo è possibile utilizzare RelatedManager per le liste di amici, che utilizzano query intelligenti. È possibile utilizzare tutti i metodi disponibili come filtero values_list.

Utilizzando ManyToManyFieldrelazioni e proprietà:

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())

Puoi accedere all'elenco di amici di un utente in questo modo:

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

Si noti tuttavia che queste relazioni sono simmetriche: se Joseph è un amico di Bob, allora Bob è un amico di 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

Ricorda che questo alla fine deve finire in un database relazionale. Quindi usare le relazioni è davvero il modo comune per risolvere questo problema. Se si insiste assolutamente sulla memorizzazione di un elenco nell'oggetto stesso, è possibile farlo ad esempio separato da virgole e archiviarlo in una stringa, quindi fornire funzioni di accesso che suddividono la stringa in un elenco. Con ciò, sarai limitato a un numero massimo di stringhe e perderai query efficienti.


3
Sto bene con il database che lo memorizza come relazione, speravo che i modelli Django ne avessero già estratto quella parte. Dal lato dell'app ho sempre voglia di trattarlo come un elenco di stringhe.
addolorarsi il



3

Memorizzazione di un elenco di stringhe nel modello Django:

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

e puoi chiamarlo così:

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."      

1

L'uso della relazione uno-a-molti (FK da Friend a classe genitore) renderà la tua app più scalabile (in quanto puoi estendere banalmente l'oggetto Friend con attributi aggiuntivi oltre il semplice nome). E quindi questo è il modo migliore


3
Questa non è scalabilità, è estensibilità. Spesso uno è a costo dell'altro. In questo caso, se sai che avrai sempre bisogno di un elenco di stringhe, puoi evitare un join costoso, rendendo così il tuo codice più scalabile (ovvero più performante dalla denormalizzazione).
Dustin Rasener,

Quanto sopra con un paio di avvertenze: 1) sai che non vuoi mai interrogare contro quei dati e 2) l'archiviazione è ancora più economica della potenza di elaborazione e della memoria (chissà, forse questo cambia con il calcolo quantistico)
Dustin Rasener

1

La mia soluzione, potrebbe essere che aiuta qualcuno:

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)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.