Mengapa django model.save () tidak memanggil full_clean ()?

150

Saya hanya ingin tahu apakah ada yang tahu jika ada alasan bagus mengapa django orm tidak memanggil 'full_clean' pada model kecuali disimpan sebagai bagian dari model form.

Perhatikan bahwa full_clean () tidak akan dipanggil secara otomatis ketika Anda memanggil metode save () model Anda. Anda harus menyebutnya secara manual saat Anda ingin menjalankan validasi model satu langkah untuk model yang Anda buat sendiri. django doc lengkap bersih

(CATATAN: kutipan diperbarui untuk Django 1.6 ... dokumen Django sebelumnya memiliki peringatan tentang ModelForms juga.)

Adakah alasan bagus mengapa orang tidak menginginkan perilaku ini? Saya pikir jika Anda meluangkan waktu untuk menambahkan validasi ke model, Anda ingin validasi dijalankan setiap kali model disimpan.

Saya tahu bagaimana agar semuanya berjalan dengan baik, saya hanya mencari penjelasan.

Harun
sumber
11
Terima kasih banyak untuk pertanyaan ini, itu menghentikan saya dari membenturkan kepala saya ke dinding lebih banyak waktu. Saya membuat mixin yang mungkin bisa membantu orang lain. Lihat intinya: gist.github.com/glarrain/5448253
glarrain
Dan saya akhirnya menggunakan sinyal untuk menangkap pre_savekait dan lakukan full_cleanpada semua model yang tertangkap.
Alfred Huang

Jawaban:

59

AFAIK, ini karena kompatibilitas ke belakang. Ada juga masalah dengan ModelForms dengan bidang yang dikecualikan, model dengan nilai default, sinyal pre_save (), dll.

Sumber tempat Anda tertarik:

lqc
sumber
3
Kutipan yang paling membantu (IMHO) dari referensi kedua: "Mengembangkan opsi validasi" otomatis "yang keduanya cukup sederhana untuk benar-benar berguna dan cukup kuat untuk menangani semua kasus tepi adalah - jika itu mungkin - jauh lebih banyak daripada dapat diselesaikan pada jangka waktu 1,2. Oleh karena itu, untuk saat ini, Django tidak memiliki hal seperti itu, dan tidak akan memilikinya dalam 1.2. Jika Anda berpikir Anda dapat membuatnya bekerja untuk 1.3, taruhan terbaik Anda adalah untuk menyelesaikan proposal, termasuk setidaknya beberapa kode contoh, bersama dengan penjelasan tentang bagaimana Anda akan membuatnya tetap sederhana dan kuat. "
Josh
30

Karena pertimbangan kompatibilitas, pembersihan otomatis saat penyimpanan tidak diaktifkan di django kernel.

Jika kita memulai proyek baru dan ingin savemetode default pada Model dapat membersihkan secara otomatis, kita dapat menggunakan sinyal berikut untuk melakukan pembersihan sebelum setiap model disimpan.

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()
Alfred Huang
sumber
2
Mengapa ini lebih baik (atau lebih buruk) daripada mengganti metode save pada beberapa BaseModel (yang akan diwarisi oleh semua orang lain) untuk memanggil full_clean terlebih dahulu, kemudian memanggil super ()?
J__
7
Saya melihat dua masalah dengan pendekatan ini 1) dalam kasus full_clean () ModelForm akan dipanggil dua kali: oleh formulir dan oleh sinyal 2) Jika formulir mengecualikan beberapa bidang, mereka masih akan divalidasi oleh sinyal.
mehmet
1
@mehmet Jadi mungkin Anda dapat menambahkan ini if send == somemodel, then exclude some fieldsdipre_save_handler
Simin Jie
4
Bagi mereka yang menggunakan atau mempertimbangkan untuk menggunakan pendekatan ini: perlu diingat bahwa pendekatan ini tidak secara resmi didukung oleh Django dan tidak akan didukung dalam waktu dekat (lihat komentar ini di pelacak bug Django: code.djangoproject.com/ticket/ 29655 # komentar: 3 ), jadi Anda mungkin akan menemukan beberapa ketidaksempurnaan seperti otentikasi berhenti bekerja ( code.djangoproject.com/ticket/29655 ) jika Anda mengaktifkan validasi untuk semua model. Anda harus berurusan dengan masalah seperti itu sendiri. Namun, tidak ada atm pendekatan yang lebih baik.
Evgeny A.
2
Pada Django 2.2.3, ini menyebabkan masalah dengan sistem otentikasi dasar. Anda akan mendapatkan ValidationError: Session with this Session key already exists. Untuk menghindari hal ini, Anda perlu menambahkan pernyataan if untuk sender in list_of_model_classesmencegah sinyal mengesampingkan model autentikasi default Django. Tetapkan list_of_model_classesbagaimana pun yang Anda pilih
Addison Klinke
15

Cara paling sederhana untuk memanggil full_cleanmetode adalah dengan mengganti savemetode di model:

def save(self, *args, **kwargs):
    self.full_clean()
    return super(YourModel, self).save(*args, **kwargs)
Batal
sumber
Mengapa ini lebih baik (atau lebih buruk) daripada menggunakan sinyal?
J__
6
Saya melihat dua masalah dengan pendekatan ini 1) dalam kasus full_clean () ModelForm akan dipanggil dua kali: oleh formulir dan oleh save 2) Jika formulir mengecualikan beberapa bidang, mereka masih akan divalidasi oleh save.
mehmet
3

Alih-alih menyisipkan sepotong kode yang menyatakan penerima, kita dapat menggunakan aplikasi sebagai INSTALLED_APPSbagian dalamsettings.py

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

Sebelum itu, Anda mungkin perlu menginstal django-fullcleanmenggunakan PyPI:

pip install django-fullclean
Alfred Huang
sumber
13
Mengapa Anda pip installbeberapa aplikasi dengan 4 baris kode di dalamnya (periksa kode sumber ) daripada menulis sendiri baris ini?
David D.
Perpustakaan lain yang belum saya coba sendiri: github.com/danielgatis/django-smart-save
Flimm
2

Jika Anda memiliki model yang ingin Anda pastikan memiliki setidaknya satu hubungan FK, dan Anda tidak ingin menggunakan null=Falsekarena itu memerlukan pengaturan default FK (yang akan menjadi data sampah), cara terbaik yang saya buat adalah untuk menambahkan kustom .clean()dan .save()metode. .clean()memunculkan kesalahan validasi, dan .save()memanggil clean. Dengan cara ini integritas ditegakkan baik dari formulir dan dari kode panggilan lain, baris perintah, dan tes. Tanpa ini, ada (AFAICT) tidak ada cara untuk menulis tes yang memastikan bahwa model memiliki hubungan FK dengan model lain yang dipilih secara spesifik (bukan default).

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
shacker
sumber
1

Mengomentari @Alfred jawaban Huang dan setuju. Seseorang mungkin mengunci kait pre_save ke aplikasi dengan mendefinisikan daftar kelas dalam modul saat ini (models.py) dan memeriksa di kait 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()
Peter Shannon
sumber