2011-10-17 36 views
33

Tôi có một vài cấp độ mô hình thừa kế trong Django:Làm thế nào để sử dụng mô hình kế thừa Django với tín hiệu?

class WorkAttachment(models.Model): 
    """ Abstract class that holds all fields that are required in each attachment """ 
    work   = models.ForeignKey(Work) 
    added   = models.DateTimeField(default=datetime.datetime.now) 
    views   = models.IntegerField(default=0) 

    class Meta: 
     abstract = True 


class WorkAttachmentFileBased(WorkAttachment): 
    """ Another base class, but for file based attachments """ 
    description  = models.CharField(max_length=500, blank=True) 
    size   = models.IntegerField(verbose_name=_('size in bytes')) 

    class Meta: 
     abstract = True 


class WorkAttachmentPicture(WorkAttachmentFileBased): 
    """ Picture attached to work """ 
    image   = models.ImageField(upload_to='works/images', width_field='width', height_field='height') 
    width   = models.IntegerField() 
    height   = models.IntegerField() 

Có rất nhiều mô hình khác nhau kế thừa từ WorkAttachmentFileBasedWorkAttachment. Tôi muốn tạo một tín hiệu, sẽ cập nhật một trường attachment_count cho công việc của phụ huynh, khi tệp đính kèm được tạo. Nó sẽ là hợp lý, để nghĩ rằng tín hiệu được thực hiện cho người gửi cha mẹ (WorkAttachment) sẽ chạy cho tất cả các mô hình kế thừa quá, nhưng nó không. Đây là mã của tôi:

@receiver(post_save, sender=WorkAttachment, dispatch_uid="att_post_save") 
def update_attachment_count_on_save(sender, instance, **kwargs): 
    """ Update file count for work when attachment was saved.""" 
    instance.work.attachment_count += 1 
    instance.work.save() 

Có cách nào để làm cho tín hiệu này hoạt động cho tất cả các kiểu được kế thừa từ WorkAttachment?

Python 2.7, Django 1.4 pre-alpha

P.S. Tôi đã thử one of the solutions I found on the net, nhưng nó không hoạt động đối với tôi.

+2

Giải pháp liên kết bị phá vỡ – Martin

+3

