Trong Django - Kế thừa mô hình - Nó có cho phép bạn ghi đè thuộc tính của mô hình mẹ không?


99

Tôi đang tìm cách làm điều này:

class Place(models.Model):
   name = models.CharField(max_length=20)
   rating = models.DecimalField()

class LongNamedRestaurant(Place):  # Subclassing `Place`.
   name = models.CharField(max_length=255)  # Notice, I'm overriding `Place.name` to give it a longer length.
   food_type = models.CharField(max_length=25)

Đây là phiên bản tôi muốn sử dụng (mặc dù tôi sẵn sàng đón nhận bất kỳ đề xuất nào): http://docs.djangoproject.com/en/dev/topics/db/models/#id7

Điều này có được hỗ trợ trong Django không? Nếu không, có cách nào để đạt được kết quả tương tự?


bạn có thể vui lòng chấp nhận câu trả lời dưới đây, từ django 1.10, nó có thể :)
holms

@holms chỉ khi lớp cơ sở là trừu tượng!
Micah Walter

Câu trả lời:


64

Câu trả lời cập nhật: như mọi người đã lưu ý trong nhận xét, câu trả lời ban đầu không trả lời đúng câu hỏi. Thật vậy, chỉ có LongNamedRestaurantmô hình được tạo trong cơ sở dữ liệu, Placekhông.

Một giải pháp là tạo một mô hình trừu tượng đại diện cho một "Địa điểm", ví dụ. AbstractPlacevà kế thừa từ nó:

class AbstractPlace(models.Model):
    name = models.CharField(max_length=20)
    rating = models.DecimalField()

    class Meta:
        abstract = True

class Place(AbstractPlace):
    pass

class LongNamedRestaurant(AbstractPlace):
    name = models.CharField(max_length=255)
    food_type = models.CharField(max_length=25)

Vui lòng đọc câu trả lời của @Mark , anh ấy đưa ra lời giải thích tuyệt vời tại sao bạn không thể thay đổi các thuộc tính được thừa kế từ một lớp không trừu tượng.

(Lưu ý rằng điều này chỉ có thể thực hiện được vì Django 1.10: trước Django 1.10, không thể sửa đổi thuộc tính được kế thừa từ một lớp trừu tượng.)

Câu trả lời ban đầu

Kể từ Django 1.10, nó có thể ! Bạn chỉ cần làm những gì bạn yêu cầu:

class Place(models.Model):
    name = models.CharField(max_length=20)
    rating = models.DecimalField()

    class Meta:
        abstract = True

class LongNamedRestaurant(Place):  # Subclassing `Place`.
    name = models.CharField(max_length=255)  # Notice, I'm overriding `Place.name` to give it a longer length.
    food_type = models.CharField(max_length=25)

8
Địa điểm cần phải trừu tượng, không?
DylanYoung

4
Tôi không nghĩ rằng tôi đã trả lời một câu hỏi khác vì tôi chỉ nói rằng mã được đăng trong câu hỏi hiện đang hoạt động kể từ Django 1.10. Lưu ý rằng theo liên kết mà anh ấy đã đăng về những gì anh ấy muốn sử dụng, anh ấy đã quên đặt lớp Place thành trừu tượng.
qmarlats

2
Không chắc tại sao đây là câu trả lời được chấp nhận ... OP đang sử dụng kế thừa đa bảng. Câu trả lời này chỉ hợp lệ cho các lớp cơ sở trừu tượng.
MrName

1
lớp trừu tượng đã có sẵn từ lâu trước khi Django 1.10
rbennell

1
@NoamG Trong câu trả lời ban đầu của tôi, Placelà trừu tượng, do đó nó không được tạo trong cơ sở dữ liệu. Nhưng OP muốn cả hai PlaceLongNamedRestaurantđược tạo trong cơ sở dữ liệu. Vì vậy, tôi đã cập nhật câu trả lời của mình để thêm AbstractPlacemô hình, là mô hình “cơ sở” (tức là. Trừu tượng) cả PlaceLongNamedRestaurantkế thừa từ đó. Bây giờ cả hai PlaceLongNamedRestaurantđược tạo trong cơ sở dữ liệu, như OP đã yêu cầu.
qmarlats

61

Không, nó không phải là :

Tên trường "ẩn" không được phép

Trong kế thừa lớp Python thông thường, lớp con được phép ghi đè bất kỳ thuộc tính nào từ lớp cha. Trong Django, điều này không được phép đối với các thuộc tính là Fieldphiên bản (ít nhất, không phải tại thời điểm này). Nếu một lớp cơ sở có một trường được gọi author, bạn không thể tạo một trường mô hình khác được gọi authortrong bất kỳ lớp nào kế thừa từ lớp cơ sở đó.


11
Xem câu trả lời của tôi cho lý do tại sao nó không thể. Mọi người thích điều này vì nó có ý nghĩa, nó không rõ ràng ngay lập tức.
Đánh dấu

