Home > Software design >  Trying to create a one-time calculated field in Django model
Trying to create a one-time calculated field in Django model

Time:02-06

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.

  • Related