0

I am getting the following error in my code:

"null value in column "bbso_record_id_id" of relation "sos_categorytoolsandequipment" violates not-null constraint DETAIL: Failing row contains (1, 1, , null)."

The thing this, the code I have works with SQLlite but since I upgraded to Postgress all of a sudden it breaks and if I go back to SQllite it works again.

I realize that the problem is with the OneToOne relationship but I am not sure how to retify it. It seems like my BBSO_Record has to save and get an id to pass to the OneToOne field first. But the problem is it works in SQLlite.

This is my code and any assistance will be greatly appreciated.

MODELS

from django.db import models
from django.db.models.deletion import CASCADE
from django.core.validators import RegexValidator, MinValueValidator, MaxValueValidator
#from django.db.models.enums import choices
from django.utils import timezone
from django.urls import reverse  # page 138 Django for Beginneers 
from django.conf import settings # 

from django.utils import timezone
from django.utils.timezone import now


class BBSO_Records(models.Model):

   COMPANY_OR_CONTRACTOR = (
       ('company', 'Company'),
       ('contractor', 'Contractor'),
      )

   severityLevel = (
       ('1', 'Level 1'),
       ('2', 'Level 2'),
       ('3', 'Level 3'),
      )
   
   severity_level = models.CharField(max_length=6, default= 3 , choices=severityLevel)
   date_recorded  = models.DateField()
   observer_name_ID = models.ForeignKey(settings.AUTH_USER_MODEL , on_delete=models.PROTECT, verbose_name="Observer Name")  # i need to double check how I would be handling this. 
   observer_department_ID =  models.ForeignKey(Observer_Department, on_delete=models.PROTECT, verbose_name="Observer Department")
   site_location_ID = models.ForeignKey(Site_Locations, on_delete=models.PROTECT, verbose_name='Location of Observation')
   location_details = models.CharField(max_length=50, help_text='e.g. In the kitchen on the 4th floor.', blank=True)
   company_or_contractor = models.CharField(max_length=10, choices=COMPANY_OR_CONTRACTOR)
  
   number_of_persons_observed = models.IntegerField(default=1, validators=[MinValueValidator(0),
                                      MaxValueValidator(25)],verbose_name='# of Persons Observed')  #  https://stackoverflow.com/questions/849142/how-to-limit-the-maximum-value-of-a-numeric-field-in-a-django-model
   # time_spent = models.IntegerField(verbose_name="Time Spent Observing (mins)", default=True)
   PTW = models.CharField(max_length=10, verbose_name="Permit To Work Number", blank=True)
   JSA = models.CharField(max_length=10,verbose_name="Job Safety Analysis Number", blank=True)
   bbso_title = models.CharField(max_length=100,verbose_name="Title of Observation", help_text='e.g. Contractor Employee not wearing correct PPE for the job (i.e. wrong gloves).')
   details_of_observation  = models.TextField()
   date_created = models.DateTimeField(auto_now_add=True, editable=True)  # setting editable equal=False wouldnot make this contorl show on the form
   date_updated = models.DateTimeField(auto_now=True, editable=True)
   
   def __str__(self) :
       return f'ID:{self.id} ;  SOS Title: {self.bbso_title}; Date Recorded: {self.date_recorded}'

   def has_related_object(self):
       return hasattr(self, 'CategoryToolsAndEquipment') 


   def get_absolute_url(self):
       return reverse ('bbso_records_detail', args=[str(self.id)])

   class Meta:
       ordering = ["date_created"]
       verbose_name_plural = "BBSO_Records" 
       # unique_together = ('field_1', 'field_2',)

class CategoryToolsAndEquipment (models.Model):

   ACCEPTABLE_UNACCEPTABLE = (
       ('1', 'ACCEPTABLE'),
       ('0', 'UNACCEPTABLE'),
   )

   bbso_record_id =  models.OneToOneField(BBSO_Records, on_delete=models.CASCADE, primary_key=True )
   right_for_the_job = models.CharField(max_length=1, blank=True, choices=ACCEPTABLE_UNACCEPTABLE)
   in_fit_for_use_condition = models.CharField(max_length=1, blank=True, choices=ACCEPTABLE_UNACCEPTABLE)
   used_correctly = models.CharField(max_length=1,blank=True, choices=ACCEPTABLE_UNACCEPTABLE)

   def __str__(self):
       return self.right_for_the_job

   class Meta:
       verbose_name_plural = "CategoryToolsAndEquipment"

FORMS

from crispy_forms.helper import FormHelper
from crispy_forms.layout import Column, Layout, Field, Fieldset, Div, Row, HTML, ButtonHolder, Submit
from crispy_forms.bootstrap import TabHolder, Tab

#  3rd Party
from .widgets import XDSoftDateTimePickerInput
from .custom_layout_object import Formset
# from bootstrap_datepicker_plus import DatePickerInput, DateTimePickerInput

# forms 
from django import forms
from django.forms import ModelForm, Textarea
from django.forms.models import inlineformset_factory 


