mô hình trừu tượng django so với kế thừa thông thường


86

Ngoài cú pháp, sự khác biệt giữa việc sử dụng mô hình trừu tượng django và sử dụng kế thừa Python đơn giản với các mô hình django là gì? Ưu và nhược điểm?

CẬP NHẬT: Tôi nghĩ câu hỏi của mình đã bị hiểu nhầm và tôi đã nhận được câu trả lời về sự khác biệt giữa mô hình trừu tượng và một lớp kế thừa từ django.db.models.Model. Tôi thực sự muốn biết sự khác biệt giữa một lớp mô hình kế thừa từ một lớp trừu tượng django (Meta: abstract = True) và một lớp Python thuần túy kế thừa từ say, 'object' (chứ không phải là models.Model).

Đây là một ví dụ:

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
Đây là tổng quan tuyệt vời về sự đánh đổi giữa việc sử dụng hai phương pháp kế thừa charlesleifer.com/blog/django-patterns-model-inheritance
jpotts18

Câu trả lời:


152

Tôi thực sự muốn biết sự khác biệt giữa một lớp mô hình kế thừa từ một lớp trừu tượng django (Meta: abstract = True) và một lớp Python thuần túy kế thừa từ say, 'object' (chứ không phải là models.Model).

Django sẽ chỉ tạo bảng cho các lớp con của models.Model, vì vậy ...

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)

... sẽ tạo ra một bảng duy nhất, dọc theo các dòng của ...

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

... trong khi cái sau ...

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)

... sẽ không tạo ra bất kỳ bảng nào.

Bạn có thể sử dụng đa kế thừa để làm điều gì đó như thế này ...

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)

... sẽ tạo một bảng, nhưng nó sẽ bỏ qua các trường được xác định trong Userlớp, vì vậy bạn sẽ kết thúc với một bảng như thế này ...

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

3
Cảm ơn, điều này trả lời câu hỏi của tôi. Vẫn chưa rõ tại sao first_name không được tạo cho Nhân viên.
rpq

4
@rpq Bạn sẽ phải kiểm tra mã nguồn của Django, nhưng IIRC nó sử dụng một số phương pháp hack siêu kính models.Model, vì vậy các trường được xác định trong lớp cha sẽ không được chọn (trừ khi chúng cũng là lớp con của models.Model).
Aya

@rpq Tôi biết điều này là 7 năm trước, khi bạn hỏi, nhưng trong ModelBase's __new__cuộc gọi, nó thực hiện một kiểm tra ban đầu của lớp' thuộc tính, kiểm tra nếu giá trị thuộc tính có một contribute_to_classthuộc tính - các Fieldsmà bạn xác định trong Django bài làm, vì vậy họ được đưa vào một từ điển đặc biệt được gọi là từ điển contributable_attrscuối cùng được chuyển trong quá trình di chuyển để tạo DDL.
Yu Chen

1
MỖI LẦN NHÌN VÀO DJANGO tôi chỉ muốn khóc, tại sao ????? Tại sao tất cả những điều vô nghĩa, tại sao khuôn khổ phải hoạt động theo một cách hoàn toàn khác với ngôn ngữ? Còn nguyên tắc ít ngạc nhiên nhất thì sao? Hành vi này (giống như ALMOST mọi thứ khác trong django) không phải là điều mà bất kỳ ai hiểu python đều mong đợi.
E.Serra

36

Một mô hình trừu tượng tạo ra một bảng với toàn bộ tập hợp các cột cho mỗi con con, trong khi sử dụng kế thừa Python "thuần túy" sẽ tạo ra một tập hợp các bảng được liên kết (hay còn gọi là "kế thừa nhiều bảng"). Hãy xem xét trường hợp bạn có hai mô hình:

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


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

Nếu Vehiclelà một mô hình trừu tượng, bạn sẽ có một bảng:

app_car:
| id | num_wheels | make | year

Tuy nhiên, nếu bạn sử dụng kế thừa Python thuần túy, bạn sẽ có hai bảng:

app_vehicle:
| id | num_wheels

app_car:
| id | vehicle_id | make | model

Trong trường hợp vehicle_idlà một liên kết đến một hàng trong app_vehicleđó cũng sẽ có số lượng bánh xe cho xe.

Bây giờ, Django sẽ đặt điều này lại với nhau một cách độc đáo dưới dạng đối tượng để bạn có thể truy cập num_wheelsdưới dạng một thuộc tính trên đó Car, nhưng cách biểu diễn bên dưới trong cơ sở dữ liệu sẽ khác.


Cập nhật

Để giải quyết câu hỏi đã cập nhật của bạn, sự khác biệt giữa kế thừa từ lớp trừu tượng Django và kế thừa từ lớp Python objectlà cái trước đó được coi là một đối tượng cơ sở dữ liệu (vì vậy các bảng của nó được đồng bộ hóa với cơ sở dữ liệu) và nó có hành vi như a Model. Việc kế thừa từ một Python thuần túy objectmang lại cho lớp (và các lớp con của nó) không có phẩm chất nào trong số đó.


1
Việc làm models.OneToOneField(Vehicle)cũng tương đương với việc kế thừa một lớp mô hình, phải không? Và điều đó sẽ dẫn đến hai bảng riêng biệt, phải không?
Rafay

1
@MohammadRafayAleem: Có, nhưng khi sử dụng kế thừa, Django sẽ tạo, ví dụ: num_wheelsmột thuộc tính trên a car, trong khi với một, OneToOneFieldbạn sẽ phải tự thực hiện việc tham chiếu đó.
mipadi