4
@ leo-the-manic Tôi nghĩ User._meta.get_field('email').required = Truecó thể hoạt động, không chắc là nghĩ.
Jens Timmerman

@ leo-the-manic, @JensTimmerman, @utapyngo Đặt giá trị thuộc tính cho lớp của bạn sẽ không ảnh hưởng đến các trường được kế thừa. Bạn cần phải hành động trên _metacủa lớp cha mẹ, ví dụ MyParentClass._meta.get_field('email').blank = False(để thực hiện một kế thừa emaillĩnh vực bắt buộc trong quản lý)
Peterino

1
Rất tiếc, xin lỗi, mã của @ utapyngo ở trên là chính xác, nhưng sau đó nó phải được đặt bên ngoài nội dung lớp! Đặt trường lớp cha 'như tôi đã đề xuất có thể có tác dụng phụ không mong muốn.
Peterino

Tôi muốn một trường trong mỗi lớp con có kiểu khác với một trường có cùng tên trong lớp cha trừu tượng để đảm bảo rằng tất cả các lớp con đều có một trường có tên nhất định. Mã của utapyngo không đáp ứng được nhu cầu này.
Daniel

28

Điều đó là không thể trừ khi trừu tượng, và đây là lý do: LongNamedRestaurantcũng là a Place, không chỉ là một lớp mà còn trong cơ sở dữ liệu. Bảng vị trí chứa một mục nhập cho mọi thuần túy Placevà cho mọi LongNamedRestaurant. LongNamedRestaurantchỉ tạo một bảng bổ sung với food_typevà một tham chiếu đến bảng vị trí.

Nếu bạn làm vậy Place.objects.all(), bạn cũng nhận được mọi vị trí là a LongNamedRestaurant, và nó sẽ là một thể hiện của Place(không có food_type). Vì vậy, Place.nameLongNamedRestaurant.namechia sẻ cùng một cột cơ sở dữ liệu, và do đó phải cùng loại.

Tôi nghĩ điều này có ý nghĩa đối với các mô hình bình thường: mỗi nhà hàng là một địa điểm, và ít nhất phải có tất cả mọi thứ mà nơi đó có. Có thể sự nhất quán này cũng là lý do tại sao các mô hình trừu tượng trước 1.10 không thể thực hiện được, mặc dù nó sẽ không gây ra các vấn đề về cơ sở dữ liệu ở đó. Như nhận xét của @lampslave, nó đã được thực hiện trong 1.10. Cá nhân tôi khuyên bạn nên cẩn thận: nếu Sub.x ghi đè Super.x, hãy đảm bảo Sub.x là một lớp con của Super.x, nếu không Sub.x không thể được sử dụng thay cho Super.

Cách giải quyết : Bạn có thể tạo một mô hình người dùng tùy chỉnh ( AUTH_USER_MODEL) liên quan đến trùng lặp mã khá nhiều nếu bạn chỉ cần thay đổi trường email. Ngoài ra, bạn có thể để nguyên email và đảm bảo rằng nó được yêu cầu trong mọi hình thức. Điều này không đảm bảo tính toàn vẹn của cơ sở dữ liệu nếu các ứng dụng khác sử dụng nó và không hoạt động theo cách khác (nếu bạn muốn đặt tên người dùng không bắt buộc).


Tôi đoán đó là do những thay đổi trong 1.10: "Được phép ghi đè các trường mô hình được kế thừa từ các lớp cơ sở trừu tượng." docs.djangoproject.com/en/2.0/releases/1.10/#models
lamplave

Tôi nghi ngờ điều đó vì nó vẫn chưa ra mắt vào thời điểm đó, nhưng đó là một điều tốt để thêm, cảm ơn!
Đánh dấu

19

Xem https://stackoverflow.com/a/6379556/15690 :

class BaseMessage(models.Model):
    is_public = models.BooleanField(default=False)
    # some more fields...

    class Meta:
        abstract = True

class Message(BaseMessage):
    # some fields...
Message._meta.get_field('is_public').default = True

