Làm cách nào để truy cập các lớp con của một đối tượng trong django mà không cần biết tên của lớp con?


81

Trong Django, khi bạn có một lớp cha và nhiều lớp con kế thừa từ nó, bạn thường truy cập vào một lớp con thông qua parentclass.childclass1_set hoặc parentclass.childclass2_set, nhưng nếu tôi không biết tên của lớp con cụ thể mà tôi muốn thì sao?

Có cách nào để lấy các đối tượng liên quan theo hướng cha-> con mà không cần biết tên lớp con không?


79
@ S.Lott Những kiểu phản hồi này thực sự đã cũ. Chỉ vì bạn không thể nghĩ ra một trường hợp sử dụng không có nghĩa là người hỏi không có. Nếu bạn đang sử dụng phân lớp cho bất kỳ loại hành vi đa hình nào (bạn biết đấy, một trong những lợi ích được cho là chính của OOP?) Thì câu hỏi này là một điều rất tự nhiên và cần thiết.
Carl Meyer

82
@ S.Lott Trong trường hợp đó, hãy thực hành một số phiên bản không thô lỗ, chẳng hạn như "Tôi không chắc mình hiểu ngữ cảnh. Bạn có thể giải thích trường hợp sử dụng của mình không?"
Carl Meyer

Câu trả lời:


84

( Cập nhật : Đối với Django 1.2 và mới hơn, có thể làm theo select_related truy vấn trên quan hệ OneToOneField ngược (và do đó giảm phân cấp thừa kế), có một kỹ thuật tốt hơn có sẵn mà không yêu cầu thêm real_typelĩnh vực trên mô hình mẹ Nó có sẵn như. InheritanceManager trong dự án django-model-utils .)

Cách thông thường để thực hiện việc này là thêm ForeignKey vào ContentType trên mô hình Parent để lưu trữ kiểu nội dung của lớp "lá" thích hợp. Nếu không có điều này, bạn có thể phải thực hiện khá nhiều truy vấn trên bảng con để tìm cá thể, tùy thuộc vào cây kế thừa của bạn lớn như thế nào. Đây là cách tôi đã làm điều đó trong một dự án:

from django.contrib.contenttypes.models import ContentType
from django.db import models

class InheritanceCastModel(models.Model):
    """
    An abstract base class that provides a ``real_type`` FK to ContentType.

    For use in trees of inherited models, to be able to downcast
    parent instances to their child types.

    """
    real_type = models.ForeignKey(ContentType, editable=False)

    def save(self, *args, **kwargs):
        if self._state.adding:
            self.real_type = self._get_real_type()
        super(InheritanceCastModel, self).save(*args, **kwargs)
    
    def _get_real_type(self):
        return ContentType.objects.get_for_model(type(self))
            
    def cast(self):
        return self.real_type.get_object_for_this_type(pk=self.pk)
    
    class Meta:
        abstract = True

Điều này được thực hiện như một lớp cơ sở trừu tượng để làm cho nó có thể tái sử dụng; bạn cũng có thể đặt các phương thức này và FK trực tiếp vào lớp cha trong hệ thống phân cấp kế thừa cụ thể của bạn.

Giải pháp này sẽ không hoạt động nếu bạn không thể sửa đổi mô hình gốc. Trong trường hợp đó, bạn khá khó kiểm tra tất cả các lớp con theo cách thủ công.


Cảm ơn bạn. Điều này thật đẹp và chắc chắn đã tiết kiệm thời gian cho tôi.
Spike

Điều này rất hữu ích, nhưng tôi tự hỏi tại sao bạn muốn định nghĩa FK với null = True. Chúng tôi đã sao chép mã ít nhiều như vậy và có một chút lỗi sẽ được phát hiện và giải quyết dễ dàng nếu FK là bắt buộc (cũng lưu ý rằng phương thức cast () coi nó là bắt buộc).
Shai Berger

1
@ShaiBerger Câu hỏi hay. Ba năm sau, tôi không biết tại sao nó lại như vậy ban đầu :-) Chỉnh sửa để loại bỏ null = True.
Carl Meyer

2
@sunprophit Bạn cần đọc lại phản hồi của tôi cẩn thận hơn. Có, tất nhiên bạn có thể thực hiện self.child_object()bằng cách sử dụng real_typetrường (trong đoạn mã ở trên làm cast()phương thức) và điều đó sẽ chỉ cần một truy vấn cho một trường hợp. Nhưng nếu bạn có một bộ truy vấn chứa đầy các phiên bản, thì đó sẽ trở thành N truy vấn. Cách duy nhất để lấy dữ liệu lớp con cho toàn bộ tập truy vấn các đối tượng trong một truy vấn duy nhất là sử dụng các phép nối mà InheritanceManager thực hiện.
Carl Meyer

1
Sai lầm của tôi thực sự, xấu hổ cho tôi. Cảm ơn bạn đã chú ý đến nó và sửa chữa nó.
Antoine Pinsard

22

Trong Python, với một lớp X ("kiểu mới"), bạn có thể lấy các lớp con (trực tiếp) của nó, lớp X.__subclasses__()này trả về một danh sách các đối tượng của lớp. (Nếu bạn muốn có "con cháu xa hơn", bạn cũng sẽ phải gọi __subclasses__từng lớp con trực tiếp, v.v ... - nếu bạn cần trợ giúp về cách làm điều đó hiệu quả trong Python, chỉ cần hỏi!).