# models 
from .models import BBSO_Records, BBSO_Record_Actions, Category_PPE
from .models import CategoryToolsAndEquipment
from .models import Category_Environmental
from .models import Category_Driving
from .models import Category_Health_Hygiene
# from .models import Category_Peoples_Reactions
from .models import Category_Potential_For_Injury
from .models import Category_Permts_and_Procedures
from .models import Category_Workspace_Ergonomics
from .models import Category_Housekeeping
from .models import Site_Locations
from .models import Company, Classification_Code, Site_Locations, Observer_Department

class DateInput(forms.DateInput):
    input_type = 'date'


class BBSORecordsForm(ModelForm):
    # date_recorded = forms.DateTimeField(input_formats=['%d/%m/%Y %H:%M'], widget=XDSoftDateTimePickerInput())
    # observer_name_ID = forms.CharField(widget=forms.HiddenInput())

    class Meta:
        model = BBSO_Records
        fields = '__all__'
        exclude = ('observer_name_ID',)
        widgets = {
            'date_recorded': DateInput(),
            # 'id': forms.HiddenInput()
        }


        # widgets = { 'date_recorded': DateTimePickerInput(options={"showClose": True,"showClear": True,"showTodayButton": True,}), } 
        # https://pypi.org/project/django-bootstrap-datepicker-plus/ 
        # exclude = ['date_created', 'date_updated']

    def __init__(self, *args, **kwargs):
        super(BBSORecordsForm, self).__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.form_tag = True
        self.helper.form_class = 'form-horizontal'
        self.helper.label_class = 'col-sm-3 create-label'
        self.helper.field_class = 'col-sm-9'
        self.helper.attrs = {'novalidate':''}  # this is to remove crispyform validation from pop up to red
        self.helper.attrs['autocomplete'] = 'off'
        self.helper.layout = Layout(
            Div(
                Field('id'),
                Field('severity_level'),
                Field('date_recorded'),  #   css_class='form-group'
                Field('observer_department_ID'),
                Field('site_location_ID'),
                Field('location_details'),
                Field('company_or_contractor'),
                Field('number_of_persons_observed'),
                Field('PTW'),
                Field('JSA'),
                Field('bbso_title'),
                Field('details_of_observation', id='mytextarea'), # https://www.tiny.cloud/docs/quick-start/
                # Field('observer_name_ID'),
                TabHolder(
                    Tab('Tools & Equipment',  Formset('category_tools_equipment')),   # this takes the name from the view which is passed here: bbso_record_actions = context['bbso_record_actions']           
                    Tab('PPE',  Formset('category_ppe')),             
                    Tab('Environmental',  Formset('category_environmental')),             
                    Tab('Driving',  Formset('category_driving')),              
                    Tab('Health & Hygiene',  Formset('category_healthhygiene')),              
                    Tab('Potential for Injury',  Formset('category_potential_for_injury')),              
                    Tab('Permits & Procedures',  Formset('category_permtis_and_procedures')),              
                    Tab('Workspace Ergonomics',  Formset('category_workspace_ergonomics')),              
                    Tab('Housekeeping',  Formset('category_housekeeping')),              
                    # Tab('Tools & Equipment',Formset('categorytoolsAndEquipment'))
                ),
                TabHolder(
                    Tab('SOS Actions',  Formset('bbso_record_actions'))
                    # Field('field_name_3', css_class="extra")
                    # Div('field_name_2')
                ),
                # Fieldset('Add SOS Actions', Formset('bbso_record_actions')),
                HTML("<br>"),
                ButtonHolder(Submit('submit', 'Save', css_class='btn btn-primary btn-xs')),
            )
        )

class CategoryToolsAndEquipmentForm(ModelForm):

    class Meta:
        model = CategoryToolsAndEquipment
        fields = '__all__'
    
    def __init__(self, *args, **kwargs):
        super(CategoryToolsAndEquipmentForm, self).__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.form_tag = False
        self.helper.form_class = 'form-horizontal'
        self.helper.label_class = 'col-sm-3 create-label'
        self.helper.field_class = 'col-sm-9'

CategoryToolsAndEquipmentFormset = inlineformset_factory(BBSO_Records, 
                                      CategoryToolsAndEquipment,                                    
                                      fields = [ 'right_for_the_job',
                                                'in_fit_for_use_condition',
                                                'used_correctly' ],
                                      form = CategoryToolsAndEquipmentForm,
                                      can_delete=False) 


VIEW

