Search code examples
pythondjangocreate-view

NOT NULL constraint failed error when attempting to save using createview


This is my first post to SO, so please let me know if I've missed any important details. I am working on updates to a home-grown dJango based ticketing system.

I have two "parent" models (ParentProjects and Projects) that capture details about work we want to track. Both models have a number of columns that store information in the associated tables as well as some FK relations.

The generic class-based detail view is used to view objects in the Project table while the ParentProject table is accessed by a function based view. The function-based view accomplishes the same task of loading parent project object values as the class-based detail view does for the project objects.

The problem I am having is that I cannot add a new entry to the IDTasks model that automatically inserts the Parent Project id. I am able to add a new IDTask from within the admin site (or from the "client" site if I enable the "parent" field within the modelform) by manually selecting the parent I wish to associate the IDTask to. I can also edit and save an existing IDTask from within the Parent Project detail view without any issues. However, when I attempt to add an IDTask using the createview, dJango reports a Not NULL constraint error and the new entry is not saved.

In addition to reviewing and trying many other solutions to this problem, I have disabling the code that automatically adds the logged in user id but am still getting the same null constraint error. What's strange is that I am using the same basic createview structures for adding FK objects to the Projects model and this works perfectly. I'm still getting comfortable with Django's class-based views, so must surely be missing something obvious.

Thank you for your help!

Here are the main views related to my issue:

# Detail view for Parent Projects (function-based)
def ParentProjectDetail(request,parent_id):
   parents = ParentProject.objects.get(id=parent_id)
   projects = Project.objects.filter(parent_project__pk=parent_id).order_by('project_phase', '-launch_date',)
   return render(request, 'otis/parent_detail.html', context={'parents':parents, 'projects':projects })

# Detail View for Projects (class-based)
 class ProjectDetailView(generic.DetailView):
    model = Project
    context_object_name = 'project'
    template_name = 'otis/project_detail.html'

# Add parent progress report
 class add_idtask_view(SuccessMessageMixin, CreateView):
    model = IDTasks
    template_name = 'otis/id_report_form.html'
    form_class = idTaskForm
    success_message = "Report added"

    def form_valid(self, form):
       idtaskform = form.save(commit=False)
       idtaskform.user = self.request.user
       self.parents_id = self.kwargs.get('parent_id')
       form.instance.ParentProject = get_object_or_404(ParentProject,id=self.parents_id)
       return super(add_idtask_view, self).form_valid(form)
  
    def get_success_url(self):
       return reverse_lazy('otis:parent_detail', kwargs={'pk': self.parents_id})

Here is the modelform:

 class idTaskForm(ModelForm):
    class Meta:
    model = IDTasks
    fields = ('parent_phase','complete','milestones','nextsteps','concerns')
    widgets = {
       'milestones': Textarea(attrs={'cols': 50, 'rows': 5, 'placeholder': 'Task details...'}),
       'nextsteps': Textarea(attrs={'cols': 50, 'rows': 5, 'placeholder': 'Task details...'}),
       'concerns': Textarea(attrs={'cols': 50, 'rows': 5, 'placeholder': 'Task details...'}),
    }
    labels = {
       'parent_phase': mark_safe('<span class="required">Phase</span>'),
       'complete': mark_safe('<span class="required">Percentage Complete</span>'),
       'milestones': ('Milestones'),
       'nextsteps': ('Next steps'),
       'concerns': ('Concerns'),
   }

Here are the two models being accessed:

# Parent Project Model
 class ParentProject(models.Model):
    class Meta:
    verbose_name = "parent project"
    verbose_name_plural = "parent projects"
    ordering = ['title']

    title = models.CharField('Name', max_length=100, null=True, blank=False)
    parent_group = models.ForeignKey(ProjectGroups, on_delete=models.CASCADE, blank=True, null=True)
    parent_type = models.ForeignKey(ProjectTypes, on_delete=models.CASCADE, null=True, blank=False)
    description = models.TextField('description', blank=True)
    term_due = models.ForeignKey(Terms, on_delete=models.CASCADE, blank=True, null=True)
    term_year_due = models.ForeignKey(Years, on_delete=models.CASCADE, blank=True, null=True)
    launch_date = models.DateField('launch date', blank=True, null=True)
    parent_phase = models.ForeignKey(SDLCPhases, on_delete=models.CASCADE, null=True, blank=False)
    history = HistoricalRecords()

    def __str__(self):
       return str(self.title)
    
    def get_absolute_url(self):
       return reverse('otis:parent_detail', kwargs={'pk': self.pk})

# Reports Model
 class IDTasks(models.Model):
    class Meta:
    verbose_name = "Parent task"
    verbose_name_plural = "Parent tasks"
    ordering = ['updated_on']

    parent = models.ForeignKey(ParentProject, on_delete=models.CASCADE)
    parent_phase = models.ForeignKey(SDLCPhases, on_delete=models.CASCADE, null=True, blank=False)
    user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
    complete = models.IntegerField('percentage complete', blank=True, null=True, default=0)
    milestones = models.TextField('milestones', blank=True)
    nextsteps = models.TextField('next steps', blank=True)
    concerns = models.TextField('concerns', blank=True)
    updated_on = models.DateTimeField(auto_now_add=True, null=True)

    history = HistoricalRecords()

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

Here are the url patterns:

# url for the parent project detail view
 path('parent_detail/<int:parent_id>/', views.ParentProjectDetail, name='parent_detail'),

# url for the create report accessed within the detail view
 path('parent_detail/<int:parent_id>/add_id_report/', views.add_idtask_view.as_view(), name='add_id_report'),

Finally, the template link that invokes the modelform:

 <a href="{% url 'otis:add_id_report' parents.id %}">link_title</a>

Solution

  • I was able to solve my initial problem by restructuring the view like so:

    class add_idtask_view(SuccessMessageMixin, CreateView):
       model = IDTasks
       template_name = 'otis/id_report_form.html'
       form_class = idTaskForm
       success_message = "Report added"
    
       def form_valid(self, form):
         parents_pid = self.kwargs.get('parent_pid') 
         self.parent_id = parents_pid
         form.instance.user = self.request.user
         form.instance.parent_id = parents_pid
         return super(add_idtask_view, self).form_valid(form)
    
      def get_success_url(self):
         return reverse_lazy('otis:parent_detail', kwargs={'parent_pid': self.parent_id})
    

    The first thing I did was sort out what form fields and database fields I was trying to access. It seems I had confused these and not properly referenced them in my initial view.

    Once these were working however, I started getting an error that stated the view was expecting an integer but was getting a string. It seems, for whatever reason, when I used the get_object_or_404 method, the view returns the title of the database object and not the primary key.