tôi đã tìm thấy [trang giải pháp trong tài liệu lưu trữ web] (http: // web. archive.org/web/20120715042306/http://codeblogging.net/blogs/1/14). Có một nhược điểm của giải pháp - bạn nên khai báo tín hiệu sau tất cả các lớp con, nếu không nó sẽ không tìm thấy chúng. – HighCat

Trả lời

17

Bạn có thể thử một cái gì đó như:

model_classes = [WorkAttachment, WorkAttachmentFileBased, WorkAttachmentPicture, ...] 

def update_attachment_count_on_save(sender, instance, **kwargs): 
    instance.work.attachment_count += 1 
    instance.work.save() 

for model_class in model_classes: 
    post_save.connect(update_attachment_count_on_save, 
         sender=model_class, 
         dispatch_uid="att_post_save_"+model_class.__name__) 

(Disclaimer: Tôi chưa được thử nghiệm trên)

+4

Cảm ơn bạn đã trả lời. Nó truyền cảm hứng cho tôi cho giải pháp, mà tôi mô tả ở đây: http://codeblogging.net/blogs/1/14/ –

+0

Vui vì tôi có thể giúp, bài đăng blog hay. – codeape

+2

Chỉ cần FYI bài đăng blog của @SilverLight không còn là liên kết hợp lệ nữa. – Fitblip

0

Cũng có thể sử dụng các loại nội dung để khám phá lớp con - giả sử bạn có các lớp cơ sở và các lớp con được đóng gói trong cùng một ứng dụng. Một cái gì đó như thế này sẽ hoạt động:

from django.contrib.contenttypes.models import ContentType 
content_types = ContentType.objects.filter(app_label="your_app") 
for content_type in content_types: 
    model = content_type.model_class() 
    post_save.connect(update_attachment_count_on_save, sender=model) 
39

Bạn có thể đăng ký trình xử lý kết nối mà không cần sender được chỉ định. Và lọc các mô hình cần thiết bên trong nó.

from django.db.models.signals import post_save 
from django.dispatch import receiver 


@receiver(post_save) 
def my_handler(sender, **kwargs): 
    # Returns false if 'sender' is NOT a subclass of AbstractModel 
    if not issubclass(sender, AbstractModel): 
     return 
    ... 

Ref: https://groups.google.com/d/msg/django-users/E_u9pHIkiI0/YgzA1p8XaSMJ

+0

Đây là một giải pháp tuyệt vời. – Mikle

+5

Điều này làm việc nhưng có một nhược điểm nhỏ trong đó các chức năng nhận được gọi cho mỗi mô hình mà gọi 'lưu()'. – dhobbs

+1

Có, có vẻ như điều này sẽ phải chịu một chi phí hiệu suất đáng kể. –

2

Giải pháp này giải quyết vấn đề khi không phải tất cả các module nhập khẩu vào bộ nhớ.

def inherited_receiver(signal, sender, **kwargs): 
    """ 
    Decorator connect receivers and all receiver's subclasses to signals. 

     @inherited_receiver(post_save, sender=MyModel) 
     def signal_receiver(sender, **kwargs): 
      ... 

    """ 
    parent_cls = sender 

    def wrapper(func): 
     def childs_receiver(sender, **kw): 
      """ 
      the receiver detect that func will execute for child 
      (and same parent) classes only. 
      """ 
      child_cls = sender 
      if issubclass(child_cls, parent_cls): 
       func(sender=child_cls, **kw) 

     signal.connect(childs_receiver, **kwargs) 
     return childs_receiver 
    return wrapper 
+0

điều này được xây dựng trên http://stackoverflow.com/a/17173716/433570 và không có các khiếm khuyết mà Silverlight được đề cập trong câu trả lời được chấp nhận. – eugene

6
post_save.connect(my_handler, ParentClass) 
# connect all subclasses of base content item too 
for subclass in ParentClass.__subclasses__(): 
    post_save.connect(my_handler, subclass) 

có một ngày tốt đẹp!

+1

Đây là câu trả lời hay nhất. –

+0

Bạn cần đảm bảo điều này được chạy sau khi tất cả các lớp con có thể đã được xác định hoặc nếu không chúng sẽ bị bỏ qua (mặc dù, tôi chưa thử nghiệm xác nhận đó, tôi nghĩ đó là điều sẽ xảy ra). –

19

Giải pháp đơn giản nhất là không hạn chế trên sender, nhưng để kiểm tra trong xử lý tín hiệu cho dù trường hợp tương ứng là một lớp con:

@receiver(post_save) 
def update_attachment_count_on_save(sender, instance, **kwargs): 
    if isinstance(instance, WorkAttachment): 
     ... 

Tuy nhiên, điều này có thể phải chịu một buổi biểu diễn trên không đáng kể như mỗi thời gian bất kỳ mô hình nào được lưu, hàm trên được gọi.

Tôi nghĩ rằng tôi đã tìm được cách Django nhất để thực hiện việc này: Các phiên bản gần đây của Django đề xuất kết nối bộ xử lý tín hiệu trong một tệp có tên signals.py. Dưới đây là mã hệ thống dây điện cần thiết:

your_app/__ init__.py:

default_app_config = 'your_app.apps.YourAppConfig' 

your_app/ứng dụng.py:

import django.apps 

class YourAppConfig(django.apps.AppConfig): 
    name = 'your_app' 
    def ready(self): 
     import your_app.signals 

your_app/signals.py:

def get_subclasses(cls): 
    result = [cls] 
    classes_to_inspect = [cls] 
    while classes_to_inspect: 
     class_to_inspect = classes_to_inspect.pop() 
     for subclass in class_to_inspect.__subclasses__(): 
      if subclass not in result: 
       result.append(subclass) 
       classes_to_inspect.append(subclass) 
    return result 

def update_attachment_count_on_save(sender, instance, **kwargs): 
    instance.work.attachment_count += 1 
    instance.work.save() 

for subclass in get_subclasses(WorkAttachment): 
    post_save.connect(update_attachment_count_on_save, subclass) 

tôi nghĩ làm việc này cho tất cả các lớp con, bởi vì tất cả họ sẽ được nạp vào thời điểm YourAppConfig.ready được gọi là (và do đó signals được nhập).

+0

Đây phải là câu trả lời hàng đầu – spg

+0

câu trả lời hay. Lưu ý rằng '' 'result''' trong' '' get_subclassed''' chứa lớp cha, khớp với câu hỏi này. Nếu lớp cha của bạn là một Mô hình trừu tượng, bạn sẽ muốn '' 'result''' thành một danh sách trống ban đầu. – biodiv

3

Giải pháp của Michael Herrmann chắc chắn là cách Django nhất để thực hiện việc này. Và có nó hoạt động cho tất cả các lớp con khi chúng được tải tại cuộc gọi ready().

Tôi muốn đóng góp với các tài liệu tham khảo tài liệu hướng dẫn:

Trên thực tế, xử lý tín hiệu thường được định nghĩa trong một tín hiệu submodule của ứng dụng chúng liên quan đến. Bộ thu tín hiệu được kết nối trong phương thức ready() của lớp cấu hình ứng dụng của bạn. Nếu bạn đang sử dụng trình trang trí receiver(), chỉ cần nhập các mô-đun con tín hiệu bên trong ready().

https://docs.djangoproject.com/en/dev/topics/signals/#connecting-receiver-functions

Và thêm một cảnh báo:

Phương pháp sẵn sàng() có thể được thực hiện nhiều hơn một lần trong thử nghiệm, vì vậy bạn có thể muốn bảo vệ tín hiệu của bạn khỏi sự trùng lặp, đặc biệt là nếu bạn đang lên kế hoạch để gửi chúng trong các thử nghiệm.

https://docs.djangoproject.com/en/dev/topics/signals/#connecting-receiver-functions

Vì vậy, bạn có thể muốn ngăn chặn các tín hiệu tương tự có tham số dispatch_uid về chức năng kết nối.

post_save.connect(my_callback, dispatch_uid="my_unique_identifier") 

Trong bối cảnh này, tôi sẽ thực hiện:

for subclass in get_subclasses(WorkAttachment): 
    post_save.connect(update_attachment_count_on_save, subclass, dispatch_uid=subclass.__name__) 

https://docs.djangoproject.com/en/dev/topics/signals/#preventing-duplicate-signals

Các vấn đề liên quan