Tại sao không mô hình django của model.save () gọi full_clean ()?


150

Tôi chỉ tò mò liệu có ai biết lý do chính đáng tại sao orm của django không gọi 'full_clean' trên một mô hình trừ khi nó được lưu dưới dạng một phần của mẫu mô hình.

Lưu ý rằng full_clean () sẽ không được gọi tự động khi bạn gọi phương thức save () của mô hình. Bạn sẽ cần gọi thủ công khi bạn muốn chạy xác thực mô hình một bước cho các mô hình được tạo thủ công của riêng bạn. tài liệu sạch hoàn toàn của django

(LƯU Ý: trích dẫn được cập nhật cho Django 1.6 ... các tài liệu django trước đó cũng có một cảnh báo về ModelForms.)

Có lý do chính đáng tại sao mọi người sẽ không muốn hành vi này? Tôi nghĩ rằng nếu bạn dành thời gian để thêm xác thực cho một mô hình, bạn sẽ muốn xác thực đó chạy mỗi khi mô hình được lưu.

Tôi biết làm thế nào để mọi thứ hoạt động bình thường, tôi chỉ tìm kiếm một lời giải thích.


11
Cảm ơn bạn rất nhiều vì câu hỏi này, nó ngăn tôi đập đầu vào tường nhiều thời gian hơn. Tôi đã tạo ra một mixin có thể giúp đỡ người khác. Kiểm tra ý chính: gist.github.com/glarrain/5448253
glarrain

Và cuối cùng tôi sử dụng tín hiệu để bắt pre_savemóc và làm full_cleantrên tất cả các mô hình bị bắt.
Alfred Huang

Câu trả lời:


59

AFAIK, điều này là do khả năng tương thích ngược. Cũng có vấn đề với ModelForms với các trường bị loại trừ, các mô hình có giá trị mặc định, tín hiệu pre_save (), v.v.

Các nguồn bạn có thể được giới thiệu:


3
Đoạn trích hữu ích nhất (IMHO) từ tài liệu tham khảo thứ hai: "Phát triển tùy chọn xác thực" tự động "vừa đủ đơn giản để thực sự hữu ích và đủ mạnh để xử lý tất cả các trường hợp cạnh là - thậm chí còn có thể - còn hơn thế có thể được thực hiện trên khung thời gian 1.2. Do đó, hiện tại, Django không có bất kỳ điều gì như vậy và sẽ không có nó trong 1.2. Nếu bạn nghĩ rằng bạn có thể làm cho nó hoạt động trong 1.3, thì cách tốt nhất của bạn là làm việc đề xuất, bao gồm ít nhất một số mã mẫu, cùng với lời giải thích về cách bạn sẽ giữ nó đơn giản và mạnh mẽ. "
Josh

30

Do tính tương thích, tính năng tự động xóa khi lưu không được bật trong kernel django.

Nếu chúng tôi đang bắt đầu một dự án mới và muốn savephương thức mặc định trên Model có thể tự động xóa, chúng tôi có thể sử dụng tín hiệu sau để làm sạch trước khi mọi mô hình được lưu.

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

@receiver(pre_save)
def pre_save_handler(sender, instance, *args, **kwargs):
    instance.full_clean()

2
Tại sao điều này tốt hơn (hoặc tệ hơn) so với ghi đè phương thức lưu trên một số BaseModel (mà tất cả những người khác sẽ kế thừa) để gọi full_clean trước, sau đó gọi super ()?
J__

7
Tôi thấy có hai vấn đề với cách tiếp cận 1) này trong trường hợp Full_clean () của ModelForm sẽ được gọi hai lần: bởi biểu mẫu và tín hiệu 2) Nếu biểu mẫu loại trừ một số trường, chúng vẫn sẽ được xác thực bởi tín hiệu.
mehmet

1
@mehmet Vì vậy, có thể bạn có thể thêm những thứ này if send == somemodel, then exclude some fieldsvàopre_save_handler
Simin Jie

