Який найефективніший спосіб зберігання списку в моделях Django?


146

На даний момент у моєму коді багато об'єктів python, подібних до наступних:

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

Тепер я хочу перетворити це на модель Django, де self.myName - це рядок, а self.myFriends - це список рядків.

from django.db import models

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

Оскільки список є такою поширеною структурою даних у python, я начебто очікував, що там буде поле моделі Джанго. Я знаю, що можу використовувати відносини ManyToMany або OneToMany, але я сподівався уникнути цього додаткового непрямого коду.

Редагувати:

Я додав це пов’язане питання , яке може бути корисним для людей.


1
@drozzy: Ну, мабуть, я міг би використати іншу фразу, але в основному те, що я мав на увазі, якщо я хотів перейти до списку рядків і повернути список рядків. Я не хочу створювати купу об’єктів Friend, а закликаю inst.myFriends.add (friendObj) для кожного з них. Не те, щоб було б все так важко, але ...
сумуй

Відповіді:


77

Чи не було б це відношення краще виражатись як один-багато-багато іноземних ключових відносин до Friendsтаблиці? Я розумію, що myFriendsце лише рядки, але я думаю, що кращим дизайном було б створити Friendмодель і MyClassмістити зовнішнє ключове поняття до отриманої таблиці.


15
Це, мабуть, те, що я в кінцевому підсумку робитиму, але я дуже сподівався, що основна структура для цього буде вбудована. Напевно, я про ледачий.
скорботи

Елегантний і найкрасивіше пояснений.
Tessaracter


129

"Передчасна оптимізація - корінь усього зла".

Маючи це на увазі, давайте зробимо це! Як тільки ваші програми потрапляють у певний момент, денормалізація даних є дуже поширеною. Зроблено правильно, це може заощадити численні дорогі пошукові бази даних вартістю трохи більше ведення господарства.

Щоб повернути a list імен друзів нам потрібно створити користувацький клас поля Django, який повертає список при зверненні.

Девід Креймер розмістив у своєму блозі керівництво зі створення SeperatedValueField. Ось код:

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)

Логіка цього коду стосується серіалізації та десеріалізації значень із бази даних на Python та навпаки. Тепер ви можете легко імпортувати та використовувати наше спеціальне поле у ​​класі моделі:

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

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

8
+1 за чудову відповідь, але ми вже робимо щось подібне. Це дійсно стискання всіх значень в одну рядок, а потім їх розділення. Напевно, я сподівався на щось подібне до ListofStringsField, яке фактично будує окрему таблицю і автоматично робить зовнішні ключі. Я не впевнений, чи можливо це в Джанго. Якщо це так, і я знайду відповідь, опублікую його в stackoverflow.
скорбота

2
Якщо це так, то ви шукаєте django-denorm initcrash. Ви знайдете його на github: github.com/initcrash/django-denorm/tree/master
jb.

3
+1. Але можливі проблеми з комами в рядках. А як щодо серіалізації та десеріалізації від json?
sbeliakov

Намагаються додати це до існуючої моделі, my_vals = SeparatedValuesField(blank=True, default="")але отримують IntegrityError через NULL. Чи аргумент за замовчуванням не передається правильно?
Джон Леман

1
Зауважте, що в Django 2.1 to_pythonбільше не закликається читати. Таким чином, щоб зробити цю роботу, вам потрібно додати: def from_db_value(self, value, expression, connection, context): return self.to_python(value)
theadriangreen

46

Простий спосіб зберігання списку в Django - це просто перетворити його в рядок JSON, а потім зберегти його як текст у моделі. Потім ви можете отримати список, перетворивши рядок (JSON) назад у список пітонів. Ось як:

"Список" буде зберігатися у вашій моделі Джанго так:

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

У коді перегляду / контролера:

Зберігання списку в базі даних:

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

Отримання списку з бази даних:

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

Концептуально ось що відбувається:

>>> 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
На жаль, це не допоможе вам керувати списком за допомогою адміністратора django
GreenAsJade

25

Якщо ви використовуєте Django> = 1.9 з Postgres, ви можете використовувати переваги ArrayField

