【问题标题】:Create post_save signal for foreign key fields为外键字段创建 post_save 信号
【发布时间】:2019-02-08 19:31:26
【问题描述】:

我有一个档案模型,其中包含经验和教育作为外键领域。 当我访问配置文件模板时,它会抛出。

我试过 post_save,

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

    post_save.connect(create_education, sender=CustomUser)

它会抛出这个错误,

如何定义 post_save 信号,以便在创建个人资料时创建经验和教育?

注意:我仔细检查了错误是因为外部字段为空,即我在 django admin 中添加经验和教育字段时没有错误。

models.py

class Work_Experience(models.Model):
    job_title       = models.CharField(max_length=100, null=True, blank=True)
    company         = models.CharField(max_length=100, null=True, blank=True)
    description     = models.CharField(max_length=300, null=True, blank=True)
    exp_start_date  = models.DateField(null=True, blank=True)
    exp_end_date    = models.DateField(null=True, blank=True)


class Education(models.Model):
    degree      = models.CharField(max_length=100, null=True, blank=True)
    school      = models.CharField(max_length=100, null=True, blank=True)
    edu_start_date  = models.DateField(null=True, blank=True)
    edu_end_date    = models.DateField(null=True, blank=True)

class Profile(models.Model):    
    experience    = models.ForeignKey(Work_Experience, on_delete=models.SET_NULL, null=True, blank=True)
    education     = models.ForeignKey(Education, on_delete=models.SET_NULL, null=True, blank=True)

forms.py

class ProfileSettingsForm(forms.ModelForm):
    job_title      = forms.CharField(max_length=40, required=False)
    company        = forms.CharField(max_length=40, required=False)
    description    = forms.CharField(max_length=40, required=False)
    exp_start_date = forms.DateField(required=False)
    exp_end_date   = forms.DateField(required=False)

    degree         = forms.CharField(max_length=40, required=False)
    school         = forms.CharField(max_length=40, required=False)
    edu_start_date = forms.DateField(required=False, input_formats=settings.DATE_INPUT_FORMATS)
    edu_end_date   = forms.DateField(required=False, input_formats=settings.DATE_INPUT_FORMATS)

    def __init__(self, *args, **kwargs):
        instance = kwargs.get('instance', None)

        super(ProfileSettingsForm, self).__init__(*args, **kwargs)
        self.fields['job_title'].initial = self.instance.experience.job_title
        self.fields['company'].initial = self.instance.experience.company
        self.fields['description'].initial = self.instance.experience.description
        self.fields['exp_start_date'].initial = self.instance.experience.exp_start_date
        self.fields['exp_end_date'].initial = self.instance.experience.exp_end_date

        self.fields['degree'].initial = self.instance.education.degree
        self.fields['school'].initial = self.instance.education.school
        self.fields['edu_start_date'].initial = self.instance.education.edu_start_date
        self.fields['edu_end_date'].initial = self.instance.education.edu_end_date

    def save(self, commit=True):
        model = super(ProfileSettingsForm, self).save(commit=False)
        jt = self.cleaned_data['job_title']
        co = self.cleaned_data['company']
        desc = self.cleaned_data['description']
        esd = self.cleaned_data['exp_start_date']
        eed = self.cleaned_data['exp_end_date']

        degree = self.cleaned_data['degree']
        school = self.cleaned_data['school']
        edusd = self.cleaned_data['edu_start_date']
        edued = self.cleaned_data['edu_end_date']


        if model.experience:
            model.experience.job_title = jt
            model.experience.company = co
            model.experience.description = desc
            model.experience.exp_start_date = esd
            model.experience.exp_end_date = eed
            model.experience.save()
        else:
            model.experience = Work_Experience.objects.create(job_title=jt,
                                                              company=co,
                                                              description=desc,
                                                              exp_start_date=esd,
                                                              exp_end_date=eed)

        if model.education:
            model.education.degree = degree
            model.education.school = school
            model.education.edu_start_date = edusd
            model.education.edu_end_date = edued
            model.education.save()
        else:
            model.education = Education.objects.create(degree=degree,
                                                       school=school,
                                                       edu_start_date=edusd,
                                                       edu_end_date=edued)
        if commit:
            model.save()

        return model

Views.py

