django แบบจำลองนามธรรมเทียบกับการถ่ายทอดทางพันธุกรรมปกติ


87

นอกจากไวยากรณ์แล้วอะไรคือความแตกต่างระหว่างการใช้โมเดลนามธรรม django และการใช้การสืบทอด Python ธรรมดากับโมเดล django ข้อดีและข้อเสีย?

UPDATE: ฉันคิดว่าคำถามของฉันเข้าใจผิดและฉันได้รับคำตอบสำหรับความแตกต่างระหว่างโมเดลนามธรรมและคลาสที่สืบทอดมาจาก django.db.models.Model ฉันต้องการทราบความแตกต่างระหว่างคลาสโมเดลที่สืบทอดมาจากคลาส django abstract (Meta: abstract = True) และคลาส Python ธรรมดาที่สืบทอดมาจาก say, 'object' (ไม่ใช่ model.Model)

นี่คือตัวอย่าง:

class User(object):
   first_name = models.CharField(..

   def get_username(self):
       return self.username

class User(models.Model):
   first_name = models.CharField(...

   def get_username(self):
       return self.username

   class Meta:
       abstract = True

class Employee(User):
   title = models.CharField(...

1
นี่คือภาพรวมที่ดีของการแลกเปลี่ยนระหว่างการใช้สองวิธีการสืบทอดcharlesleifer.com/blog/django-patterns-model-inheritance
jpotts18

คำตอบ:


155

ฉันต้องการทราบความแตกต่างระหว่างคลาสโมเดลที่สืบทอดมาจากคลาส django abstract (Meta: abstract = True) และคลาส Python ธรรมดาที่สืบทอดมาจาก say, 'object' (ไม่ใช่ model.Model)

Django จะสร้างตารางสำหรับคลาสย่อยmodels.Modelเท่านั้นดังนั้นอดีต ...

class User(models.Model):
   first_name = models.CharField(max_length=255)

   def get_username(self):
       return self.username

   class Meta:
       abstract = True

class Employee(User):
   title = models.CharField(max_length=255)

... จะทำให้มีการสร้างตารางเดียวตามแนวของ ...

CREATE TABLE myapp_employee
(
    id         INT          NOT NULL AUTO_INCREMENT,
    first_name VARCHAR(255) NOT NULL,
    title      VARCHAR(255) NOT NULL,
    PRIMARY KEY (id)
);

... แต่อย่างหลัง ...

class User(object):
   first_name = models.CharField(max_length=255)

   def get_username(self):
       return self.username

class Employee(User):
   title = models.CharField(max_length=255)

... จะไม่ทำให้ตารางถูกสร้างขึ้น

คุณสามารถใช้มรดกหลายอย่างเพื่อทำสิ่งนี้ ...

class User(object):
   first_name = models.CharField(max_length=255)

   def get_username(self):
       return self.username

class Employee(User, models.Model):
   title = models.CharField(max_length=255)

... ซึ่งจะสร้างตาราง แต่จะไม่สนใจฟิลด์ที่กำหนดไว้ในUserคลาสดังนั้นคุณจะได้ตารางแบบนี้ ...

CREATE TABLE myapp_employee
(
    id         INT          NOT NULL AUTO_INCREMENT,
    title      VARCHAR(255) NOT NULL,
    PRIMARY KEY (id)
);

3
ขอบคุณสิ่งนี้ตอบคำถามของฉัน ยังไม่ชัดเจนว่าเหตุใดจึงไม่สร้าง first_name สำหรับพนักงาน
rpq

4
@rpq คุณต้องตรวจสอบซอร์สโค้ดของ Django แต่ IIRC ใช้การแฮ็กเมตาคลาสสิกบางอย่างmodels.Modelดังนั้นฟิลด์ที่กำหนดไว้ในซูเปอร์คลาสจะไม่ถูกเลือกขึ้นมา (เว้นแต่จะเป็นคลาสย่อยด้วยmodels.Model)
Aya

1
@rpq ฉันรู้ว่านี้เป็นเวลา 7 ปีที่ผ่านมาเมื่อคุณถาม แต่ในModelBase's __new__โทรจะดำเนินการตรวจสอบเบื้องต้นของชนชั้น' คุณลักษณะการตรวจสอบว่าค่าแอตทริบิวต์มีcontribute_to_classแอตทริบิวต์ - The Fieldsที่คุณกำหนดใน Django ทุกคนทำเพื่อให้พวกเขา จะรวมอยู่ในพจนานุกรมพิเศษที่เรียกว่าcontributable_attrsซึ่งท้ายที่สุดแล้วจะถูกส่งต่อระหว่างการย้ายข้อมูลเพื่อสร้าง DDL
Yu Chen

2
ทุกครั้งที่ฉันมองไปที่ DJANGO ฉันแค่อยากจะร้องไห้ทำไม ????? ทำไมเรื่องไร้สาระทั้งหมดทำไมกรอบจึงต้องมีพฤติกรรมที่แตกต่างจากภาษา แล้วหลักการของความประหลาดใจน้อยที่สุดล่ะ? พฤติกรรมนี้ (เช่นเกือบทุกอย่างอื่นใน django) ไม่ใช่สิ่งที่ใครก็ตามที่เข้าใจ python จะคาดหวัง
เซอร์รา

36

แบบจำลองนามธรรมจะสร้างตารางที่มีชุดคอลัมน์ทั้งหมดสำหรับลูกย่อยแต่ละรายการในขณะที่การใช้การสืบทอด Python "ธรรมดา" จะสร้างชุดของตารางที่เชื่อมโยง (หรือที่เรียกว่า "การสืบทอดหลายตาราง") พิจารณากรณีที่คุณมีสองรุ่น:

class Vehicle(models.Model):
  num_wheels = models.PositiveIntegerField()


class Car(Vehicle):
  make = models.CharField(…)
  year = models.PositiveIntegerField()

หากVehicleเป็นโมเดลนามธรรมคุณจะมีตารางเดียว:

app_car:
| id | num_wheels | make | year

อย่างไรก็ตามหากคุณใช้การสืบทอด Python ธรรมดาคุณจะมีสองตาราง:

app_vehicle:
| id | num_wheels

app_car:
| id | vehicle_id | make | model

ที่ไหน vehicle_idลิงก์ไปยังแถวapp_vehicleนั้นก็จะมีจำนวนล้อสำหรับรถด้วย

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


อัปเดต

เพื่อตอบคำถามที่อัปเดตของคุณความแตกต่างระหว่างการสืบทอดจากคลาสนามธรรม Django และการสืบทอดจาก Python objectคืออดีตจะถือว่าเป็นวัตถุฐานข้อมูล (ดังนั้นตารางจะถูกซิงค์กับฐานข้อมูล) และมีลักษณะการทำงานของไฟล์Model. การสืบทอดจาก Python ธรรมดาobjectทำให้คลาส (และคลาสย่อยของมัน) ไม่มีคุณสมบัติเหล่านั้น


1
การทำเช่นmodels.OneToOneField(Vehicle)นี้จะเทียบเท่ากับการสืบทอดคลาสโมเดลใช่ไหม? และนั่นจะส่งผลให้ตารางสองตารางแยกกันไม่ใช่เหรอ?
Rafay

1
@MohammadRafayAleem: ใช่ แต่เมื่อใช้การสืบทอด Django จะสร้างเช่นnum_wheelsแอตทริบิวต์บน a carในขณะที่OneToOneFieldคุณจะต้องทำการยกเลิกการอ้างอิงด้วยตัวคุณเอง
mipadi

ฉันจะบังคับด้วยการสืบทอดแบบปกติเพื่อให้ฟิลด์ทั้งหมดถูกสร้างขึ้นใหม่สำหรับapp_carตารางเพื่อให้มีnum_wheelsฟิลด์ด้วยแทนที่จะมีvehicle_idตัวชี้
Don Grem

@DorinGrecu: ทำให้คลาสพื้นฐานเป็นแบบจำลองนามธรรมและสืบทอดจากสิ่งนั้น
mipadi

@mipadi ปัญหาคือฉันไม่สามารถสร้างนามธรรมคลาสพื้นฐานได้มันถูกใช้ทั่วทั้งโครงการ
Don Grem

13

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

ถ้าคุณใช้ abstract = Trueสำหรับคลาสพื้นฐาน Django จะสร้างตารางสำหรับคลาสที่สืบทอดมาจากคลาสพื้นฐานเท่านั้นไม่ว่าฟิลด์จะถูกกำหนดในคลาสพื้นฐานหรือคลาสที่สืบทอดก็ตาม

ข้อดีและข้อเสียขึ้นอยู่กับสถาปัตยกรรมของแอปพลิเคชันของคุณ ให้โมเดลตัวอย่างต่อไปนี้:

class Publishable(models.Model):
    title = models.CharField(...)
    date = models.DateField(....)

    class Meta:
        # abstract = True

class BlogEntry(Publishable):
    text = models.TextField()


class Image(Publishable):
    image = models.ImageField(...)

ถ้าPublishableชั้นไม่ได้เป็นนามธรรม Django จะสร้างตารางสำหรับ publishables กับคอลัมน์titleและdateและตารางที่แยกต่างหากสำหรับและBlogEntry Imageข้อดีของโซลูชันนี้คือคุณสามารถค้นหาได้ข้อมูลที่เผยแพร่ทั้งหมดสำหรับฟิลด์ที่กำหนดไว้ในโมเดลพื้นฐานไม่ว่าจะเป็นรายการบล็อกหรือรูปภาพก็ตาม แต่ดังนั้น Django จะต้องทำการรวมหากคุณเช่นค้นหารูปภาพ ... หากการสร้างPublishable abstract = TrueDjango จะไม่สร้างตารางสำหรับPublishableแต่สำหรับรายการบล็อกและรูปภาพเท่านั้นที่มีฟิลด์ทั้งหมด (รวมถึงรายการที่สืบทอดมาด้วย) สิ่งนี้จะเป็นประโยชน์เพราะไม่จำเป็นต้องมีการเชื่อมต่อกับการดำเนินการเช่น get

ยังเห็นเอกสารของ Django ในรูปแบบการถ่ายทอดทางพันธุกรรม


9

แค่อยากจะเพิ่มบางสิ่งที่ฉันไม่เห็นในคำตอบอื่น ๆ

ไม่เหมือนกับคลาส python การซ่อนชื่อฟิลด์ไม่ได้รับอนุญาตกับการสืบทอดโมเดล

ตัวอย่างเช่นฉันได้ทดลองปัญหาเกี่ยวกับกรณีการใช้งานดังนี้:

ฉันมีโมเดลที่สืบทอดมาจากPermissionMixinรับรองความถูกต้องของ django :

class PermissionsMixin(models.Model):
    """
    A mixin class that adds the fields and methods necessary to support
    Django's Group and Permission model using the ModelBackend.
    """
    is_superuser = models.BooleanField(_('superuser status'), default=False,
        help_text=_('Designates that this user has all permissions without '
                    'explicitly assigning them.'))
    groups = models.ManyToManyField(Group, verbose_name=_('groups'),
        blank=True, help_text=_('The groups this user belongs to. A user will '
                                'get all permissions granted to each of '
                                'his/her group.'))
    user_permissions = models.ManyToManyField(Permission,
        verbose_name=_('user permissions'), blank=True,
        help_text='Specific permissions for this user.')

    class Meta:
        abstract = True

    # ...

จากนั้นผมก็มี mixin ของฉันซึ่งในสิ่งอื่น ๆ ที่ฉันอยากให้มันแทนที่related_nameของgroupsข้อมูล ดังนั้นมันจึงเป็นเช่นนี้มากหรือน้อย:

class WithManagedGroupMixin(object):
    groups = models.ManyToManyField(Group, verbose_name=_('groups'),
        related_name="%(app_label)s_%(class)s",
        blank=True, help_text=_('The groups this user belongs to. A user will '
                            'get all permissions granted to each of '
                            'his/her group.'))

ฉันใช้ 2 มิกซ์อินนี้ดังนี้:

class Member(PermissionMixin, WithManagedGroupMixin):
    pass

ใช่ฉันคาดหวังว่าสิ่งนี้จะได้ผล แต่ก็ไม่เป็นเช่นนั้น แต่ปัญหานั้นร้ายแรงกว่าเนื่องจากข้อผิดพลาดที่ฉันได้รับไม่ได้ชี้ไปที่โมเดลเลยฉันไม่รู้ว่าเกิดอะไรขึ้น

ในขณะที่พยายามแก้ปัญหานี้ฉันสุ่มตัดสินใจเปลี่ยนมิกซ์อินและแปลงเป็นมิกซ์อินโมเดลนามธรรม ข้อผิดพลาดเปลี่ยนเป็น:

django.core.exceptions.FieldError: Local field 'groups' in class 'Member' clashes with field of similar name from base class 'PermissionMixin'

อย่างที่คุณเห็นข้อผิดพลาดนี้อธิบายถึงสิ่งที่เกิดขึ้น

นี่เป็นความแตกต่างอย่างมากในความคิดของฉัน :)


ฉันมีคำถามที่ไม่เกี่ยวข้องกับคำถามหลักที่นี่ แต่คุณจัดการกับสิ่งนี้ได้อย่างไร (เพื่อแทนที่ฟิลด์ related_name ของกลุ่ม) ในตอนท้าย
kalo

@kalo IIRC ฉันเพิ่งเปลี่ยนชื่อเป็นอย่างอื่นทั้งหมด ไม่มีวิธีใดในการลบหรือแทนที่เขตข้อมูลที่สืบทอดมา
Adrián

ใช่ฉันเกือบจะพร้อมที่จะยอมแพ้กับสิ่งนี้แล้วเมื่อฉันพบวิธีที่จะทำได้โดยใช้เมธอด Contrib_to_class
kalo

1

ความแตกต่างหลักคือเมื่อคุณสืบทอดคลาส User เวอร์ชันหนึ่งจะทำตัวเหมือนคลาสธรรมดาและอีกเวอร์ชันจะทำงานเหมือนโมเดล Django

หากคุณสืบทอดเวอร์ชัน "object" พื้นฐานคลาสพนักงานของคุณจะเป็นคลาสมาตรฐานและ first_name จะไม่กลายเป็นส่วนหนึ่งของตารางฐานข้อมูล คุณไม่สามารถสร้างแบบฟอร์มหรือใช้คุณสมบัติ Django อื่น ๆ ได้

หากคุณสืบทอดโมเดลเวอร์ชันโมเดลคลาสพนักงานของคุณจะมีเมธอดทั้งหมดของ Django Modelและจะสืบทอดฟิลด์ first_name เป็นฟิลด์ฐานข้อมูลที่สามารถใช้ได้ในรูปแบบ

ตามเอกสารประกอบโมเดลบทคัดย่อ "ให้วิธีแยกข้อมูลทั่วไปในระดับ Python ในขณะที่ยังคงสร้างตารางฐานข้อมูลเพียงตารางเดียวต่อโมเดลลูกในระดับฐานข้อมูล"


0

ฉันจะชอบคลาสนามธรรมในกรณีส่วนใหญ่เพราะมันไม่ได้สร้างตารางแยกต่างหากและ ORM ไม่จำเป็นต้องสร้างการรวมในฐานข้อมูล และการใช้คลาสนามธรรมนั้นค่อนข้างง่ายใน Django

class Vehicle(models.Model):
    title = models.CharField(...)
    Name = models.CharField(....)

    class Meta:
         abstract = True

class Car(Vehicle):
    color = models.CharField()

class Bike(Vehicle):
    feul_average = models.IntegerField(...)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.