2010-12-13 21 views
11

(Django 1.1) Tôi có một mô hình dự án theo dõi các thành viên của nó bằng cách sử dụng trường m2m. Nó trông giống như thế này:Django - Cách lưu dữ liệu m2m qua tín hiệu post_save?

class Project(models.Model): 
    members = models.ManyToManyField(User) 
    sales_rep = models.ForeignKey(User) 
    sales_mgr = models.ForeignKey(User) 
    project_mgr = models.ForeignKey(User) 
    ... (more FK user fields) ... 

Khi dự án được tạo ra, chọn sales_rep, sales_mgr, project_mgr, vv User s được bổ sung cho các thành viên để làm cho nó dễ dàng hơn để theo dõi các điều khoản của dự án. Cách tiếp cận này đã làm việc rất tốt cho đến nay.

Vấn đề tôi đang giải quyết bây giờ là cách cập nhật tư cách thành viên của dự án khi một trong các trường FK User được cập nhật qua quản trị viên. Tôi đã thử các giải pháp khác nhau cho vấn đề này, nhưng cách tiếp cận sạch nhất dường như là một tín hiệu post_save như sau:

def update_members(instance, created, **kwargs): 
    """ 
    Signal to update project members 
    """ 
    if not created: #Created projects are handled differently 
     instance.members.clear() 

     members_list = [] 
     if instance.sales_rep: 
      members_list.append(instance.sales_rep) 
     if instance.sales_mgr: 
      members_list.append(instance.sales_mgr) 
     if instance.project_mgr: 
      members_list.append(instance.project_mgr) 

     for m in members_list: 
      instance.members.add(m) 
signals.post_save.connect(update_members, sender=Project) 

Tuy nhiên, Project vẫn có các thành viên cùng ngay cả khi tôi thay đổi một trong các lĩnh vực thông qua quản trị viên! Tôi đã thành công trong việc cập nhật các thành viên m2m bằng cách sử dụng quan điểm của riêng tôi trong các dự án khác, nhưng tôi chưa bao giờ phải làm cho nó chơi tốt đẹp với quản trị viên.

Có cách tiếp cận nào khác mà tôi nên thực hiện ngoài tín hiệu post_save để cập nhật tư cách thành viên không? Cảm ơn trước sự giúp đỡ của bạn!

UPDATE:

Chỉ cần làm rõ, tín hiệu post_save hoạt động chính xác khi tôi tiết kiệm hình thức riêng của tôi ở cuối phía trước (thành viên cũ bị loại bỏ, và những người mới được bổ sung). Tuy nhiên, tín hiệu post_save KHÔNG hoạt động chính xác khi tôi lưu dự án thông qua admin (các thành viên vẫn giữ nguyên).

Tôi nghĩ chẩn đoán của Peter Rowell là chính xác trong tình huống này. Nếu tôi xóa trường "thành viên" khỏi biểu mẫu quản trị thì tín hiệu post_save hoạt động chính xác. Khi trường được bao gồm, nó sẽ lưu các thành viên cũ dựa trên các giá trị có trong biểu mẫu tại thời điểm lưu. Không có vấn đề gì thay đổi tôi thực hiện cho các thành viên m2m lĩnh vực khi dự án được lưu (cho dù đó là một tín hiệu hoặc tùy chỉnh lưu phương pháp), nó sẽ luôn luôn được ghi đè bởi các thành viên đã có mặt trong mẫu trước khi lưu. Cảm ơn bạn đã chỉ ra điều đó!

+2

Tôi không biết nếu điều này là vấn đề của bạn, nhưng tôi có một cảm giác ruột mà bạn có thể chạy vào một artifact của cách mã hình thức cập nhật thông tin m2m. Về cơ bản họ lần đầu tiên lưu đối tượng chính, sau đó họ thiết lập các giá trị m2m bằng cách đầu tiên thanh toán bù trừ tất cả chúng, và sau đó đặt chúng dựa trên các giá trị hiện tại * trong biểu mẫu *. Điều này xảy ra * sau * save() trên đối tượng chính, vì vậy bất cứ điều gì bạn làm trong save() hoặc dựa trên tín hiệu 'post_save' được thực hiện lần đầu tiên, và sau đó * undone *. Đây là trong 'django.forms.models.save_instance()'. Sẽ tốt hơn nếu có tín hiệu 'after_form_save'. –

+0

Cảm ơn, Peter! Tôi tin rằng chẩn đoán của bạn là chính xác. Tôi đã cập nhật bài đăng gốc của mình để bao gồm thông tin này. –

+0

Peter đúng. Tôi đã có cùng một vấn đề và tìm thấy một workaround, nhưng nó không phải là gọn gàng như một tín hiệu 'after_form_save': http://stackoverflow.com/questions/3652585/simple-django-form-model-save-question –

Trả lời

4

Tôi không thể thấy bất kỳ điều gì sai với mã của bạn, nhưng tôi nhầm lẫn là tại sao bạn cho rằng quản trị viên sẽ hoạt động khác với bất kỳ ứng dụng nào khác.

