如何正确创建覆盖save()方法的相关对象?

时间:2017-12-18 13:04:31

标签: django django-models django-forms

我在UserCreationForm的子类上覆盖了save()方法。我之所以这样做是因为我想在创建User对象时创建另一个相关对象 以下是表单以及save方法:

class MyUserCreationForm(UserCreationForm):

    error_message = UserCreationForm.error_messages.update({
        'duplicate_username': 'This username has already been taken.'
    })

    class Meta(UserCreationForm.Meta):
        model = User

    def clean_username(self):
        username = self.cleaned_data["username"]
        try:
            User.objects.get(username=username)
        except User.DoesNotExist:
            return username
        raise forms.ValidationError(self.error_messages['duplicate_username'])

    def save(self, commit=True):
        user = super(MyUserCreationForm, self).save(commit=False)
        if commit:
            user.save()
            Profile.objects.create(user=user)
        return user  

因此永远不会创建Profile对象。从技术上讲,如果我删除if commit:,我可以让它工作:

    def save(self, commit=True):
        user = super(MyUserCreationForm, self).save(commit=False)
        user.save()
        Profile.objects.create(user=user)
        return user   

但是,我想知道为什么每次创建用户时False都会传递给save()方法。基于我所阅读的内容,条件应该存在,以便保留与被覆盖的save()方法相同的行为。

2 个答案:

答案 0 :(得分:1)

这张表格在哪里使用?例如,如果它位于ModelAdmin,可能是因为ModelAdmin有自己的save_model方法。

def save_model(self, request, obj, form, change):
    """
    Given a model instance save it to the database.
    """
    obj.save()

由于这是要保存模型的地方,我将假设commitFalse,以便可以在此处完成。

例如,您可以尝试在ModelAdmin中覆盖此内容,但这取决于使用它的位置。 Dan Loewenherz是正确的,你不应该使用commit检查这样的东西。通常,它更适合与表单一起使用,而这是围绕模型的问题。

我认为这里的问题是你正在使用的模式。我认为最好保持表格的目的与模型本身更紧密地联系在一起。

如果稍后使用不同的方法或表单创建用户,会发生什么?他们将缺少Profile。我假设这是常见的用户/用户配置文件问题,它来自内置的auth用户模型的限制,在这种情况下,User没有Profile可能是灾难性的。

我的建议是在post_save中使用models.py信号:

from django.db.models.signals import post_save
from django.contrib.auth.models import User
from models import UserProfile
from django.db import models

def create_profile(sender, instance, created **kwargs):
    if created:
        Profile.objects.create(user = instance)

post_save.connect(create_profile, sender = User, dispatch_uid = "users-
profilecreate-signal")

有些人对使用信号犹豫不决,但在这种情况下,它们是保护模型完整性的最可靠方法。

另一种选择是在User的模型级别进行。像这样:

def save(self, *args, *kwargs):
    user = super(User, self).save(*args, **kwargs)
    Profile.objects.get_or_create(user = user)
    return user

答案 1 :(得分:1)

commit=False旨在用于内联表单。它用于将父键分配给表单实例并稍后保存该实例。

来源:https://github.com/django/django/blob/master/django/forms/models.py#L942

您应该能够稍微调整一下您的解决方案。

def save(self, *args, **kwargs):
    user = super(MyUserCreationForm, self).save(*args, **kwargs)
    if user.pk:  # If user object has been saved to the db
        Profile.objects.get_or_create(user=user)
    return user

但我建议使用Django的模型信号。

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

@receiver(post_save, sender=User)
def user_created_signal_handler(request, user, *args, **kwargs):
    Profile.objects.get_or_create(user=user)

https://docs.djangoproject.com/en/2.0/ref/signals/#django.db.models.signals.post_save