วิธีที่มีประสิทธิภาพมากที่สุดในการจัดเก็บรายการในรุ่น Django คืออะไร?


146

ขณะนี้ฉันมีวัตถุหลามจำนวนมากในรหัสของฉันคล้ายกับต่อไปนี้:

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?

เนื่องจากรายการดังกล่าวเป็นโครงสร้างข้อมูลทั่วไปในไพ ธ อนฉันจึงคาดหวังว่าจะมีฟิลด์โมเดล Django สำหรับมัน ฉันรู้ว่าฉันสามารถใช้ความสัมพันธ์ ManyToMany หรือ OneToMany ได้ แต่ฉันหวังว่าจะหลีกเลี่ยงการใช้รหัสพิเศษนี้

แก้ไข:

ฉันเพิ่มคำถามที่เกี่ยวข้องซึ่งผู้คนอาจพบว่ามีประโยชน์


1
@drozzy: ดีฉันอาจจะได้ใช้วลีที่แตกต่างกัน แต่โดยทั่วไปสิ่งที่ฉันหมายถึงคือฉันต้องการที่จะผ่านในรายการของสตริงและกลับรายการของสตริง ฉันไม่ต้องการสร้างกลุ่มวัตถุเพื่อนและโทรไปที่ inst.myFriends.add (friendObj) สำหรับแต่ละรายการ ไม่ว่ามันจะเป็นสิ่งที่ยาก แต่ ...
เสียใจ

คำตอบ:


77

ความสัมพันธ์นี้จะไม่ถูกแสดงให้ดีขึ้นในฐานะความสัมพันธ์สำคัญต่างประเทศแบบหนึ่งต่อหลายคนในFriendsตารางหรือไม่ ฉันเข้าใจว่าmyFriendsเป็นเพียงสายอักขระ แต่ฉันคิดว่าการออกแบบที่ดีกว่านั้นคือการสร้างFriendแบบจำลองและมีMyClassการแบ่งส่วนสำคัญต่างประเทศให้กับตารางผลลัพธ์


15
นี่อาจเป็นสิ่งที่ฉันจะทำ แต่ฉันหวังว่าโครงสร้างพื้นฐานสำหรับสิ่งนี้จะถูกสร้างขึ้นมาฉันเดาว่าฉันจะขี้เกียจ
เสียใจ

สง่างามและสวยงามที่สุด
Tessaracter


129

"การเพิ่มประสิทธิภาพก่อนวัยอันควรเป็นรากฐานของความชั่วร้ายทั้งหมด"

ด้วยใจที่แน่วแน่มาทำสิ่งนี้กันเถอะ! เมื่อแอพของคุณเข้าสู่จุดหนึ่งการทำให้ข้อมูลผิดปกติเป็นเรื่องปกติ ทำอย่างถูกต้องมันสามารถบันทึกการค้นหาฐานข้อมูลราคาแพงจำนวนมากที่ค่าใช้จ่ายของการดูแลทำความสะอาดอีกเล็กน้อย

ในการส่งคืนlistชื่อเพื่อนเราจะต้องสร้างคลาส Django Field ที่กำหนดเองซึ่งจะส่งคืนรายการเมื่อเข้าถึง

David Cramer โพสต์คำแนะนำในการสร้าง 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 ซึ่งสร้างตารางแยกต่างหากและทำให้ปุ่มต่างประเทศโดยอัตโนมัติ ฉันไม่แน่ใจว่าเป็นไปได้ที่ Django หรือไม่ หากเป็นและฉันพบคำตอบฉันจะโพสต์ไว้ใน stackoverflow
เสียใจ

2
หากเป็นกรณีนี้คุณกำลังมองหา django-denorm ของ initcrash คุณจะพบมันได้ที่ github: github.com/initcrash/django-denorm/tree/master
jb

3
+1 แต่ปัญหาที่เป็นไปได้ด้วยเครื่องหมายจุลภาคในสตริง สิ่งที่เกี่ยวกับการเป็นอันดับและ deserializing จาก JSON?
sbeliakov

กำลังพยายามเพิ่มสิ่งนี้ลงในโมเดลที่มีอยู่ด้วยmy_vals = SeparatedValuesField(blank=True, default="")แต่รับ IntegrityError เนื่องจาก NULLs อาร์กิวเมนต์เริ่มต้นไม่ผ่านอย่างถูกต้องหรือไม่
John Lehmann

1
โปรดทราบว่าใน Django 2.1 to_pythonจะไม่ถูกเรียกเมื่ออ่านอีกต่อไป ดังนั้นเพื่อให้งานนี้คุณต้องเพิ่ม: def from_db_value(self, value, expression, connection, context): return self.to_python(value)
theadriangreen

46

วิธีง่าย ๆ ในการจัดเก็บรายการใน Django คือเพียงแปลงเป็นสตริง JSON แล้วบันทึกเป็น Text ในโมเดล คุณสามารถดึงรายการโดยการแปลงสตริง (JSON) กลับไปเป็นรายการหลาม นี่คือวิธี:

