Neden django's model.save (), full_clean () öğesini çağırmıyor?


150

Sadece django's orm'ın bir model formunun parçası olarak kaydedilmedikçe bir model üzerinde 'full_clean' dememesinin iyi bir nedeni olup olmadığını bilen merak ediyorum.

Modelinizin save () yöntemini çağırdığınızda full_clean () işlevinin otomatik olarak çağrılmayacağını unutmayın. Kendi oluşturduğunuz modelleriniz için tek adımlı model doğrulaması yapmak istediğinizde manuel olarak çağırmanız gerekir. django tam temiz doktor

(NOT: teklif Django 1.6 için güncellendi ... önceki django dokümanlarında ModelForms hakkında da bir uyarı vardı.)

İnsanların bu davranışı istememelerinin iyi nedenleri var mı? Bir modele doğrulama eklemek için zaman ayırdıysanız, modelin her kaydedilişinde bu doğrulamanın çalışmasını istersiniz.

Her şeyin düzgün çalışmasını nasıl sağlayacağımı biliyorum, sadece bir açıklama arıyorum.


11
Bu soru için çok teşekkür ederim, kafamı duvara daha fazla vurmamı engelledi. Başkalarına yardımcı olabilecek bir mixin oluşturdum. Özeti
inceleyin

Ve nihayet pre_savekanca yakalamak ve full_cleanyakalanan tüm modellerde yapmak için sinyal kullanıyorum .
Alfred Huang

Yanıtlar:


59

AFAIK, bunun nedeni geriye dönük uyumluluktur. Hariç tutulan alanlara sahip ModelForms, varsayılan değerlere sahip modeller, pre_save () sinyalleri vb. İle ilgili sorunlar da vardır.

İlgilenebileceğiniz kaynaklar:


3
İkinci referansın en yararlı alıntısı (IMHO): "Hem son durumları ele alacak kadar kullanışlı ve hem de yeterince sağlam bir" otomatik "doğrulama seçeneği geliştirmek - mümkünse - Bu nedenle, şimdilik, Django'da böyle bir şey yok ve 1.2'de olmayacak. 1.3 için çalıştırabileceğinizi düşünüyorsanız, en iyi bahsiniz bir en azından bazı örnek kodlar da dahil olmak üzere, hem basit hem de sağlam olmasını nasıl sağlayacağınıza ilişkin bir açıklama. "
Josh

30

Uyumluluk dikkate alındığında, django çekirdeğinde kaydetme sırasında otomatik temizleme etkinleştirilmez.

Yeni bir proje başlatıyor ve saveModel'deki varsayılan yöntemin otomatik olarak temizlenmesini istiyorsak, her model kaydedilmeden önce temizlemek için aşağıdaki sinyali kullanabiliriz.

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
Bu neden önce full_clean öğesini çağırmak, sonra super () öğesini çağırmak için bazı BaseModel'deki (diğerlerinin devralacağı) kaydetme yöntemini geçersiz kılmaktan daha iyi (veya daha kötü)?
J__

7
Bu yaklaşım ile iki sorun görüyorum 1) ModelForm'un full_clean () iki kez çağrılacaksa: form ve sinyal ile 2) Form bazı alanları dışlarsa, yine de sinyal tarafından onaylanır.
mehmet

1
@mehmet Yani bunlar ekleyebilir olabilir if send == somemodel, then exclude some fieldsdepre_save_handler
Simin Jie

4
Bu yaklaşımı kullanan veya kullanmayı düşünenler için: bu yaklaşımın Django tarafından resmi olarak desteklenmediğini ve öngörülebilir gelecekte desteklenmeyeceğini unutmayın (Django hata izleyicisindeki şu yoruma bakın: code.djangoproject.com/ticket/ 29655 # comment: 3 ), bu nedenle , tüm modeller için doğrulamayı etkinleştirirseniz, kimlik doğrulamasının çalışmayı durdurması ( code.djangoproject.com/ticket/29655 ) gibi bazı kusurlarla karşılaşabilirsiniz . Bu tür sorunlarla başa çıkmak zorunda kalacaksınız. Ancak, daha iyi bir yaklaşım yoktur.
Evgeny A.

2
Django 2.2.3 itibariyle, bu temel kimlik doğrulama sistemiyle ilgili bir soruna neden olur. Bir alacaksınız ValidationError: Session with this Session key already exists. Bunu önlemek için sender in list_of_model_classes, sinyalin Django'nun varsayılan kimlik doğrulama modellerini geçersiz kılmasını önlemek için bir if ifadesi eklemeniz gerekir . list_of_model_classesİstediğiniz şekilde tanımlayın
Addison Klinke

15

Çağırmak için simpliest yolu full_cleanyöntemi sadece kılma etmektir saveGözlerinde farklı yöntemle model:

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

Bu neden bir sinyal kullanmaktan daha iyi (veya daha kötü)?
J__

6
Bu yaklaşım ile iki sorun görüyorum 1) ModelForm'un full_clean () iki kez çağrılacaksa: form ve kaydetme tarafından 2) Form bazı alanları dışlarsa, yine de kaydetme ile doğrulanır.
mehmet

3

Bir alıcı bildiren bir kod parçası eklemek yerine, bir uygulamayı INSTALLED_APPSbölüm olarak kullanabilirsiniz .settings.py

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

Bundan önce, django-fullcleanPyPI kullanarak yüklemeniz gerekebilir :

pip install django-fullclean

13
Neden bu satırları kendiniz yazmak yerine pip install4 satır kod içeren bir uygulama ( kaynak kodunu kontrol edin )?
David D.21

Kendimi denemediğim başka bir kütüphane: github.com/danielgatis/django-smart-save
Flimm

2

En az bir FK ilişkisi olduğundan emin olmak istediğiniz bir modeliniz varsa ve kullanmak istemiyorsanız, null=Falsebunun için varsayılan bir FK (çöp verisi olacaktır) ayarlamanızı gerektiriyorsa, en iyi yöntem özel .clean()ve .save()yöntem eklemek için . .clean()doğrulama hatasını yükseltir ve .save()temizlemeyi çağırır. Bu şekilde bütünlük hem formlardan hem de diğer arama kodlarından, komut satırından ve testlerden uygulanır. Bu olmadan, (AFAICT) bir modelin özel olarak seçilmiş (varsayılan olmayan) başka bir modelle FK ilişkisi olmasını sağlayan bir test yazmanın bir yolu yoktur.

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

@Fredfred Huang'ın yanıtı ve cevabını yorumluyor. Mevcut modülde (models.py) sınıfların bir listesini tanımlayarak ve pre_save kancada kontrol ederek pre_save kancasını bir uygulamaya kilitleyebilir:

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()
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.