2009-12-17 27 views
14

Tôi đang gặp sự cố với mối quan hệ ManytoMany không cập nhật trong mô hình khi tôi lưu nó (thông qua quản trị viên) và cố gắng sử dụng giá trị mới trong một hàm được đính kèm tín hiệu post_save hoặc trong số save_model của liên kết AdminModel. Tôi đã thử tải lại đối tượng trong các hàm đó bằng cách sử dụng hàm nhận được với id .. nhưng nó vẫn có giá trị cũ.Vấn đề với quan hệ ManyToMany không cập nhật ngay sau khi lưu

Đây có phải là sự cố giao dịch không? Có tín hiệu nào bị hủy khi giao dịch kết thúc không?

Cảm ơn,

+0

Vì vậy, bạn đang thay đổi pk của các đối tượng? –

+0

Tôi có một đối tượng và có mối quan hệ với nhiều người khác, nhưng tôi có thể nhận được mối quan hệ cập nhật – diegueus9

Trả lời

20

Khi bạn lưu một mô hình thông qua các hình thức quản trị nó không phải là một giao dịch nguyên tử. Đối tượng chính được lưu trước tiên (để đảm bảo nó có PK), sau đó M2M là xóa và các giá trị mới được đặt thành bất kỳ thứ gì xuất hiện trong biểu mẫu. Vì vậy, nếu bạn đang ở trong tiết kiệm() của đối tượng chính bạn đang ở trong một cửa sổ của cơ hội, nơi M2M chưa được cập nhật. Trên thực tế, nếu bạn thử để làm điều gì đó với M2M, thay đổi sẽ bị xóa bởi xóa(). Tôi đã gặp phải điều này khoảng một năm trước.

Mã đã thay đổi đôi chút so với ngày tái cấu trúc ORM trước, nhưng mã này được viết thành mã số django.db.models.fields.ManyRelatedObjectsDescriptorReverseManyRelatedObjectsDescriptor. Hãy xem các phương thức __set __() của chúng và bạn sẽ thấy manager.clear(); manager.add(*value) Rõ ràng() hoàn thành làm sạch mọi tham chiếu M2M cho đối tượng chính hiện tại trong bảng đó. Add() sau đó đặt các giá trị mới.

Vì vậy, để trả lời câu hỏi của bạn: có, đây là sự cố giao dịch.

Có tín hiệu nào bị ném khi giao dịch kết thúc không? Không có gì chính thức, nhưng đọc trên:

Có một đề xuất là related thread a few months ago và MonkeyPatching. Grégoire posted a MonkeyPatch cho việc này. Tôi đã không thử nó, nhưng có vẻ như nó sẽ hoạt động.

+1

Tôi cũng gặp sự cố này, tôi đã sử dụng giải pháp được tìm thấy tại đây http://stackoverflow.com/questions/6200233/manytomany -field-not-saved-when-using-django-admin –

+1

Đúng nếu tôi sai nhưng câu trả lời này của @peterrowell vẫn hợp lệ như phiên bản hiện tại của Django (1.10). Trường M2M được ** xóa ** trước và sau đó được lấp đầy với dữ liệu của biểu mẫu. –

+0