"รายการ" จะถูกเก็บไว้ในรุ่น Django ของคุณดังนี้:

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 admin
GreenAsJade

25

หากคุณใช้ Django> = 1.9 กับ Postgres คุณสามารถใช้ประโยชน์จากArrayFieldได้

ฟิลด์สำหรับจัดเก็บรายการข้อมูล สามารถใช้ประเภทฟิลด์ส่วนใหญ่ได้คุณเพียงส่งผ่านอินสแตนซ์ของฟิลด์อื่นเป็น base_field คุณอาจระบุขนาด 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
Joel G Mathew

ควรกล่าวว่าสิ่งนี้ใช้ได้กับ PostGres เท่านั้น
theadriangreen

1
Django 1.8 มี ArrayField เช่นกัน: docs.djangoproject.com/en/1.8/ref/contrib/postgres/fields
kontextify

15

เนื่องจากเป็นคำถามเก่าและเทคนิค Django ต้องเปลี่ยนไปอย่างมากเนื่องจากคำตอบนี้สะท้อนถึง Django เวอร์ชัน 1.4 และน่าจะใช้ได้กับ v 1.5

Django โดยค่าเริ่มต้นใช้ฐานข้อมูลเชิงสัมพันธ์ คุณควรใช้ 'em จัดทำแผนที่มิตรภาพกับความสัมพันธ์ของฐานข้อมูล (ข้อ จำกัด กุญแจต่างประเทศ) กับการใช้ ManyToManyField การทำเช่นนั้นอนุญาตให้คุณใช้ RelatedManagers สำหรับรายการเพื่อนซึ่งใช้ชุดการสืบค้นอัจฉริยะ คุณสามารถใช้วิธีการที่ใช้ได้ทั้งหมดเช่นหรือfiltervalues_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

จำไว้ว่าสิ่งนี้จะต้องจบลงในฐานข้อมูลเชิงสัมพันธ์ ดังนั้นการใช้ความสัมพันธ์จึงเป็นวิธีทั่วไปในการแก้ปัญหานี้ หากคุณยืนยันการจัดเก็บรายการในวัตถุอย่างแน่นอนคุณสามารถสร้างมันขึ้นมาได้เช่นคั่นด้วยเครื่องหมายจุลภาคและเก็บไว้ในสตริงจากนั้นจัดเตรียมฟังก์ชัน accessor ที่แยกสตริงออกเป็นรายการ ด้วยสิ่งนี้คุณจะถูก จำกัด จำนวนสูงสุดของสตริงและคุณจะสูญเสียการค้นหาที่มีประสิทธิภาพ


3
ฉันพอใจกับฐานข้อมูลที่จัดเก็บเป็นความสัมพันธ์ฉันหวังว่าแบบจำลอง Django จะแยกส่วนที่ออกมาให้ฉันแล้ว จากด้านแอพฉันมักจะต้องการที่จะถือมันเป็นรายการของสตริง
เสียใจ

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 Field เช่นเดียวกับตัวอย่างนี้:

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


3
ไม่มันไม่ใช่ อ่านคำอธิบายของตัวอย่าง
แฮโรลด์

pypi.python.org/pypi/django-picklefieldใหม่ทำหน้าที่เดียวกัน (เช่นเดียวกันกับฐานข้อมูลอิสระ)
Massagran

3

การจัดเก็บรายการสตริงในโมเดล 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

และคุณสามารถเรียกมันว่าสิ่งนี้:

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 จากเพื่อนถึงระดับผู้ปกครอง) จะทำให้แอปของคุณสามารถปรับขนาดได้มากขึ้น (เนื่องจากคุณสามารถขยายวัตถุเพื่อนด้วยคุณลักษณะเพิ่มเติมนอกเหนือจากชื่อแบบง่าย) และนี่คือวิธีที่ดีที่สุด


3
นั่นไม่ใช่ความสามารถในการปรับขยายได้ บ่อยครั้งที่หนึ่งเป็นค่าใช้จ่ายของอื่น ๆ ในกรณีนี้หากคุณรู้ว่าคุณจะต้องมีรายการสตริงคุณสามารถหลีกเลี่ยงการเข้าร่วมที่มีราคาแพงได้ซึ่งจะทำให้โค้ดของคุณสามารถปรับขนาดได้มากขึ้น (เช่นมีประสิทธิภาพมากกว่าจากการทำให้เป็นปกติ)
Dustin Rasener

ดังกล่าวข้างต้นกับคู่ของคำเตือน: 1 ที่) คุณรู้ว่าคุณไม่ต้องการที่จะใช้แบบสอบถามกับข้อมูลที่และ 2) การจัดเก็บข้อมูลยังคงเป็นราคาถูกกว่าพลังการประมวลผลและหน่วยความจำ (ใครจะรู้บางทีการเปลี่ยนแปลงนี้กับควอนตัมคอมพิวเตอร์)
ดัสติน Rasener

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)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.