Поле для зберігання списків даних. Можна використовувати більшість типів полів, ви просто передаєте інший екземпляр поля як базове поле. Ви також можете вказати розмір. ArrayField можна вкладати для зберігання багатовимірних масивів.

Також можливо вкласти поля масивів:

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

Як зазначав @ thane-brimhall, можна також безпосередньо запитувати елементи. Довідка щодо документації


2
Великою перевагою цього є те, що ви можете запитувати елементи безпосередньо з поля масиву.
Thane Brimhall

@ThaneBrimhall ви праві. Можливо, я повинен оновити відповідь з цією інформацією. Дякую
wolendranh

На жаль, рішення для mysql не знайдено
Джоель Г Метью

Слід зазначити, що це працює лише з PostGres.
theadriangreen

1
У Django 1.8 також є ArrayField: docs.djangoproject.com/en/1.8/ref/contrib/postgres/fields
kontextify

15

Оскільки це старе питання, і методи Джанго, мабуть, суттєво змінилися, оскільки ця відповідь відображає версію Джанго 1.4 і, швидше за все, застосовується для версії 1.5.

Django за замовчуванням використовує реляційні бази даних; вам слід скористатися ними. Позначте дружбу з відносинами баз даних (обмеження іноземних ключів) із використанням ManyToManyField. Це дозволяє використовувати RelatedManagers для списків друзів, які використовують розумні набори запитів. Ви можете використовувати всі доступні методи, такі як filterабоvalues_list .

Використання ManyToManyFieldвідносин та властивостей:

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

Ви можете отримати доступ до списку друзів користувача таким чином:

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

Однак зауважте, що ці відносини симетричні: якщо Джозеф є другом Боба, то Боб є другом Джозефа.


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

Пам'ятайте, що це врешті-решт повинно опинитися у реляційній базі даних. Тож використання відносин насправді є загальним способом вирішення цієї проблеми. Якщо ви абсолютно наполягаєте на збереженні списку в самому об'єкті, ви можете зробити його, наприклад, розділеним комами, і зберегти його в рядку, а потім надати функції доступу, які розділяють рядок на список. З цим ви обмежитеся максимальною кількістю рядків, і ви втратите ефективні запити.


3
Я добре з базою даних, яка зберігає це як відношення, я сподівався, що моделі Джанго вже віддалили цю частину для мене. З боку програми я завжди хочу вважати це списком рядків.
журба

7

Якщо ви використовуєте postgres, ви можете використовувати щось подібне:

class ChessBoard(models.Model):

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

якщо вам потрібна додаткова інформація, ви можете прочитати за посиланням нижче: https://docs.djangoproject.com/pt-br/1.9/ref/contrib/postgres/fields/


4

Ви можете зберігати практично будь-який об'єкт, використовуючи поле Django Pickle, ала цей фрагмент:

http://www.djangosnippets.org/snippets/513/


3
Ні, це не так. Прочитайте опис фрагмента.
Гарольд

Новий pypi.python.org/pypi/django-picklefield виконує те саме завдання (також незалежне від db)
Massagran

3

Збереження списку рядків у моделі Джанго:

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

і ви можете назвати це так:

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

Використання відношення "один до багатьох" (FK від "Друг до батьківського класу") зробить вашу програму більш масштабованою (оскільки ви можете тривіально розширити об'єкт Friend додатковими атрибутами, що перевищують просте ім'я). І таким чином це найкращий спосіб


3
Це не масштабованість, це розширюваність. Часто одне коштує інше. У цьому випадку, якщо ви знаєте, що вам завжди знадобиться список рядків, ви можете уникнути дорогого приєднання, тим самим зробивши свій код більш масштабованим (тобто більш ефективним щодо денормалізації).
Дастін Разенер

Наведене вище з кількома застереженнями: 1) ви знаєте, що ніколи не хочете запитувати проти цих даних, і 2) зберігання все ще дешевше, ніж обробка потужності та пам'яті (хто знає, можливо, це змінюється при квантових обчисленнях)
Дастін Разенер

1

Моє рішення, можливо, воно допомагає комусь:

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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.