Ahsaan-566
Ahsaan-566

Reputation: 603

Create post_save signal for foreign key fields

I have a profile model which contains experience and education as foreign key fields. When I access profile template, it throws.

enter image description here

I tried post_save,

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

    post_save.connect(create_education, sender=CustomUser)

it throws this error,

enter image description here

How do I define a post_save signal so experience and education are created when I create a profile?

Note: I double checked the error is because foreign fields are empty i.e there is no error when I add experience and education fields in 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,))

UPDATE

If I remove the init() method in form, the error resolves. But I don't get the values from database in the form fields once I save it. How can I rewrite init() method?

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 

Upvotes: 1

Views: 1465

Answers (2)

Silasi
Silasi

Reputation: 129

You cannot do that,i recommend you read django docs. just Do this: Update here

The code bellow work as expected..

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()

This should work. Tested on my database:

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

The null values on education_id and experience_id will be update by user_id instance when updating profile.

Now User can update his/her profile like this: note:i'm not using signal.

#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)

Upvotes: 0

Raydel Miranda
Raydel Miranda

Reputation: 14360

Why the error?

In the line:

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

you're dealing with a Profile instance that does not have a related experience.

How do I define a post_save signal so experience and education are created when I create a profile?

If you want every time you create a Profile it gets populated with am experience and education you should have a signal like:

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)

Why the post_save signal is not triggered when calling the save() of the form?

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

According the docs:

This save() method accepts an optional commit keyword argument, which accepts either True or False. If you call save() with commit=False, then it will return an object that hasn’t yet been saved to the database. In this case, it’s up to you to call save() on the resulting model instance. This is useful if you want to do custom processing on the object before saving it, or if you want to use one of the specialized model saving options. commit is True by default.

So by the time you do:

model.experience.job_title = jt

your post_save signal hasn't been triggered and therefore model.experience remains None hence the error:

'NoneType' object has no attribute job_title.

Upvotes: 2

Related Questions