4
Đối với những người đang sử dụng hoặc cân nhắc sử dụng phương pháp này: hãy nhớ rằng phương pháp này không được Django hỗ trợ chính thức và sẽ không được hỗ trợ trong tương lai gần (xem nhận xét này trong trình theo dõi lỗi Django: code.djangoproject.com/ticket/ 29655 # bình luận: 3 ), do đó bạn có thể vấp phải một số điểm không hoàn hảo như dừng xác thực để hoạt động ( code.djangoproject.com/ticket/29655 ) nếu bạn bật xác thực cho tất cả các mô hình. Bạn sẽ phải tự mình giải quyết những vấn đề như vậy. Tuy nhiên, không có cách tiếp cận tốt hơn atm.
Evgeny A.

2
Kể từ Django 2.2.3, điều này gây ra sự cố với hệ thống xác thực cơ bản. Bạn sẽ nhận được một ValidationError: Session with this Session key already exists. Để tránh điều này, bạn cần thêm một câu lệnh if sender in list_of_model_classesđể ngăn tín hiệu ghi đè lên các mô hình xác thực mặc định của Django. Xác định list_of_model_classestuy nhiên bạn chọn
Addison Klinke

15

Cách đơn giản nhất để gọi full_cleanphương thức chỉ là ghi đè savephương thức trong model:

def save(self, *args, **kwargs):
    self.full_clean()
    return super(YourModel, self).save(*args, **kwargs)

Tại sao điều này tốt hơn (hoặc tồi tệ hơn) so với việc sử dụng tín hiệu?
J__

6
Tôi thấy có hai vấn đề với cách tiếp cận 1) này trong trường hợp Model_orm 'full_clean () sẽ được gọi hai lần: bởi biểu mẫu và lưu 2) Nếu biểu mẫu loại trừ một số trường, chúng vẫn sẽ được xác thực bởi lưu.
mehmet

3

Thay vì chèn một đoạn mã khai báo người nhận, chúng ta có thể sử dụng một ứng dụng như một INSTALLED_APPSphần trongsettings.py

INSTALLED_APPS = [
    # ...
    'django_fullclean',
    # your apps here,
]

Trước đó, bạn có thể cần cài đặt django-fullcleanbằng PyPI:

pip install django-fullclean

13
Tại sao bạn lại có pip installmột số ứng dụng có 4 dòng mã trong đó (kiểm tra mã nguồn ) thay vì tự viết những dòng này?
David D.

Một thư viện khác mà tôi chưa từng thử: github.com/danielgatis/django-smart-save
Flimm

2

Nếu bạn có một mô hình mà bạn muốn đảm bảo có ít nhất một mối quan hệ FK và bạn không muốn sử dụng null=Falsevì yêu cầu phải đặt FK mặc định (đó sẽ là dữ liệu rác), cách tốt nhất tôi nghĩ ra là để thêm tùy chỉnh .clean().save()phương thức. .clean()làm tăng lỗi xác nhận và .save()gọi sạch. Bằng cách này, tính toàn vẹn được thi hành cả từ các biểu mẫu và từ mã gọi khác, dòng lệnh và kiểm tra. Không có điều này, (AFAICT) không có cách nào để viết một bài kiểm tra đảm bảo rằng một mô hình có mối quan hệ FK với một mô hình khác được chọn cụ thể (không mặc định).

class Payer(models.Model):

    name = models.CharField(blank=True, max_length=100)
    # Nullable, but will enforce FK in clean/save:
    payer_group = models.ForeignKey(PayerGroup, null=True, blank=True,)

    def clean(self):
        # Ensure every Payer is in a PayerGroup (but only via forms)
        if not self.payer_group:
            raise ValidationError(
                {'payer_group': 'Each Payer must belong to a PayerGroup.'})

    def save(self, *args, **kwargs):
        self.full_clean()
        return super().save(*args, **kwargs)

    def __str__(self):
        return self.name

1

Nhận xét về câu trả lời của @Alfred Huang và đồng ý với nó. Người ta có thể khóa hook pre_save xuống một ứng dụng bằng cách xác định danh sách các lớp trong mô-đun hiện tại (model.py) và kiểm tra lại nó trong hook pre_save:

CUSTOM_CLASSES = [obj for name, obj in
        inspect.getmembers(sys.modules[__name__])
        if inspect.isclass(obj)]

@receiver(pre_save)
def pre_save_handler(sender, instance, **kwargs):
    if type(instance) in CUSTOM_CLASSES:
        instance.full_clean()
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.