Building my first Django app, and I'm running into a snag. I have a Django model that creates Job objects, and I want each job code to be unique and auto-generated, with a particular format. The format is: aaaMMnnYYYY, where aaa is a 3-letter client identifier that we set, nn is a counter that represents the nth job from that client in that month., and MM and YYYY are month and year respectively. e.g., for the 3rd job from client "AIE" in feb 2023, the ID would be AIE02032023.
Using a calculated property with the @property decorator causes the field to be updated with every save, so I'm trying to do this by modifying the save() method. There's also a related Cost object that has a Job attribute as a Foreign Key. The way I have it now, the job code gets assigned as expected, but when I add a Cost to the Job, the 'iterating' part of the job code iterates, changing the job code, which causes uniqueness errors as well as URL errors (I'm using the job code in the URLConf. Is there any way to have this field get calculated once and then never change?
As a side note, I'd also like to be able to override the job code. Is there a way to set flags within a model, such as job_code_overridden = False, etc.?
Here's the relevant code, let me know what else you need to see.
models.py:
class Job(models.Model):
job_name = models.CharField(max_length=50, default='New Job')
client = models.ForeignKey(Client, on_delete=models.CASCADE)
job_code = models.CharField(max_length=15, unique=True,)
def get_job_code(self):
'''
I only want this to run once
Format abcMMnnYYYY
'''
jc = ''
prefix = self.client.job_code_prefix
month = str(str(self.job_date).split('-')[1])
identifier = len(Job.objects.filter(job_date__contains = f'-{month}-',
client__job_code_prefix = prefix)) 2
year = str(str(self.job_date).split('-')[0])
jc = f'{prefix}{month}{identifier:02d}{year}'
return jc
@property
def total_cost(self):
all_costs = Cost.objects.filter(job__job_code = self.job_code)
total = 0
if all_costs:
for cost in all_costs:
total = cost.amount
return total
# Is there a way to add something like the flags in the commented-out code here?
def save(self, *args, **kwargs):
# if not self.job_code_fixed:
if self.job_code != self.get_job_code():
self.job_code = self.get_job_code()
# self.job_code_fixed = True
super().save(*args, **kwargs)
costsheet.py:
class costsheetView(ListView):
template_name = "main_app/costsheet.html"
form_class = CostForm
model = Cost
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
current_job_code = self.kwargs['job_code']
currentJob = Job.objects.get(job_code=current_job_code)
return context
def get(self, request, *args, **kwargs):
cost_form = self.form_class()
current_job_code = self.kwargs['job_code']
currentJob = Job.objects.get(job_code=current_job_code)
all_costs = Cost.objects.filter(job__job_code = current_job_code)
return render(request, self.template_name, {'cost_form':cost_form, 'currentJob':currentJob,'all_costs':all_costs})
def post(self, request, *args, **kwargs):
cost_form = self.form_class(request.POST)
current_job_code = self.kwargs['job_code']
currentJob = Job.objects.get(job_code=current_job_code)
messages = []
errors = ''
if cost_form.is_valid():
instance = cost_form.save()
instance.job = currentJob
instance.save()
currentJob.vendors.add(instance.vendor)
currentJob.save()
messages.append(f'cost added, job date: {currentJob.job_date}')
else:
print('oops')
print(cost_form.errors)
errors = cost_form.errors
all_costs = Cost.objects.filter(job__job_code = current_job_code)
return render(request, self.template_name, {'cost_form':cost_form,
'currentJob':currentJob,
'errors':errors,
'messages':messages,
'all_costs':all_costs,
})
Lastly, in the save() method I know I could do something like
if job_code != get_job_code():
job_code = get_job_code()
..but the job 'month' often changes throughout the life of the job, and if I run get_job_code() after the month changes then the job code will change again, which is undesirable.
CodePudding user response:
A possible solution is to add an additional flag field, job_code_fixed
, to the Job
model. The job_code
should only be generated once when the Job object is created, and not on subsequent saves. This can be achieved by setting job_code_fixed
to True
after the job_code
is generated in the save
method, and only generating the job_code
if the job_code_fixed
is False.
Here is an updated version of the code:
class Job(models.Model):
job_name = models.CharField(max_length=50, default='New Job')
client = models.ForeignKey(Client, on_delete=models.CASCADE)
job_code = models.CharField(max_length=15, unique=True,)
job_code_fixed = models.BooleanField(default=False)
def get_job_code(self):
'''
I only want this to run once
Format abcMMnnYYYY
'''
jc = ''
prefix = self.client.job_code_prefix
month = str(str(self.job_date).split('-')[1])
identifier = len(Job.objects.filter(job_date__contains = f'-{month}-',
client__job_code_prefix = prefix)) 2
year = str(str(self.job_date).split('-')[0])
jc = f'{prefix}{month}{identifier:02d}{year}'
return jc
@property
def total_cost(self):
all_costs = Cost.objects.filter(job__job_code = self.job_code)
total = 0
if all_costs:
for cost in all_costs:
total = cost.amount
return total
def save(self, *args, **kwargs):
if not self.job_code_fixed:
self.job_code = self.get_job_code()
self.job_code_fixed = True
super().save(*args, **kwargs)
By adding the job_code_fixed
flag, the job_code
will only be generated once and never changed, solving the problem with changing job codes causing uniqueness and URL errors.