class ProfileSettingsView(UpdateView):
    model = Profile
    form_class = ProfileSettingsForm
    pk_url_kwarg = 'pk'
    context_object_name = 'object'
    template_name = 'profile_settings.html'

    def get_success_url(self):
          return reverse_lazy('users:profile_settings', args = (self.object.id,))

更新

如果我删除表单中的 init() 方法,错误就会解决。但是一旦我保存它,我就不会从表单字段中的数据库中获取值。 如何重写 init() 方法?

def __init__(self, *args, **kwargs):
        instance = kwargs.get('instance', None)

        super(ProfileSettingsForm, self).__init__(*args, **kwargs)
        self.fields['job_title'].initial = self.instance.experience.job_title
        self.fields['company'].initial = self.instance.experience.company
        self.fields['description'].initial = self.instance.experience.description
        self.fields['exp_start_date'].initial = self.instance.experience.exp_start_date
        self.fields['exp_end_date'].initial = self.instance.experience.exp_end_date

        self.fields['degree'].initial = self.instance.education.degree
        self.fields['school'].initial = self.instance.education.school
        self.fields['edu_start_date'].initial = self.instance.education.edu_start_date
        self.fields['edu_end_date'].initial = self.instance.education.edu_end_date 

【问题讨论】:

    标签: python django python-3.x django-models


    【解决方案1】:

    为什么会出错?

    行内:

    self.fields['job_title'].initial = self.instance.experience.job_title
    

    您正在处理一个没有相关experienceProfile 实例。

    如何定义 post_save 信号,以便在创建个人资料时创建经验和教育?

    如果您希望每次创建 Profile 时都会填充 am experienceeducation,您应该有如下信号:

    def create_profile(sender, instance, created, **kwargs):
        if created:
            experience = Work_Experience.objects.create(profile=instance)
            education = Education.objects.create(profile=instance)
    
    post_save.connect(create_profile, sender=Profile)
    

    为什么调用表单的save()时没有触发post_save信号?

    model = super(ProfileSettingsForm, self).save(commit=False)
    

    根据文档:

    这个 save() 方法接受一个可选的 commit 关键字参数,它接受 True 或 False。 如果您在 commit=False 的情况下调用 save(),那么它将返回一个尚未保存到数据库的对象。在这种情况下,您可以在生成的模型实例上调用 save()。如果您想在保存对象之前对其进行自定义处理,或者如果您想使用专门的模型保存选项之一,这将非常有用。 commit 默认为 True。

    所以到你这样做的时候:

    model.experience.job_title = jt
    

    您的post_save 信号尚未触发,因此model.experience 仍然是None,因此出现错误:

    'NoneType' 对象没有属性 job_title。

    【讨论】:

    • 我按照您的建议删除了 commit=False,但我仍然收到同样的错误。
    • 是的,这是一个错误,但不是你的主要问题的原因,我已经修改了答案。问题是您正在尝试访问与体验实例无关的新创建的配置文件的体验属性,请参阅答案以了解如何在创建配置文件时使用信号自动创建体验和教育。
    • 好的,但是该 post_signal 的发送者是谁?我迷失在这些 post_signals 上。
    • Profile 模型。
    • def create_exp(sender, instance, created, **kwargs): if created: experience = Work_Experience.objects.create(profile=instance) education = Education.objects.create(profile=instance) post_save .connect(create_exp, sender=Profile)
    【解决方案2】:

    你不能这样做,我建议你阅读 django 文档。只需这样做: 在此处更新

    下面的代码按预期工作..

    from django.conf import settings
    from django.db.models.signals import post_save
    from django.dispatch import receiver
    from django.contrib.auth.base_user import AbstractBaseUser
    from latiro_app.managers import UserManager
    
    
    
    class User(AbstractBaseUser):
         email         = models.CharField(verbose_name='email or phone number ', max_length=50, unique=True )
         first_name    = models.CharField('first name', max_length=15,blank=True)
         last_name     = models.CharField('last name', max_length=15,blank=True)
         country       = CountryField(blank=True)
         date_joined   = models.DateTimeField('date joined', auto_now_add=True)
         slug          = models.SlugField('slug', max_length=50, unique=True, null=True) 
         is_active     = models.BooleanField('active',default=False)
         is_staff      = models.BooleanField('staff', default=False)
         email_confirmed = models.BooleanField(default=False)
    
    
         objects = UserManager()
    
         USERNAME_FIELD = 'email'
         REQUIRED_FIELDS = []
    
    
         class Meta:
             db_table = "users" 
             permissions = (
                ("edit_user", "Edit User"),
                )
    
    
    
    
    
    
    
    
    class WorkExperience(models.Model):
         job_title       = models.CharField(max_length=100, null=True, blank=True)
         company         = models.CharField(max_length=100, null=True, blank=True)
         description     = models.CharField(max_length=300, null=True, blank=True)
         exp_start_date  = models.DateField(null=True, blank=True)
         exp_end_date    = models.DateField(null=True, blank=True)
    
        class Meta:
             db_table = "experience"
    
        def __str__(self):
            return (self.job_title)
    
    
    class Education(models.Model):
        degree      = models.CharField(max_length=100, null=True, blank=True)
        school      = models.CharField(max_length=100, null=True, blank=True)
        edu_start_date  = models.DateField(null=True, blank=True)
        edu_end_date    = models.DateField(null=True, blank=True)
    
        class Meta:
            db_table = "education"
    
        def __str__(self):
            return (self.degree)
    
    
    
    
    class Profile (models.Model):
        user            = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete= models.CASCADE,
                                         verbose_name='list of users', null=True)
        experience    = models.ForeignKey(WorkExperience, on_delete=models.SET_NULL, null=True, blank=True)
        education     = models.ForeignKey(Education, on_delete=models.SET_NULL, null=True, blank=True)
    
    
    
    
        def __str__(self):
            return (self.user)
    
        class Meta:
            db_table = "profile"
    
    @receiver(post_save, sender=settings.AUTH_USER_MODEL)
    def create_profile(sender, instance, created, **kwargs):
        if created and not kwargs.get('raw', False):
            profile = Profile(user=instance)
            profile.save()
    

    这应该可以。在我的数据库上测试:

    +----+--------------+---------------+---------+
    | id | education_id | experience_id | user_id |
    +----+--------------+---------------+---------+
    |  1 |         NULL |          NULL |       2 |
    +----+--------------+---------------+---------+
    

    education_id 和 experience_id 的 null 值将在更新 profile 时由 user_id 实例更新。

    现在用户可以像这样更新他/她的个人资料: 注意:我没有使用信号。

    #Form.py 
    class EducationForm(forms.ModelForm):
        degree         = forms.CharField(max_length=40, required=False)
        school         = forms.CharField(max_length=40, required=False)
        edu_start_date = forms.DateField(required=False, 
                         input_formats=settings.DATE_INPUT_FORMATS)
        edu_end_date   = forms.DateField(required=False, 
        input_formats=settings.DATE_INPUT_FORMATS)
    
    
        class Meta:
            model= Education
            fields =["degree","school", "edu_start_date","edu_start_date"]
    
    #View.py 
    class EducationFormView(UpdateView):
        model = Education
        form_class = EducationForm
        template_name = 'latiro_app/education_form.html'
    
        def get_success_url(self):
            return reverse_lazy('users:profile_settings',
                                                args =(self.object.id,))
    
        def get(self, form, ** kwargs):
           profile_instance = get_object_or_404(self.model, user_id=self.kwargs['pk'])
            #Pass user profile data to the form 
            context = {'form':self.form_class(instance=profile_instance)}
            if self.request.is_ajax():
                kwargs['ajax_form'] = render_to_string(self.template_name, context, request=self.request )
                return JsonResponse(kwargs)
            else:
                return render(self.request, self.template_name, context)
    

    【讨论】:

    • 您的 post_save 信号不起作用,仍然是同样的错误。 “NoneType”对象没有属性“job_title”
    • 可以发布整个跟踪吗?
    • @Ahsaan-566 OP 的信号应该可以工作,但他用commit=False 调用了表单save 方法,这会阻止保存实例,因此不会触发信号。
    • Silas 仍然没有工作,可能与 Raydel 所说的有关。 @RaydelMiranda 我把它改成 commit=True,还是不行。
    • 我编辑了我的答案,我刚刚在我的数据库上测试了它
    猜你喜欢
    • 2014-11-16
    • 1970-01-01
    • 2023-03-05
    • 2014-07-10
    • 1970-01-01
    • 2021-05-10
    • 2021-11-05
    • 2016-08-11
    相关资源
    最近更新 更多