Làm thế nào tôi có thể buộc với kế thừa thông thường rằng tất cả các trường được tạo lại cho app_carbảng để nó cũng có num_wheelstrường, thay vì có vehicle_idcon trỏ?
Don Grem

@DorinGrecu: Đặt lớp cơ sở thành một mô hình trừu tượng và kế thừa từ đó.
mipadi

@mipadi, vấn đề là tôi không thể làm cho lớp cơ sở trừu tượng, nó đang được sử dụng trong toàn bộ dự án.
Don Grem

13

Sự khác biệt chính là cách tạo bảng cơ sở dữ liệu cho các mô hình. Nếu bạn sử dụng kế thừa mà không có abstract = TrueDjango sẽ tạo một bảng riêng biệt cho cả mô hình mẹ và mô hình con chứa các trường được xác định trong mỗi mô hình.

Nếu bạn sử dụng abstract = Truecho lớp cơ sở, Django sẽ chỉ tạo một bảng cho các lớp kế thừa từ lớp cơ sở - bất kể trường được xác định trong lớp cơ sở hay lớp kế thừa.

Ưu và nhược điểm phụ thuộc vào kiến ​​trúc của ứng dụng của bạn. Đưa ra các mô hình ví dụ sau:

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

Nếu Publishablelớp này không phải là trừu tượng, Django sẽ tạo một bảng cho các mục có thể xuất bản với các cột titledatevà các bảng riêng biệt cho BlogEntryImage. Ưu điểm của giải pháp này là bạn có thể truy vấn trên tất cả các trường có thể xuất bản cho các trường được xác định trong mô hình cơ sở, bất kể chúng là mục blog hay hình ảnh. Nhưng do đó Django sẽ phải thực hiện các phép nối nếu bạn ví dụ như truy vấn hình ảnh ... Nếu tạo Publishable abstract = TrueDjango sẽ không tạo một bảng cho Publishable, mà chỉ cho các mục blog và hình ảnh, chứa tất cả các trường (cũng là những trường được kế thừa). Điều này sẽ rất hữu ích vì không cần nối đối với một hoạt động như get.

Cũng xem tài liệu của Django về kế thừa mô hình .


9

Tôi chỉ muốn thêm một cái gì đó mà tôi chưa thấy trong các câu trả lời khác.

Không giống như với các lớp python, ẩn tên trường không được phép với kế thừa mô hình.

Ví dụ: tôi đã thử nghiệm các vấn đề với một ca sử dụng như sau:

Tôi đã có một mô hình kế thừa từ PermissionMixin auth của 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

    # ...

Sau đó, tôi có mixin của tôi mà trong số những thứ khác tôi muốn nó ghi đè lên related_namecác groupslĩnh vực. Vì vậy, nó ít nhiều giống như thế này:

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.'))

Tôi đã sử dụng 2 mixin này như sau:

class Member(PermissionMixin, WithManagedGroupMixin):
    pass

Vì vậy, tôi mong đợi điều này sẽ hoạt động nhưng nó đã không. Nhưng vấn đề nghiêm trọng hơn vì lỗi mà tôi nhận được không chỉ vào các mô hình, tôi không biết điều gì đang xảy ra.

Trong khi cố gắng giải quyết vấn đề này, tôi ngẫu nhiên quyết định thay đổi mixin của mình và chuyển nó thành mixin mô hình trừu tượng. Lỗi đã thay đổi thành thế này:

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

Như bạn có thể thấy, lỗi này giải thích những gì đang xảy ra.

Đây là một sự khác biệt rất lớn, theo ý kiến ​​của tôi :)


Tôi có một câu hỏi không liên quan đến câu hỏi chính ở đây, nhưng làm thế nào bạn quản lý để triển khai điều này (để ghi đè tên_của trường nhóm) ở cuối?
kalo

@kalo IIRC Tôi vừa đổi tên hoàn toàn thành một thứ khác. Không có cách nào để xóa hoặc thay thế các trường kế thừa.
Adrián

Có, tôi gần như đã sẵn sàng từ bỏ điều này, khi tôi tìm ra cách để thực hiện nó bằng cách sử dụng phương thức donate_to_class.
kalo

1

Sự khác biệt chính là khi bạn kế thừa lớp Người dùng. Một phiên bản sẽ hoạt động như một lớp đơn giản, và phiên bản kia sẽ hoạt động như một chế độ Django.

Nếu bạn kế thừa phiên bản "đối tượng" cơ sở, lớp Nhân viên của bạn sẽ chỉ là một lớp tiêu chuẩn và first_name sẽ không trở thành một phần của bảng cơ sở dữ liệu. Bạn không thể tạo biểu mẫu hoặc sử dụng bất kỳ tính năng Django nào khác với nó.

Nếu bạn kế thừa phiên bản Model.Model, lớp Employee của bạn sẽ có tất cả các phương thức của Mô hình Django và nó sẽ kế thừa trường first_name dưới dạng trường cơ sở dữ liệu có thể được sử dụng trong một biểu mẫu.

Theo tài liệu, Mô hình trừu tượng "cung cấp một cách để phân tích thông tin phổ biến ở cấp Python, trong khi vẫn chỉ tạo một bảng cơ sở dữ liệu cho mỗi mô hình con ở cấp cơ sở dữ liệu."


0

Tôi sẽ thích lớp trừu tượng hơn trong hầu hết các trường hợp vì nó không tạo một bảng riêng biệt và ORM không cần tạo các phép nối trong cơ sở dữ liệu. Và việc sử dụng lớp trừu tượng khá đơn giản trong 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(...)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.