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!