Search code examples
djangopython-3.xdjango-formsdjango-crispy-formsinline-formset

Auto save user in Django inline formsets with Class-based views and crispy forms


I am using Django inline formsets with Class-based views and crispy forms. Currently, I have a requirement in my project where a User can submit a request for a new Study Concept and any user in the Application can provide comments and share their opinion for a Study Concept. When a user enters her or his comments and submit the form, I want to auto save the currently logged in user to the user field.

For example, User A created a new Study Concept and provided a comment (Please approve my request) while creating the new request. Then for the above comment user A should be saved.

Later User B comes and edit this request and comments " Request looks good to me" and updates the form. Now user B should be auto saved for this comment.

This is a new requirement for the project. I have tried various solutions available on stack-overflow, but I haven't succeeded yet. You can check in my code what currently I am trying to achieve.

Please find my code below:

models.py:

class StudyRequestConcept(models.Model):
    CHOICES = (
        ('Yes', 'Yes'),
        ('No', 'No'),
    )

    REQUEST_STATUS = (
        ('Pending', 'Pending'),
        ('Approved', 'Approved'),
        ('Denied', 'Denied'),
    )

    APPROVER_CHOICES = (
        ('Amey Kelekar', 'Amey Kelekar'),
    )

    requestor_name = models.CharField(max_length=240, blank=False, null=False)
    project = models.CharField(max_length=240, blank=False, null=False)
    date_of_request = models.DateField(blank=False, null=False)
    brief_summary = models.CharField(max_length=4000, blank=False, null=False)
    scientific_question = models.CharField(max_length=2000, blank=False, null=False)
    strategic_fit = models.CharField(max_length=2000, blank=False, null=False)
    collaborators = models.CharField(max_length=1000, blank=False, null=False)
    risk_assessment = models.CharField(max_length=2000, blank=False, null=False)
    devices = models.CharField(max_length=1000, blank=False, null=False)
    statistics = models.CharField(max_length=2000, blank=False, null=False)
    personnel = models.CharField(max_length=1000, blank=False, null=False)
    sample_size = models.PositiveIntegerField(blank=False, null=False, default=0)
    population = models.CharField(max_length=2000, blank=False, null=False)
    dmti_study_start_date = models.DateField(blank=False, null=False)
    duration = models.PositiveIntegerField(blank=False, null=False, default=0)
    decision_date = models.DateField(blank=False, null=False)
    deliverables = models.CharField(max_length=4000, blank=False, null=False)
    logistics_flag = models.CharField(max_length=3, choices=CHOICES, default='No')

    status = models.CharField(max_length=20, choices=REQUEST_STATUS, default='Pending')
    approver_date = models.DateField(blank=True, null=True)
    approver_name = models.CharField(max_length=240, choices=APPROVER_CHOICES, blank=True, null=True)

    user = models.ForeignKey(User, on_delete=models.CASCADE)

    def __str__(self):
        return str(self.id)

    def get_absolute_url(self):
        return reverse('update_StudyRequestConcept', kwargs={'pk': self.pk})

    def get_commentsStudyRequestConcept(self):
        return ','.join([str(i) for i in self.commentsStudyRequestConcept.all().values_list('id', flat=True)])


class CommentsStudyRequestConcept(models.Model):
    """
    A Class for Study Request Concept Comments.
    """
    comments = models.CharField('Comment', max_length=2000, blank=True, null=True)
    commented_on = models.DateTimeField(default=timezone.now)
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    studyRequestConcept = models.ForeignKey(StudyRequestConcept, related_name='commentsStudyRequestConcept', on_delete=models.CASCADE)

    def __str__(self):
        return str(self.id)

forms.py:

class StudyRequestConceptForm(forms.ModelForm):
    requestor_name = forms.CharField(label=mark_safe('<b>Name of Requestor</b>'))
    project = forms.CharField(label=mark_safe('<b>On behalf of which project or platform</b> <br/>  <i>(What is the portfolio project / asset team / RU which will benefit from this study? At what development stage is it?)</i>'))
    date_of_request = forms.DateField(label=mark_safe('<b>Date of Request</b>'), 
        input_formats=['%Y-%m-%d'],
        widget=XDSoftDateTimePickerInput()
    )
    brief_summary = forms.CharField(label=mark_safe('<b>Brief Summary of Project</b>'))
    scientific_question = forms.CharField(label=mark_safe('<b>Scientific Question</b> <br/><i>(What is the question you would like answered? What is the challenge we are trying to address?)</i>'))
    strategic_fit = forms.CharField(label=mark_safe('<b>Strategic Fit / Impact Statement </b><br/><i>(What is the rationale for this study? What is the potential value to Pfizer? What asset team would it support, and have you secured endorsement from that asset team?)</i>'))
    collaborators = forms.CharField(label=mark_safe('<b>Potential Collaborators (Internal & External)</b> <br/><i>(Who are the key collaborators required to execute the study?)</i>'))
    risk_assessment = forms.CharField(label=mark_safe('<b>Risk Assessment</b> <br/> <i>(What are the risks you foresee? If so, what is your proposed mitigation plan?)</i>'))
    devices = forms.CharField(label=mark_safe('<b>Devices / imaging modality</b> <br/> <i>(What device(s) will be deployed, if known)</i>'))
    statistics = forms.CharField(label=mark_safe('<b>Statistics</b> <br/><i>(Have you consulted with a statistician? If so, with whom?)</i>'))
    personnel = forms.CharField(label=mark_safe('<b>Anticipated personnel needs</b> <br/> <i>(Technician(s), Data Manager(s), CTS, etc.)</i>'))
    sample_size = forms.IntegerField(label=mark_safe('<b>Anticipated sample size</b>'), min_value=0, max_value=2147483647)
    population = forms.CharField(label=mark_safe('<b>Anticipated study population, or animal model</b><br/><i> (Pfizer internal, healthy, specific disease population…)</i>'))
    dmti_study_start_date = forms.DateField(label=mark_safe('<b>Anticipated DMTI study start date</b>'), 
        input_formats=['%Y-%m-%d'],
        widget=XDSoftDateTimePickerInput()
    )
    duration = forms.IntegerField(label=mark_safe('<b>Estimated study duration (months)</b>'), min_value=0, max_value=2147483647)
    decision_date = forms.DateField(label=mark_safe('<b>Anticipated Asset Team study start date (or asset decision date)</b><br/><i> (For decision making to inform internal phased study design</i>'), 
        input_formats=['%Y-%m-%d'],
        widget=XDSoftDateTimePickerInput()
    )
    deliverables = forms.CharField(label=mark_safe('<b>Expected deliverables</b><br/><i> (Device selection & validation, algorithm developed to qualify…)</i>'))
    source = forms.CharField(max_length=50, widget=forms.HiddenInput(), required=False)

    def clean(self):
        requestor_name = self.cleaned_data.get('requestor_name')
        project = self.cleaned_data.get('project')
        date_of_request = self.cleaned_data.get('date_of_request')
        brief_summary = self.cleaned_data.get('brief_summary')
        scientific_question = self.cleaned_data.get('scientific_question')
        strategic_fit = self.cleaned_data.get('strategic_fit')
        collaborators = self.cleaned_data.get('collaborators')
        risk_assessment = self.cleaned_data.get('risk_assessment')
        devices = self.cleaned_data.get('devices')
        statistics = self.cleaned_data.get('statistics')
        personnel = self.cleaned_data.get('personnel')
        sample_size = self.cleaned_data.get('sample_size')
        population = self.cleaned_data.get('population')
        dmti_study_start_date = self.cleaned_data.get('dmti_study_start_date')
        duration = self.cleaned_data.get('duration')
        decision_date = self.cleaned_data.get('decision_date')
        deliverables = self.cleaned_data.get('deliverables')

        if (dmti_study_start_date not in EMPTY_VALUES) and (dmti_study_start_date < datetime.date.today()):
            self._errors['dmti_study_start_date'] = self.error_class([
                'The date cannot be in the past.'])

        if (dmti_study_start_date not in EMPTY_VALUES) and (decision_date not in EMPTY_VALUES) and (decision_date < dmti_study_start_date):
            self._errors['decision_date'] = self.error_class([
                'The date cannot be earlier Anticipated DMTI study start date.'])

        return self.cleaned_data

    class Meta:
        model = StudyRequestConcept
        exclude = ('user', 'logistics_flag', 'status','approver_date','approver_name')



class CommentsStudyRequestConceptForm(forms.ModelForm):
    comments = forms.CharField(label='Comments',
                                    widget=forms.TextInput(attrs={
                                        'class': 'form-control',
                                    }))


    def clean(self):
        comments = self.cleaned_data.get('comments')

    class Meta:
        model = CommentsStudyRequestConcept
        exclude = ('commented_on', 'user')

CommentsStudyRequestConceptFormset = inlineformset_factory(StudyRequestConcept, CommentsStudyRequestConcept, form=CommentsStudyRequestConceptForm, extra=1)

views.py:

class StudyRequestConceptCreate(CreateView):
    model = StudyRequestConcept
    form_class = StudyRequestConceptForm


