Django CBV использует значения из Modelform для вычисления других полей модели
Я пытаюсь сделать систему управления складом с помощью Django 3.2 на основе следующих моделей:
class itemtype(models.Model):
item_id = models.IntegerField(primary_key=True)
item_name = models.CharField(max_length=100)
group_name = models.CharField(max_length=100)
category_name = models.CharField(max_length=100)
mass = models.FloatField()
volume = models.FloatField()
used_in_storage = models.BooleanField(default=False, null=True)
class Meta:
indexes = [
models.Index(fields=['item_id'])
]
def __str__(self):
return '{}, {}'.format(self.item_id, self.item_name)
class material_storage(models.Model):
storage_id = models.AutoField(primary_key=True)
material = models.ForeignKey(itemtype, on_delete=models.PROTECT)
amount_total = models.IntegerField(null=True)
price_avg = models.FloatField(null=True)
order_amount = models.IntegerField(null=True)
order_price = models.IntegerField(null=True)
timestamp = models.DateTimeField(default=timezone.now)
def __str__(self):
return '{}, {} avg.: {} ISK'.format(self.material, self.amount, self.price)
"itemtype" определяет в основном возможные объекты, которые могут храниться, а "material_storage" показывает, что есть на складе. Я попытался объединить общее количество каждого товара, а также среднюю цену, уплаченную за него, и количество и цену для одного заказа в одной строке базы данных. Идея заключается в том, чтобы получить последнюю запись для выбранного товара/материала, когда происходит новый заказ, добавить сумму этого заказа и пересчитать среднюю цену. Теоретически это можно разделить на две таблицы, но в данный момент я не вижу причин для этого. Однако я не могу разобраться с кодом функции для выполнения вычислений. Я новичок в Django и поэтому немного ошеломлен сложностью. Я пытался использовать представления на основе классов и модельные формы, для простых вещей, которые работали хорошо, но теперь я немного потерялся.
Создание формы только для добавления новых строк в эту таблицу хранения было в порядке.
class NewAssetForm(forms.ModelForm):
material = MaterialChoiceField(models.itemtype.objects.filter(used_in_storage= True))
def __init__(self, *args, **kwargs):
super(NewAssetForm, self).__init__(*args, **kwargs)
self.fields['amount'].widget.attrs['min'] = 1
self.fields['price'].widget.attrs['min'] = 1
class Meta:
model = models.material_storage
fields = (
'material',
'amount',
'price'
)
widgets = {
'material': forms.Select(),
}
То же самое для View, чтобы обработать его.
class NewItemView(FormView):
template_name = 'assetmanager/newasset.html'
form_class = forms.NewAssetForm
success_url = '/storage/current'
def form_valid(self, form):
return super().form_valid(form)
Но теперь я застрял. Я думал, что это должна быть довольно стандартная задача, но я не смог найти решение для нее до сих пор. Идея была в том, чтобы поместить это в функцию form_valid, взять материал из формы, найти последнюю соответствующую запись, добавить новую сумму, а также рассчитать новую среднюю цену и сохранить все вместе в модель. Пока я нашел только несколько примеров, сопоставимых с моей проблемой, и я не смог перевести их на мою установку, так что, возможно, кто-то может дать мне подсказку для более успешного поиска или предоставить мне пример, как подойти к этой теме.
thx in advance.
Для изменения значений полей формы можно переопределить метод "clean" и предоставить значения полям формы. Доступ к данным можно получить с помощью "self.clean_data", это словарь.
class NewAssetForm(ModelForm):
def clean(self):
super().clean()
# place code that retrieves existing data and calculate new values.
self.cleaned_data['price'] = 'New Value'
очищенные_данные будут переданы в "form_valid", там вы можете вызвать функцию сохранения. "form.save()" создаст новую строку, убедитесь, что вы передаете действительные значения представлениям. Поскольку вы принимаете в форму несколько полей, убедитесь, что у вас есть значения по умолчанию для полей, которые не включены в объект формы.
Спасибо за ответ Я нашел решение, используя метод form_valid() в FormView. Большая часть кода используется для создания записей на основе существующих записей или для проверки, есть ли уже записи для того же материала.
class NewItemView(FormView):
template_name = 'assetmanager/newasset.html'
form_class = forms.NewAssetForm
success_url = '/storage/current'
def form_valid(self, form):
try:
# check if there is already a record for this material.
material_storage.objects.filter(material_id = form.cleaned_data['material'])[:1]
# getting total amount and average price values from latest entry with said material.
total_amount = material_storage.objects.values('amount_total').filter(material_id=form.cleaned_data['material']).order_by('-timestamp')[:1][0]['amount_total']
avg_price = material_storage.objects.values('price_avg').filter(material_id=form.cleaned_data['material']).order_by('-timestamp')[:1][0]['price_avg']
amount = form.cleaned_data['amount']
price = form.cleaned_data['price']
# calculating new total amount and average price based on old values and new entry.
form.instance.amount_total = total_amount + amount
form.instance.price_avg = ((avg_price * total_amount) + (price * amount)) / (total_amount + amount)
form.save()
except material_storage.DoesNotExist:
# if there is no entry for the chosen material yet, amount = total amount, price = average price.
form.instance.amount_total = form.cleaned_data['amount']
form.instance.price_avg = form.cleaned_data['price']
form.save()
return super().form_valid(form)
На данный момент это решает мою проблему, однако я не знаю, имеет ли смысл выбранное место (form_valid()) - ваш ответ предполагает, что это имело бы больше смысла в другом месте. Кроме того, проверка того, существует ли уже запись для материала, и выбор значений из такой записи, не очень элегантны и эффективны. Но, как уже было сказано, я начинающий - буду рад любым предложениям по улучшению.
Я также пока не уверен, что это справится со всеми возможными особыми случаями, которые могут появиться...