使用基于 Class 的视图和清晰的表单在 Django 内联表单集中自动保存用户

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

我正在使用 Django 内联表单集以及基于 Class 的视图和清晰的表单。目前,我在我的项目中有一个要求,用户可以提交对新研究概念的请求,应用程序中的任何用户都可以提供评论并分享他们对研究概念的意见。当用户输入她或他的评论并提交表单时,我想将当前登录的用户自动保存到用户字段。

例如,用户 A 创建了一个新的研究概念,并在创建新请求时提供了评论(请批准我的请求)。然后对于上面的评论用户A应该被保存。

稍后用户 B 来编辑此请求并评论“请求看起来不错”并更新表单。现在用户 B 应该会自动保存此评论。

这是项目的新要求。我已经尝试了各种关于堆栈溢出的解决方案,但我还没有成功。您可以查看我的代码,了解我目前正在努力实现的目标。

请在下面找到我的代码:

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)

由于'commented_on'字段默认为timezone.now,它会自动将其保存到table。但是用户就不一样了

我得到的错误是:

/1/updateStudyRequestConcept/ 处的 IntegrityError RequestPortal_commentsstudyrequestconcept.user_id 可能不为 NULL

如有任何帮助或建议,我们将不胜感激!我将非常乐意提供所需的任何进一步代码或信息。预先感谢所有的支持和帮助。

此致,

Amey Kelekar

我认为与其说这是对我的问题的回答,不如说这是一种解决方法。因为我只需要写评论的人的名字,所以我更新了我的 models.py 如下:

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)

我删除了外键 user 并将其替换为 non-mandatory 字符字段 commented_by。因此,我更新了我的 forms.py 如下:

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', )

我还确保字段 commented_by 是 read-only。

在我的 views.py 中,一旦表格有效,我就更新评论 table 如下:

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

欢迎任何更好的解决方案。请注意,在此解决方案中,您必须在创建用户时提供名字和姓氏,以便使此逻辑毫无顾虑地工作。如果你愿意,你可以保留外键 user 而不是 commented_by 但将其设为 non-mandatory 并使用与上面讨论的相同的逻辑。

此致,

Amey Kelekar