class BBSORecord_NestedForm_CreateView(LoginRequiredMixin, CreateView):  # main form and nested form for BBSO Records
    model  = BBSO_Records
    template_name = 'sos/bbso_records/bbso_record_create.html' # 'sos/bbso_records/bbso_record_nested_new.html'
    form_class = BBSORecordsForm
    # fields = ('date_recorded','time_of_observation', 'observer_department_ID', 'site_location_ID', 
    #         'location_details', 'company_or_contractor','number_of_persons_observed', 'time_spent', 
    #         'PTW', 'JSA', 'details_of_observation')
    # exclude = ('observer_name_ID')
    # success_url =  None #  reverse_lazy('home')

    def get_context_data(self, **kwargs):
        data = super(BBSORecord_NestedForm_CreateView, self).get_context_data(**kwargs)
        if self.request.POST:
            data['bbso_record_actions'] = BBSO_Record_ActionsFormset(self.request.POST)
            data['category_tools_equipment'] = CategoryToolsAndEquipmentFormset(self.request.POST)
            data['category_ppe'] = CategoryPPEFormset(self.request.POST)
            data['category_environmental'] = CategoryEnvironmentalFormset(self.request.POST)
            data['category_driving'] = CategoryDrivingFormset(self.request.POST)
            data['category_healthhygiene'] = CategoryHealthHygieneFormset(self.request.POST)
            data['category_potential_for_injury'] = CategoryPotentialForInjuryFormset(self.request.POST)
            data['category_permtis_and_procedures'] = CategoryPermitsandProceduresFormset(self.request.POST)
            data['category_workspace_ergonomics'] = CategoryWorkspaceErgonomicsFormset(self.request.POST)
            data['category_housekeeping'] = CategoryHouseKeepingFormset(self.request.POST)
            
        else:
            data['bbso_record_actions'] = BBSO_Record_ActionsFormset()
            data['category_tools_equipment'] = CategoryToolsAndEquipmentFormset()
            data['category_ppe'] = CategoryPPEFormset()
            data['category_environmental'] = CategoryEnvironmentalFormset()
            data['category_driving'] = CategoryDrivingFormset()
            data['category_healthhygiene'] = CategoryHealthHygieneFormset()
            data['category_potential_for_injury'] = CategoryPotentialForInjuryFormset()
            data['category_permtis_and_procedures'] = CategoryPermitsandProceduresFormset()
            data['category_workspace_ergonomics'] = CategoryWorkspaceErgonomicsFormset()
            data['category_housekeeping'] = CategoryHouseKeepingFormset()
            
        return data

    def form_valid(self, form):
        context = self.get_context_data()

        bbso_record_actions = context['bbso_record_actions']
        category_tools_equipment = context['category_tools_equipment']
        category_ppe = context['category_ppe']
        category_environmental = context['category_environmental']
        category_driving = context['category_driving']
        category_healthhygiene = context['category_healthhygiene']
        category_potential_for_injury = context['category_potential_for_injury']
        category_permtis_and_procedures = context['category_permtis_and_procedures']
        category_workspace_ergonomics = context['category_workspace_ergonomics']
        category_housekeeping = context['category_housekeeping']
            
        
        with transaction.atomic():
            form.instance.observer_name_ID = self.request.user
            self.object = form.save()  #moved to inside the .is_valid

            if bbso_record_actions.is_valid():                
                bbso_record_actions.instance = self.object
                bbso_record_actions.save()

            # if category_tools_equipment.is_valid(): 
                category_tools_equipment.instance = self.object
                category_tools_equipment.save()

                category_ppe.instance = self.object
                category_ppe.save()  

                category_environmental.instance = self.object
                category_environmental.save()   

                category_driving.instance = self.object
                category_driving.save()      

                category_healthhygiene.instance = self.object
                category_healthhygiene.save()   

                category_potential_for_injury.instance = self.object
                category_potential_for_injury.save()     

                category_permtis_and_procedures.instance = self.object
                category_permtis_and_procedures.save() 

                category_workspace_ergonomics.instance = self.object
                category_workspace_ergonomics.save()  

            # if category_housekeeping.is_valid(): 
                category_housekeeping.instance = self.object
                category_housekeeping.save()               

                return super(BBSORecord_NestedForm_CreateView, self).form_valid(form)
            else:
                return self.render_to_response(self.get_context_data(form=form))

    def get_success_url(self):
        return reverse_lazy('bbso_records_detail', kwargs={'pk': self.object.pk})

custom_layout_object.py

from crispy_forms.layout import LayoutObject  #, TEMPLATE_PACK
from crispy_forms.utils import TEMPLATE_PACK
from django.shortcuts import render
from django.template.loader import render_to_string


###### Thanks!
###### https://stackoverflow.com/questions/15157262/django-crispy-forms-nesting-a-formset-within-a-form/22053952#22053952

class Formset(LayoutObject):
    template = "mycollections/formset.html"

    def __init__(self, formset_name_in_context, template=None):
        self.formset_name_in_context = formset_name_in_context
        self.fields = []
        if template:
            self.template = template

    def render(self, form, form_style, context, template_pack=TEMPLATE_PACK):
        formset = context[self.formset_name_in_context]
        return render_to_string(self.template, {'formset': formset}, context['request'])

Hey Guys, this is the last thing to complete my form and any help will be greatly appreicated!

Dane
  • 11
  • 4

1 Answers1

0

It's a good practice to link your project to any DBMS before writing any models, I suggest that you create migrations again with the project linked to Postgres, also try to provide a default record for any OneToOne, FK etc... field: for example:

bbso_record_id =  models.OneToOneField(BBSO_Records, on_delete=models.CASCADE, primary_key=True,default=1)
Dharman
  • 30,962
  • 25
  • 85
  • 135