เหตุใดรุ่นของ django จึงไม่บันทึก full_clean ()


150

ฉันแค่อยากรู้ว่าใครรู้ว่ามีเหตุผลที่ดีว่าทำไม orm ของ django ไม่เรียก 'full_clean' ในแบบจำลองเว้นแต่ว่ามันถูกบันทึกไว้เป็นส่วนหนึ่งของแบบจำลอง

โปรดทราบว่า full_clean () จะไม่ถูกเรียกโดยอัตโนมัติเมื่อคุณเรียกเมธอด save () ของแบบจำลองของคุณ คุณจะต้องเรียกมันด้วยตนเองเมื่อคุณต้องการเรียกใช้การตรวจสอบความถูกต้องแบบขั้นตอนเดียวสำหรับรุ่นที่คุณสร้างขึ้นเอง เอกสารฉบับเต็มของ django

(หมายเหตุ: การอัพเดทใบเสนอราคาสำหรับ Django 1.6 ... เอกสาร django ก่อนหน้ามีข้อแม้เกี่ยวกับ ModelForms ด้วย)

มีเหตุผลที่ดีที่ผู้คนไม่ต้องการพฤติกรรมนี้หรือไม่? ฉันคิดว่าถ้าคุณใช้เวลาในการเพิ่มการตรวจสอบความถูกต้องให้กับแบบจำลองคุณจะต้องการให้การตรวจสอบความถูกต้องทำงานทุกครั้งที่แบบจำลองนั้นได้รับการบันทึก

ฉันรู้วิธีทำให้ทุกอย่างทำงานอย่างถูกต้องฉันแค่มองหาคำอธิบาย


11
ขอบคุณมากสำหรับคำถามนี้มันทำให้ฉันไม่ต้องต่อสู้กับกำแพงอีกต่อไป ฉันสร้างมิกซ์อินที่อาจช่วยเหลือผู้อื่น ตรวจสอบส่วนสำคัญ: gist.github.com/glarrain/5448253
glarrain

ในที่สุดฉันก็ใช้สัญญาณจับpre_saveตะขอและทำfull_cleanทุกรุ่นที่จับได้
Alfred Huang

คำตอบ:


59

AFAIK นี่เป็นเพราะความเข้ากันได้ย้อนหลัง นอกจากนี้ยังมีปัญหาเกี่ยวกับ ModelForms ที่มีฟิลด์ที่ยกเว้นรุ่นที่มีค่าเริ่มต้นสัญญาณ pre_save () และอื่น ๆ

แหล่งข้อมูลที่คุณอาจสนใจ:


3
ข้อความที่ตัดตอนมามีประโยชน์มากที่สุด (IMHO) จากการอ้างอิงที่สอง: "การพัฒนาตัวเลือกการตรวจสอบ" อัตโนมัติ "ซึ่งทั้งง่ายพอที่จะเป็นประโยชน์จริงและแข็งแกร่งพอที่จะจัดการกับกรณีขอบทั้งหมด - ถ้าเป็นไปได้ - มากกว่า สามารถทำได้ในระยะเวลา 1.2 ดังนั้นสำหรับตอนนี้ Django ไม่มีสิ่งดังกล่าวและจะไม่มีใน 1.2 ถ้าคุณคิดว่าคุณสามารถทำให้มันใช้งานได้ 1.3 การเดิมพันที่ดีที่สุดของคุณคือ ข้อเสนอรวมถึงโค้ดตัวอย่างอย่างน้อยบางส่วนพร้อมคำอธิบายว่าคุณจะเก็บรักษาไว้ได้อย่างไรทั้งเรียบง่ายและมีประสิทธิภาพ "
Josh

30

เนื่องจากการพิจารณาความเข้ากันได้การบันทึกการล้างอัตโนมัติจะไม่เปิดใช้งานในเคอร์เนล django

หากเรากำลังเริ่มโครงการใหม่และต้องการให้saveวิธีการเริ่มต้นบน Model สามารถล้างข้อมูลโดยอัตโนมัติเราสามารถใช้สัญญาณต่อไปนี้เพื่อล้างข้อมูลก่อนที่ทุกรุ่นจะถูกบันทึก

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
เหตุใดจึงดีกว่าหรือแย่กว่าการบันทึกเมธอดใน BaseModel บางอัน (ซึ่งคนอื่นทั้งหมดจะสืบทอดจาก) เพื่อเรียก full_clean ก่อนแล้วจึงเรียก super ()
J__

7
ฉันเห็นปัญหาสองข้อเกี่ยวกับวิธีนี้ 1) ในกรณี full_clean () ของ ModelForm จะถูกเรียกสองครั้ง: โดยฟอร์มและสัญญาณ 2) หากฟอร์มแยกบางฟิลด์พวกเขาจะยังคงตรวจสอบโดยสัญญาณ
เมห์เม็ต

