Попытка создать одноразовое вычисляемое поле в модели Django
Строю свое первое приложение на Django и столкнулся с проблемой. У меня есть модель Django, которая создает объекты Job, и я хочу, чтобы каждый код задания был уникальным и автоматически генерировался в определенном формате. Формат следующий: aaaMMnnYYYY, где aaa - это 3-буквенный идентификатор клиента, который мы задаем, nn - это счетчик, который представляет n-ое задание от этого клиента в этом месяце, а MM и YYYY - это месяц и год соответственно. Например, для 3-го задания от клиента "AIE" в феврале 2023 года идентификатор будет AIE02032023.
Использование вычисляемого поля с декоратором @property приводит к постоянному обновлению поля, поэтому я пытаюсь сделать это путем модификации метода save(). Есть также связанный объект Cost, который имеет атрибуты Job через Foreign Key. Сейчас, когда я добавляю Cost, "итерирующая" часть кода задания выполняет итерации, изменяя код задания, что вызывает ошибки уникальности, а также ошибки URL (я использую код задания в URLConf.
).В качестве побочного примечания, я также хотел бы иметь возможность переопределять код задания. Есть ли способ установить флаги в модели, такие как job_code_overridden = False и т.д.?
Вот соответствующий код, сообщите мне, что еще вам нужно увидеть.
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,
})
Наконец, в методе save() я знаю, что могу сделать что-то вроде
if job_code != get_job_code():
job_code = get_job_code()
...но "месяц" задания часто меняется в течение жизни задания, и если я запущу get_job_code() после изменения месяца, то код задания снова изменится.
Я проделал то, что пробовал выше^
Возможным решением является добавление дополнительного поля флага job_code_fixed
к модели Job
. Флаг job_code
должен генерироваться только один раз при создании объекта Job, а не при последующих сохранениях. Этого можно достичь, установив job_code_fixed
в True
после того, как job_code
будет сгенерировано в методе save
, и генерируя job_code
только в том случае, если job_code_fixed
будет False.
Вот обновленная версия кода:
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)
Добавив флаг job_code_fixed
, job_code
будет генерироваться только один раз и никогда не будет изменяться, решая проблему с изменением кодов заданий, вызывающих ошибки уникальности и URL.
В итоге я просто переопределил метод save()
, используя флаг job_code_is_fixed
boolean, чтобы job_code
не обновлялся.
class Job(models.Model):
#...
job_code = models.CharField(
max_length=15, unique=True, blank=True, null=True
)
job_code_is_fixed = models.BooleanField(default=False)
#...
def set_job_code(self, prefix=None, year=None, month=None):
'''
Create a job code in the following format:
{prefix}{month:02d}{i:02d}{year}
e.g. APL06012023:
prefix: APL
month: 06
i: 01
year: 2023
params:
prefix: job code prefix
year: the year to be used in the job code
month: the month to be used in the job code
i: iterator for generating unique job codes
passable args are auto generated, so they default to None unless passed in.
This function will run in the save() method as long as job_code_is_fixed is False
'''
jc = ''
prefix = self.client.job_code_prefix if prefix is None else prefix
month = int(timezone.now().month) if month is None else month
year = int(timezone.now().year) if year is None else year
i = 1
while jc == '' and i <= 99:
temp_jc = f'{prefix}{month:02d}{i:02d}{year}'
if not Job.objects.filter(job_code=temp_jc).exists():
jc = temp_jc
return jc
else:
i += 1
if jc == '':
# Handle error
def save(self, *args, **kwargs):
# Auto-generate the job code
if not self.job_code_is_fixed:
self.job_code = self.set_job_code()
self.job_code_is_fixed = True
# ...
super().save(*args, **kwargs)