class StudyRequestConceptFormsetCreate(CreateView):
    model = StudyRequestConcept
    template_name = 'createStudyRequestConcept.html'
    form_class = StudyRequestConceptForm
    success_url = reverse_lazy('create_StudyRequestConcept')

    def get_context_data(self, **kwargs):
        data = super(StudyRequestConceptFormsetCreate, self).get_context_data(**kwargs)
        if self.request.POST:
            data['comment'] = CommentsStudyRequestConceptFormset(self.request.POST, prefix='comments')
        else:
            data['comment'] = CommentsStudyRequestConceptFormset(prefix='comments')
        return data

    def form_valid(self, form):
        context = self.get_context_data()
        comment = context['comment']
        with transaction.atomic():
            if comment.is_valid():
                self.object = form.save(commit=False)
                self.object.user = self.request.user
                self.object = form.save()
                comment.instance = self.object
                commentinstance = comment.save(commit=False)
                commentinstance.user = self.request.user
                commentinstance.save()

                messages.success(self.request, StudyRequestConcept.__name__ +' Form ID: '+ str(self.object.id) + ' was submitted successfully')
                return super(StudyRequestConceptFormsetCreate, self).form_valid(form)
            else:
                return self.render_to_response(self.get_context_data(form=form))


class StudyRequestConceptUpdate(UpdateView):
    model = StudyRequestConcept
    form_class = StudyRequestConceptEditForm
    template_name = 'updateStudyRequestConcept.html'
    success_url = '/'


class StudyRequestConceptFormsetUpdate(UpdateView):
    model = StudyRequestConcept
    form_class = StudyRequestConceptEditForm
    template_name = 'updateStudyRequestConcept.html'
    success_url = reverse_lazy('edit_StudyRequestConcept')

    def get_context_data(self, **kwargs):
        data = super(StudyRequestConceptFormsetUpdate, self).get_context_data(**kwargs)
        if self.request.POST:
            data['comment'] = CommentsStudyRequestConceptFormset(self.request.POST, prefix='comments', instance=self.object)
        else:
            data['comment'] = CommentsStudyRequestConceptFormset(prefix='comments', instance=self.object)
        return data

    def form_valid(self, form):
        context = self.get_context_data()
        comment = context['comment'] 

        with transaction.atomic():
            self.object = form.save()

            if comment.is_valid():
                comment.instance = self.object
                commentinstance = comment.save(commit=False)    
                commentinstance.user = self.request.user
                commentinstance.save()


        messages.success(self.request, (StudyRequestConcept.__name__) +' Form ID: '+ str(self.object.id) + ' was updated successfully')
        return super(StudyRequestConceptFormsetUpdate, self).form_valid(form)

Since 'commented_on' field defaults to timezone.now, it automatically saves it to the table. But it's not the same case with the user.

The error I am getting is:

IntegrityError at /1/updateStudyRequestConcept/ RequestPortal_commentsstudyrequestconcept.user_id may not be NULL

Any help or advice will be appreciated! I will be more than happy to provide any further code or information needed. Thanks in advance for all the support and help.

Regards,

Amey Kelekar


Solution

  • I believe instead of saying this as an answer to my question, I will say that it is a workaround. Since I just require the name of the person who wrote the comment, I updated my models.py as below:

    class CommentsStudyRequestConcept(models.Model):
        """
        A Class for Study Request Concept Comments.
        """
        comments = models.CharField('Comment', max_length=2000, blank=True, null=True)
        commented_on = models.DateTimeField(default=timezone.now)
        commented_by = models.CharField(max_length=2000, blank=True, null=True)
        studyRequestConcept = models.ForeignKey(StudyRequestConcept, related_name='commentsStudyRequestConcept', on_delete=models.CASCADE)
    
        def __str__(self):
            return str(self.id)
    
    

    I removed the Foreign key user and replaced it with a non-mandatory character field commented_by. Accordingly, I updated my forms.py as below:

    class CommentsStudyRequestConceptForm(forms.ModelForm):
        comments = forms.CharField(label='Comments',
                                        widget=forms.TextInput(attrs={
                                            'class': 'form-control',
                                        }),
                                        required=False)
    
        commented_by = forms.CharField(label='Commented By',
                                        widget=forms.TextInput(attrs={
                                            'class': 'form-control',
                                            'readonly': 'readonly'
                                        }),
                                        required=False)
        def clean(self):
            comments = self.cleaned_data.get('comments')
            commented_by = self.cleaned_data.get('commented_by')
    
        class Meta:
            model = CommentsStudyRequestConcept
            exclude = ('commented_on', )
    

    I also made sure that the field commented_by is read-only.

    In my views.py, once the form is valid, I updated the comments table as below:

    #While creating a new Study concept
    CommentsStudyRequestConcept.objects.filter(studyRequestConcept_id=self.object.id).update(commented_by=self.request.user.first_name + " " + self.request.user.last_name)
    
    #while adding a new comment to the Study concept:
    CommentsStudyRequestConcept.objects.filter(studyRequestConcept_id=self.object.id).filter(commented_by__exact='').update(commented_by=self.request.user.first_name + " " + self.request.user.last_name)
    

    Any better solution is welcomed. Please note that in this solution you have to provide First and Last name while creating the User inorder to make this logic work without any concerns. If you want you can keep the Foreign key user instead of commented_by but make it non-mandatory and use the same logic as discussed above.

    Regards,

    Amey Kelekar