Tuy nhiên, tôi phải nói rằng tôi nghĩ rằng cấu trúc mô hình của bạn sai. Tôi nghĩ bạn cần phải loại bỏ tất cả các trường ForeignKey đó, và chỉ có một ManyToMany - nhưng sử dụng một bảng thông qua để theo dõi các vai trò.

class Project(models.Model): 
    members = models.ManyToManyField(User, through='ProjectRole') 

class ProjectRole(models.Model): 
    ROLES = (
     ('SR', 'Sales Rep'), 
     ('SM', 'Sales Manager'), 
     ('PM', 'Project Manager'), 
    ) 
    project = models.ForeignKey(Project) 
    user = models.ForeignKey(User) 
    role = models.CharField(max_length=2, choices=ROLES) 
+0

Tôi đồng ý rằng cấu trúc mô hình cần được cải thiện, nhưng tôi đang làm việc với triển khai cũ hơn và cố gắng tận dụng tối đa nó. Tại thời điểm này, tôi chưa sẵn sàng di chuyển hệ thống sang cấu trúc mới này, nhưng tôi sẽ lưu ý đến tương lai của bạn. Cảm ơn. –

6

Có cùng sự cố, giải pháp của tôi là sử dụng tín hiệu m2m_changed. Bạn có thể sử dụng nó ở hai nơi, như trong ví dụ sau.

Các admin khi tiết kiệm sẽ tiến hành:

  • lưu các lĩnh vực mô hình
  • phát ra tín hiệu post_save
  • cho mỗi m2m:
    • phát ra pre_clear
    • rõ ràng mối quan hệ
    • phát ra post_clear
    • phát ra pre_add
    • populate lại
    • phát ra post_add

Ở đây bạn có một ví dụ đơn giản rằng những thay đổi nội dung của dữ liệu được lưu trước khi thực sự tiết kiệm nó.

class MyModel(models.Model): 

    m2mfield = ManyToManyField(OtherModel) 

    @staticmethod 
    def met(sender, instance, action, reverse, model, pk_set, **kwargs): 
     if action == 'pre_add': 
      # here you can modify things, for instance 
      pk_set.intersection_update([1,2,3]) 
      # only save relations to objects 1, 2 and 3, ignoring the others 
     elif action == 'post_add': 
      print pk_set 
      # should contain at most 1, 2 and 3 

m2m_changed.connect(receiver=MyModel.met, sender=MyModel.m2mfield.through) 

Bạn cũng có thể nghe pre_remove, post_remove, pre_clearpost_clear. Trong trường hợp của tôi, tôi đang sử dụng chúng để lọc một danh sách ('điều tích cực') trong nội dung của người khác ('kích hoạt điều') không phụ thuộc vào thứ tự mà danh sách được lưu:

def clean_services(sender, instance, action, reverse, model, pk_set, **kwargs): 
    """ Ensures that the active services are a subset of the enabled ones. 
    """ 
    if action == 'pre_add' and sender == Account.active_services.through: 
     # remove from the selection the disabled ones 
     pk_set.intersection_update(instance.enabled_services.values_list('id', flat=True)) 
    elif action == 'pre_clear' and sender == Account.enabled_services.through: 
     # clear everything 
     instance._cache_active_services = list(instance.active_services.values_list('id', flat=True)) 
     instance.active_services.clear() 
    elif action == 'post_add' and sender == Account.enabled_services.through: 
     _cache_active_services = getattr(instance, '_cache_active_services', None) 
     if _cache_active_services: 
      instance.active_services.add(*list(instance.enabled_services.filter(id__in=_cache_active_services))) 
      delattr(instance, '_cache_active_services') 
    elif action == 'pre_remove' and sender == Account.enabled_services.through: 
     # de-default any service we are disabling 
     instance.active_services.remove(*list(instance.active_services.filter(id__in=pk_set))) 

Nếu "kích hoạt" những cái được cập nhật (xóa/xóa + thêm lại, như trong admin) thì các "active" được lưu trữ và xóa trong pass đầu tiên ('pre_clear') và sau đó được bổ sung trở lại từ cache sau pass thứ hai ('post_add') .

Bí quyết là cập nhật một danh sách trên các tín hiệu m2m_changed của người kia.

+0

Bạn đã lưu ngày của tôi! Cảm ơn :) –

0

Tôi đã gặp phải tình huống, khi tôi cần tìm mục mới nhất từ ​​bộ mặt hàng, được kết nối với mô hình qua m2m_field.

Sau câu trả lời Saverio của, đoạn mã sau giải quyết vấn đề của tôi:

def update_item(sender, instance, action, **kwargs): 
    if action == 'post_add': 
     instance.related_field = instance.m2m_field.all().order_by('-datetime')[0] 
     instance.save() 

m2m_changed.connect(update_item, sender=MyCoolModel.m2m_field.through) 
Các vấn đề liên quan