1
@mehmet คุณอาจจะสามารถเพิ่มสิ่งเหล่านี้ได้if send == somemodel, then exclude some fieldsในpre_save_handler
Simin Jie

4
สำหรับผู้ที่กำลังใช้หรือพิจารณาใช้วิธีนี้: โปรดทราบว่าวิธีนี้ไม่ได้รับการสนับสนุนอย่างเป็นทางการจาก Django และจะไม่ได้รับการสนับสนุนในอนาคตอันใกล้ (ดูความคิดเห็นนี้ในตัวติดตามข้อผิดพลาด Django: code.djangoproject.com/ticket/ 29655 # ความคิดเห็น: 3 ) ดังนั้นคุณมีแนวโน้มที่จะสะดุดกับข้อบกพร่องบางอย่างเช่นการตรวจสอบสิทธิ์ที่หยุดทำงาน ( code.djangoproject.com/ticket/29655 ) หากคุณเปิดใช้งานการตรวจสอบความถูกต้องสำหรับทุกรุ่น คุณจะต้องจัดการกับปัญหาดังกล่าวด้วยตัวเอง อย่างไรก็ตามไม่มีวิธีการที่ดีกว่า ATM
Evgeny A.

2
ในฐานะของ Django 2.2.3 สิ่งนี้ทำให้เกิดปัญหากับระบบการตรวจสอบสิทธิ์พื้นฐาน ValidationError: Session with this Session key already existsคุณจะได้รับ เพื่อหลีกเลี่ยงปัญหานี้คุณต้องเพิ่มคำสั่ง if sender in list_of_model_classesเพื่อป้องกันสัญญาณจากการแทนที่รุ่น auth เริ่มต้นของ Django กำหนดได้list_of_model_classesตามที่คุณเลือก
Addison Klinke

15

วิธีที่ง่ายที่สุดในการเรียกใช้full_cleanเมธอดคือการแทนที่saveเมธอดในmodel:

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

ทำไมสิ่งนี้ถึงดีกว่า (หรือแย่กว่า) มากกว่าการใช้สัญญาณ
J__

6
ฉันเห็นปัญหาสองประการเกี่ยวกับวิธีนี้ 1) ในกรณี full_clean () ของ ModelForm จะถูกเรียกสองครั้ง: ตามแบบฟอร์มและบันทึก 2) หากฟอร์มยกเว้นบางฟิลด์พวกเขาจะยังคงถูกตรวจสอบโดยบันทึก
เมห์เม็ต

3

แทนที่จะใส่รหัสที่ประกาศผู้รับเราสามารถใช้แอพเป็นINSTALLED_APPSส่วนได้settings.py

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

ก่อนหน้านั้นคุณอาจต้องติดตั้งdjango-fullcleanโดยใช้ PyPI:

pip install django-fullclean

13
ทำไมคุณถึงpip installมีแอพที่มีรหัส 4 บรรทัดอยู่ด้วย (ตรวจสอบซอร์สโค้ด ) แทนที่จะเขียนด้วยตัวเอง?
David D.

ห้องสมุดอื่นที่ฉันไม่ได้ลองด้วยตัวเอง: github.com/danielgatis/django-smart-save
Flimm

2

หากคุณมีแบบจำลองที่คุณต้องการให้แน่ใจว่ามีความสัมพันธ์อย่างน้อยหนึ่ง FK และคุณไม่ต้องการใช้null=Falseเพราะต้องตั้งค่าเริ่มต้น FK (ซึ่งจะเป็นข้อมูลขยะ) วิธีที่ดีที่สุดที่ฉันคิดคือ เพื่อเพิ่มกำหนดเอง.clean()และ.save()วิธีการ .clean()ยกข้อผิดพลาดในการตรวจสอบและ.save()เรียกการทำความสะอาด วิธีนี้ความสมบูรณ์จะถูกบังคับใช้ทั้งจากแบบฟอร์มและจากรหัสการโทรอื่น ๆ , บรรทัดคำสั่งและการทดสอบ หากไม่มีสิ่งนี้จะไม่มี (AFAICT) ไม่มีวิธีเขียนการทดสอบที่รับรองว่าแบบจำลองนั้นมีความสัมพันธ์กับ FK กับรุ่นอื่นที่ถูกเลือกโดยเฉพาะ (ไม่ใช่ค่าเริ่มต้น)

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

แสดงความคิดเห็นในคำตอบของ @Alfred Huang และมาที่นี่ หนึ่งอาจล็อคตะขอ pre_save ลงไปที่แอพโดยการกำหนดรายการของคลาสในโมดูลปัจจุบัน (models.py) และตรวจสอบกับมันในตะขอ 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()
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.