Jaki jest najbardziej efektywny sposób przechowywania listy w modelach Django?

146

Obecnie mam wiele obiektów Pythona w moim kodzie, podobnych do następujących:

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

Teraz chcę przekształcić to w model Django, w którym self.myName to pole tekstowe, a self.myFriends to lista ciągów.

from django.db import models

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

Ponieważ lista jest tak powszechną strukturą danych w Pythonie, spodziewałem się, że będzie dla niej pole modelu Django. Wiem, że mogę użyć relacji ManyToMany lub OneToMany, ale miałem nadzieję uniknąć tego dodatkowego pośrednictwa w kodzie.

Edytować:

Dodałem to pokrewne pytanie , które może się przydać.

smucić
źródło
1
@drozzy: Cóż, prawdopodobnie mogłem użyć innego wyrażenia, ale zasadniczo miałem na myśli to, że chcę przekazać listę ciągów i odzyskać listę ciągów. Nie chcę tworzyć wielu obiektów Friend i wywoływać inst.myFriends.add (friendObj) dla każdego z nich. Nie żeby to było takie trudne, ale ...
opłakuj

Odpowiedzi:

77

Czy ta relacja nie byłaby lepiej wyrażona jako relacja klucza obcego jeden do wielu z Friendstabelą? Rozumiem, że myFriendsto tylko łańcuchy, ale myślę, że lepszym projektem byłoby utworzenie Friendmodelu i MyClasszawarcie powiązania klucza obcego z wynikową tabelą.

Andrew Hare
źródło
15
Prawdopodobnie to właśnie ostatecznie zrobię, ale naprawdę miałem nadzieję, że podstawowa struktura tego została wbudowana. Myślę, że jestem zbyt leniwy.
opłakiwać
Eleganckie i najpiękniej wyjaśnione.
Tessaracter
129

„Przedwczesna optymalizacja jest źródłem wszelkiego zła”.

Mając to na uwadze, zróbmy to! Gdy Twoje aplikacje osiągną określony punkt, denormalizacja danych jest bardzo powszechna. Wykonane poprawnie, może zaoszczędzić wiele kosztownych wyszukiwań w bazie danych, kosztem nieco większego uporządkowania.

Aby zwrócić listimiona znajomych, będziemy musieli utworzyć niestandardową klasę Django Field, która po uzyskaniu dostępu zwróci listę.

David Cramer opublikował na swoim blogu przewodnik dotyczący tworzenia SeperatedValueField. Oto kod:

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)

Logika tego kodu dotyczy serializacji i deserializacji wartości z bazy danych do języka Python i odwrotnie. Teraz możesz łatwo importować i używać naszego pola niestandardowego w klasie modelu:

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

class Person(models.Model):
    name = models.CharField(max_length=64)
    friends = SeparatedValuesField()
jb.
źródło
8
+1 za świetną odpowiedź, ale już robimy coś takiego. To naprawdę zgniatanie wszystkich wartości w jeden ciąg, a następnie dzielenie ich. Wydaje mi się, że liczyłem na coś bardziej podobnego do ListofStringsField, który w rzeczywistości buduje oddzielną tabelę i automatycznie tworzy klucze obce. Nie jestem pewien, czy jest to możliwe w Django. Jeśli tak, i znajdę odpowiedź, wrzucę to na stackoverflow.
opłakiwać
2
W takim przypadku szukasz pliku django-denorm initcrash. Znajdziesz go na github: github.com/initcrash/django-denorm/tree/master
jb.
3
+1. Ale możliwe problemy z przecinkami w łańcuchach. A co z serializacją i deserializacją z json?
sbeliakov
Próbuję dodać to do istniejącego modelu, my_vals = SeparatedValuesField(blank=True, default="")ale otrzymuję IntegrityError z powodu wartości NULL. Czy domyślny argument nie jest poprawnie przekazywany?
John Lehmann
1
Zauważ, że w Django 2.1 to_pythonnie jest już wywoływana podczas odczytu. Tak więc, aby to zadziałało, musisz dodać: def from_db_value(self, value, expression, connection, context): return self.to_python(value)
theadriangreen
46

Prostym sposobem na przechowywanie listy w Django jest po prostu przekonwertowanie jej na ciąg JSON, a następnie zapisanie go jako tekstu w modelu. Następnie możesz pobrać listę, konwertując ciąg (JSON) z powrotem na listę Pythona. Oto jak:

„Lista” byłaby przechowywana w Twoim modelu Django w następujący sposób:

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

Twój widok / kod kontrolera:

Przechowywanie listy w bazie danych:

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

Pobieranie listy z bazy danych:

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

Koncepcyjnie, oto co się dzieje:

>>> 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'>
złodziej umysłów
źródło
8
Niestety nie pomaga to w zarządzaniu listą za pomocą administratora django
GreenAsJade
25