2
AttributeError: không thể thiết lập thuộc tính ((((nhưng tôi đang cố gắng lựa chọn bộ
Alexey

Điều này không hoạt động trên Django 1.11 (nó từng hoạt động trên các phiên bản trước) ... phản hồi được chấp nhận hoạt động
acaruci

9

Đã dán mã của bạn vào một ứng dụng mới, thêm ứng dụng vào INSTALLED_APPS và chạy syncdb:

django.core.exceptions.FieldError: Local field 'name' in class 'LongNamedRestaurant' clashes with field of similar name from base class 'Place'

Có vẻ như Django không hỗ trợ điều đó.


7

Đoạn mã siêu lạnh này cho phép bạn 'ghi đè' các trường trong các lớp cha trừu tượng.

def AbstractClassWithoutFieldsNamed(cls, *excl):
    """
    Removes unwanted fields from abstract base classes.

    Usage::
    >>> from oscar.apps.address.abstract_models import AbstractBillingAddress

    >>> from koe.meta import AbstractClassWithoutFieldsNamed as without
    >>> class BillingAddress(without(AbstractBillingAddress, 'phone_number')):
    ...     pass
    """
    if cls._meta.abstract:
        remove_fields = [f for f in cls._meta.local_fields if f.name in excl]
        for f in remove_fields:
            cls._meta.local_fields.remove(f)
        return cls
    else:
        raise Exception("Not an abstract model")

Khi các trường đã bị xóa khỏi lớp cha trừu tượng, bạn có thể tự do xác định lại chúng khi cần.

Đây không phải là công việc của riêng tôi. Mã gốc từ đây: https://gist.github.com/specialunderwear/9d917ddacf3547b646ba


6

Có thể bạn có thể đối phó với Contrib_to_class:

class LongNamedRestaurant(Place):

    food_type = models.CharField(max_length=25)

    def __init__(self, *args, **kwargs):
        super(LongNamedRestaurant, self).__init__(*args, **kwargs)
        name = models.CharField(max_length=255)
        name.contribute_to_class(self, 'name')

Syncdb hoạt động tốt. Tôi chưa thử ví dụ này, trong trường hợp của tôi, tôi chỉ ghi đè một tham số ràng buộc nên ... hãy chờ xem!


1
Ngoài ra, các đối số cho Contribute_to_class có vẻ lạ (cũng sai cách?) Có vẻ như bạn đã gõ điều này từ bộ nhớ. Bạn có thể vui lòng cung cấp mã thực mà bạn đã kiểm tra không? Nếu bạn làm được điều này, tôi rất muốn biết chính xác bạn đã làm như thế nào.
Michael Bylstra

Điều này không hiệu quả với tôi. Cũng sẽ quan tâm đến một ví dụ làm việc.
garromark

vui lòng xem blog.jupo.org/2011/11/10/django-model-field-injection nó phải là Contrib_to_class (<ModelClass>, <fieldToReplace>)
goh

3
Place._meta.get_field('name').max_length = 255trong phần thân của lớp nên thực hiện thủ thuật, không ghi đè __init__(). Cũng sẽ ngắn gọn hơn.
Peterino

4

Tôi biết đó là một câu hỏi cũ, nhưng tôi đã gặp sự cố tương tự và đã tìm ra cách giải quyết:

Tôi đã học các lớp sau:

class CommonInfo(models.Model):
    image = models.ImageField(blank=True, null=True, default="")

    class Meta:
        abstract = True

class Year(CommonInfo):
    year = models.IntegerField() 

Nhưng tôi muốn trường hình ảnh kế thừa của Năm được yêu cầu trong khi vẫn giữ trường hình ảnh của lớp cha có giá trị rỗng. Cuối cùng, tôi đã sử dụng ModelForms để thực thi hình ảnh ở giai đoạn xác thực:

class YearForm(ModelForm):
    class Meta:
        model = Year

    def clean(self):
        if not self.cleaned_data['image'] or len(self.cleaned_data['image'])==0:
            raise ValidationError("Please provide an image.")

        return self.cleaned_data

admin.py:

class YearAdmin(admin.ModelAdmin):
    form = YearForm

Có vẻ như điều này chỉ có thể áp dụng cho một số trường hợp (chắc chắn là nơi bạn cần thực thi các quy tắc chặt chẽ hơn trên trường lớp con).

Ngoài ra, bạn có thể sử dụng clean_<fieldname>()phương pháp này thay vì clean(), ví dụ: nếu một trường townsẽ được yêu cầu điền vào:

def clean_town(self):
    town = self.cleaned_data["town"]
    if not town or len(town) == 0:
        raise forms.ValidationError("Please enter a town")
    return town

1

Bạn không thể ghi đè các trường Mô hình, nhưng nó có thể dễ dàng đạt được bằng cách ghi đè / chỉ định phương thức clean (). Tôi gặp sự cố với trường email và muốn biến nó thành duy nhất ở cấp Model và đã làm như thế này:

def clean(self):
    """
    Make sure that email field is unique
    """
    if MyUser.objects.filter(email=self.email):
        raise ValidationError({'email': _('This email is already in use')})

Thông báo lỗi sau đó được trường Biểu mẫu ghi lại với tên "email"


Câu hỏi là về việc mở rộng max_length của một trường char. Nếu điều này được thực thi bởi cơ sở dữ liệu, thì "giải pháp" này không hữu ích. Một giải pháp khác sẽ là chỉ định max_length dài hơn trong mô hình cơ sở và sử dụng phương thức clean () để thực thi độ dài ngắn hơn ở đó.
DylanYoung

0

Giải pháp của tôi rất đơn giản tiếp theo monkey patching, hãy chú ý cách tôi đã thay đổi trường max_lengththuộc tính fo nametrong LongNamedRestaurantmô hình:

class Place(models.Model):
   name = models.CharField(max_length=20)

class LongNamedRestaurant(Place):
    food_type = models.CharField(max_length=25)
    Place._meta.get_field('name').max_length = 255
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.