@nik_m: Đã khá lâu rồi kể từ lần cuối tôi xem xét điều này. Vấn đề cơ bản là đây là vốn * không * một giao dịch nguyên tử vì mô hình chính * phải * được tạo/sửa đổi trước khi m2m được tạo/cập nhật (vì m2m tham chiếu đến PKID của cha mẹ). Có thể có một công việc xung quanh được đề cập [ở đây] (http://stackoverflow.com/a/23796604/17017) và [ở đây] (https://docs.djangoproject.com/en/1.10/ref/signals/# m2m-thay đổi). Chúc may mắn! –

0

Một cách tiếp cận khác không có bản vá Khỉ là celery, Bạn có thể thực hiện Tác vụ sẽ truy cập dữ liệu chính xác cho mối quan hệ M2M, điều này là do tác vụ chạy không đồng bộ và nếu bạn trì hoãn 30 giây, aproxim, bạn sẽ chắc chắn rằng giao dịch đã kết thúc.

Bạn phải gọi Task.apply_async(args=[...], countdown=30) trong các tín hiệu post_save hoặc pre_save.

0

Bạn có thể tìm thêm thông tin trong luồng này: Django manytomany signals?

+0

Tôi đã thử nó trong django 1.4, nó vô ích, sử dụng tín hiệu 'm2m_changed', gán giá trị m2m sẽ gây ra lỗi resursing, nếu bạn ngắt kết nối sau đó gán, sau đó' instance.save() ', bản cập nhật không hoàn toàn hoạt động. – est

5

Tôi có một giải pháp chung để điều này mà có vẻ hơi sạch hơn khỉ vá lõi hoặc thậm chí sử dụng cần tây (mặc dù tôi chắc chắn rằng ai đó có thể tìm thấy khu vực nơi nó không thành công). Về cơ bản tôi thêm một phương thức clean() trong admin cho form có quan hệ m2m, và thiết lập các quan hệ cá thể cho phiên bản clean_data. Điều này làm cho dữ liệu chính xác có sẵn cho phương thức lưu của cá thể, mặc dù nó chưa được "trên sách".Hãy thử nó và xem nó như thế nào đi:

def clean(self, *args, **kwargs): 
    # ... actual cleaning here 
    # then find the m2m fields and copy from cleaned_data to the instance 
    for f in self.instance._meta.get_all_field_names(): 
     if f in self.cleaned_data: 
      field = self.instance._meta.get_field_by_name(f)[0] 
      if isinstance(field, ManyToManyField): 
       setattr(self.instance,f,self.cleaned_data[f]) 
+0

có bất kỳ cập nhật nào cho vấn đề này đối với 1.4 không? – est

3

Xem http://gterzian.github.io/Django-Cookbook/signals/2013/09/07/manipulating-m2m-with-signals.html

vấn đề: Khi bạn thao tác các m2m của một mô hình trong mỗi bài viết hoặc nhận tín hiệu pre_save, những thay đổi của bạn được xóa sổ trong 'thanh toán bù trừ tiếp theo 'của m2m bởi Django.

giải pháp: Khi bạn đăng hoặc xử lý tín hiệu trước, hãy đăng ký trình xử lý khác cho tín hiệu m2m_changed trên mô hình trung gian m2m của mô hình có m2m bạn muốn cập nhật. Vui lòng lưu ý rằng trình xử lý thứ hai này sẽ nhận được một số tín hiệu m2m_changed, và đó là chìa khóa để kiểm tra giá trị của các đối số 'hành động' được truyền đi cùng với chúng.

Trong trình xử lý thứ hai này, hãy kiểm tra hành động 'post_clear'. Khi bạn nhận được tín hiệu với hành động post_clear, m2m đã bị xóa bởi Django và bạn có cơ hội thao tác thành công nó.

một ví dụ:

def save_handler(sender, instance, *args, **kwargs): 
    m2m_changed.connect(m2m_handler, sender=sender.m2mfield.through, weak=False) 


def m2m_handler(sender, instance, action, *args, **kwargs): 
    if action =='post_clear': 
     succesfully_manipulate_m2m(instance) 


pre_save.connect(save_handler, sender=YouModel, weak=False) 

thấy https://docs.djangoproject.com/en/1.5/ref/signals/#m2m-changed

4

Khi bạn đang cố gắng truy cập vào các lĩnh vực ManyToMany trong tín hiệu post_save của mô hình các đối tượng có liên quan đã được loại bỏ và sẽ không được thêm một lần nữa cho đến khi sau khi tín hiệu kết thúc.

Để truy cập dữ liệu này, bạn phải liên kết với phương thức save_related trên bạn ModelAdmin. Rất tiếc, bạn cũng sẽ phải bao gồm mã trong tín hiệu post_save cho các yêu cầu không phải quản trị viên yêu cầu tùy chỉnh của bạn.

xem: https://docs.djangoproject.com/en/1.7/ref/contrib/admin/#django.contrib.admin.ModelAdmin.save_related

Ví dụ:

# admin.py 
Class GroupAdmin(admin.ModelAdmin): 
    ... 
    def save_related(self, request, form, formsets, change): 
     super(GroupAdmin, self).save_related(request, form, formsets, change) 
     # do something with the manytomany data from the admin 
     form.instance.users.add(some_user) 

Sau đó, trong tín hiệu của bạn, bạn có thể làm cho những thay đổi tương tự mà bạn muốn thực hiện trên tiết kiệm:

# signals.py 
@receiver(post_save, sender=Group) 
def group_post_save(sender, instance, created, **kwargs): 
    # do somethign with the manytomany data from non-admin 
    instance.users.add(some_user) 
    # note that instance.users.all() will be empty from the admin: [] 
+0

điều này chắc chắn sẽ là câu trả lời đúng – psychok7

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