Jeśli używasz Django> = 1.9 z Postgresem, możesz skorzystać z zalet ArrayField

Pole do przechowywania list danych. Można użyć większości typów pól, wystarczy przekazać inną instancję pola jako pole podstawowe. Możesz także określić rozmiar. ArrayField można zagnieżdżać w celu przechowywania tablic wielowymiarowych.

Możliwe jest również zagnieżdżenie pól tablicowych:

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

Jak wspomniał @ thane-brimhall, możliwe jest również bezpośrednie zapytanie o elementy. Odniesienie do dokumentacji

wolendranh
źródło
2
Dużą zaletą tego jest to, że możesz zapytać o elementy bezpośrednio z pola tablicy.
Thane Brimhall
@ThaneBrimhall masz rację. Może powinienem zaktualizować odpowiedź o te informacje. Dzięki
wolendranh
Niestety, nie ma rozwiązania dla mysql
Joel G Mathew
Należy wspomnieć, że działa to tylko z PostGres.
theadriangreen
1
Django 1.8 również ma ArrayField: docs.djangoproject.com/en/1.8/ref/contrib/postgres/fields
kontextify
15

Ponieważ jest to stare pytanie, a techniki Django musiały się znacznie zmienić od tamtego czasu, ta odpowiedź odzwierciedla wersję 1.4 Django i najprawdopodobniej ma zastosowanie do wersji 1.5.

Django domyślnie używa relacyjnych baz danych; powinieneś z nich skorzystać. Mapuj przyjaźnie do relacji w bazie danych (ograniczenia klucza obcego) za pomocą ManyToManyField. Dzięki temu możesz używać RelatedManager do list znajomych, które używają inteligentnych zestawów zapytań. Możesz użyć wszystkich dostępnych metod, takich jak filterlub values_list.

Korzystanie z ManyToManyFieldrelacji i właściwości:

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

Możesz uzyskać dostęp do listy znajomych użytkownika w ten sposób:

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

Zauważ jednak, że te relacje są symetryczne: jeśli Józef jest przyjacielem Boba, to Bob jest przyjacielem Josepha.

sleblanc
źródło
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')
Andriy Drozdyuk
źródło
8

Pamiętaj, że to ostatecznie musi trafić do relacyjnej bazy danych. Tak więc używanie relacji jest naprawdę powszechnym sposobem rozwiązania tego problemu. Jeśli absolutnie nalegasz na przechowywanie listy w samym obiekcie, możesz na przykład rozdzielić ją przecinkami i przechowywać w ciągu, a następnie udostępnić funkcje akcesory, które podzielą ciąg na listę. Dzięki temu będziesz ograniczony do maksymalnej liczby ciągów i utracisz wydajne zapytania.

Martin przeciwko Löwis
źródło
3
Nic mi nie jest z bazą danych przechowującą to jako relację, miałem nadzieję, że modele Django już wyabstrahowały tę część dla mnie. Od strony aplikacji zawsze będę chciał traktować ją jako listę ciągów znaków.
opłakiwać
7

Jeśli używasz postgres, możesz użyć czegoś takiego:

class ChessBoard(models.Model):

    board = ArrayField(
        ArrayField(
            models.CharField(max_length=10, blank=True),
            size=8,
        ),
        size=8,
    )

jeśli potrzebujesz więcej informacji, możesz przeczytać w poniższym linku: https://docs.djangoproject.com/pt-br/1.9/ref/contrib/postgres/fields/

Marcos Souza
źródło
3

Przechowywanie listy ciągów w modelu 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

i możesz to nazwać tak:

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."      
Ahtisham
źródło
2

Moje rozwiązanie, może komuś pomoże:

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)
Stefanitsky
źródło
1

Użycie relacji jeden do wielu (FK z klasy Friend do klasy nadrzędnej) sprawi, że Twoja aplikacja będzie bardziej skalowalna (ponieważ możesz w trywialny sposób rozszerzyć obiekt Friend o dodatkowe atrybuty poza prostą nazwą). A więc to najlepszy sposób

Strzec
źródło
3
To nie jest skalowalność, to jest rozszerzalność. Często jedno jest kosztem drugiego. W takim przypadku, jeśli wiesz, że zawsze będziesz potrzebować listy ciągów, możesz uniknąć kosztownego łączenia, dzięki czemu twój kod będzie bardziej skalowalny (tj. Bardziej wydajny od denormalizacji).
Dustin Rasener
Powyższe z kilkoma zastrzeżeniami: 1) wiesz, że nigdy nie chcesz sprawdzać tych danych i 2) przechowywanie jest nadal tańsze niż moc obliczeniowa i pamięć (kto wie, może to się zmienia w obliczeniach kwantowych)
Dustin Rasener