Một khi bạn đã xác định được bằng cách nào đó một lớp con mà bạn quan tâm (có thể là tất cả chúng, nếu bạn muốn các trường hợp của tất cả các lớp con, v.v.), getattr(parentclass,'%s_set' % childclass.__name__)sẽ giúp ích (nếu tên của lớp con 'foo', điều này giống như truy cập parentclass.foo_set- không hơn, không kém ). Một lần nữa, nếu bạn cần làm rõ hoặc ví dụ, vui lòng hỏi!


1
Đây là thông tin tuyệt vời (tôi không biết về các lớp con ), nhưng tôi tin rằng câu hỏi thực sự cụ thể hơn nhiều về cách các mô hình Django triển khai kế thừa. Nếu bạn truy vấn bảng "Parent", bạn sẽ nhận được một phiên bản Parent. Trên thực tế, nó có thể là một ví dụ của SomeChild, nhưng Django không tự động tìm ra điều đó cho bạn (có thể tốn kém). Bạn có thể truy cập cá thể SomeChild thông qua một thuộc tính trên cá thể Cha, nhưng chỉ khi bạn đã biết rằng đó là SomeChild mà bạn muốn, trái ngược với một số lớp con khác của Parent.
Carl Meyer

2
Xin lỗi, không rõ khi tôi nói " trên thực tế có thể là một ví dụ của SomeChild." Đối tượng bạn có là một bản sao của Parent trong Python, nhưng nó có thể có mục nhập liên quan trong bảng SomeChild, có nghĩa là bạn có thể thích làm việc với nó như một bản sao SomeChild.
Carl Meyer

Thật buồn cười ... Tôi không cần thông tin cụ thể này vào thời điểm đó, nhưng tôi chỉ đang nghĩ về một vấn đề khác và đây chính xác là những gì tôi cần, vì vậy, cảm ơn bạn một lần nữa!
Gabriel Hurley 28/09/09

Đây là câu trả lời THỰC SỰ . Django chỉ là Python vào cuối ngày.
rắnNbronies

5

Giải pháp của Carl là một giải pháp tốt, đây là một cách để làm điều đó theo cách thủ công nếu có nhiều lớp con liên quan:

def get_children(self):
    rel_objs = self._meta.get_all_related_objects()
    return [getattr(self, x.get_accessor_name()) for x in rel_objs if x.model != type(self)]

Nó sử dụng một chức năng ngoài _meta, không được đảm bảo sẽ ổn định khi django phát triển, nhưng nó thực hiện được mẹo và có thể được sử dụng nhanh chóng nếu cần.



5

Bạn có thể sử dụng django-polymorphic cho điều đó.

Nó cho phép tự động truyền các lớp dẫn xuất trở lại kiểu thực của chúng. Nó cũng cung cấp hỗ trợ quản trị Django, xử lý truy vấn SQL hiệu quả hơn và mô hình proxy, nội tuyến và hỗ trợ bộ định dạng.

Nguyên tắc cơ bản dường như được phát minh lại nhiều lần (bao gồm cả Wagtail's .specific, hoặc các ví dụ được nêu trong bài đăng này). Tuy nhiên, cần nhiều nỗ lực hơn để đảm bảo nó không gây ra sự cố N-query hoặc tích hợp độc đáo với quản trị viên, bộ định dạng / nội tuyến hoặc ứng dụng của bên thứ ba.


1
Điều này có vẻ tốt và tôi muốn thử nó. Khi di chuyển có đủ chỉ cần điền vào các trường polymorphic_ctype các kiểu nội dung thích hợp (trong cuộc di cư về phía nam) không?
joshua

1
@joshua: vâng, đó chính xác là điều duy nhất bạn phải làm.
vdboor

Bạn có thể xác định câu trả lời của mình một chút bằng cách giải thích sự khác biệt so với cách tiếp cận được thực hiện trong django-model-utils (xem câu trả lời của Carl Meyer).
Ryne Everett

1
Câu trả lời được chấp nhận đã lỗi thời, đây là câu trả lời tốt nhất bây giờ.
Pierre.Sassoulas

2

Đây là giải pháp của tôi, một lần nữa nó sử dụng _metanên không được đảm bảo là ổn định.

class Animal(models.model):
    name = models.CharField()
    number_legs = models.IntegerField()
    ...

    def get_child_animal(self):
        child_animal = None
        for r in self._meta.get_all_related_objects():
            if r.field.name == 'animal_ptr':
                child_animal = getattr(self, r.get_accessor_name())
        if not child_animal:
            raise Exception("No subclass, you shouldn't create Animals directly")
        return child_animal

class Dog(Animal):
    ...

for a in Animal.objects.all():
    a.get_child_animal() # returns the dog (or whatever) instance

0

Bạn có thể đạt được điều này khi tìm kiếm tất cả các trường trong trường mẹ là một trường hợp của django.db.models.fields.osystem.RelatedManager. Từ ví dụ của bạn, có vẻ như các lớp con mà bạn đang nói đến không phải là các lớp con. Đúng?


0

Có thể tìm thấy một cách tiếp cận khác bằng cách sử dụng proxy trong bài đăng trên blog này . Giống như các giải pháp khác, nó có những lợi ích và trách nhiệm pháp lý, được đưa ra ở cuối bài viết.

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.