我知道,出现此问题是因为代码尝试使用已存在的唯一键组合向数据库添加条目。但我的问题是:为什么以及如何防止这种情况?根据我的逻辑,对我来说这种情况并没有意义。
我有这段代码,基本上为发票存储了一些静态标签。我们的想法是在不存在的情况下即时创建标签,而不是手动将其插入代码中的许多地方。
每当生成Item
时,也会创建ItemInv
,并通过保存后者,检查是否存在静态标签。如果没有,则创建一个新的。获取(或创建)静态标签的代码也会在显示发票/草稿时调用。
错误似乎就在那里发生。标签不存在,创建了一个新标签,但不知怎的,这种情况发生了两次?我的意思是,我真的不明白为什么代码会尝试两次添加相同的项目。这可能是一个线程问题吗?
我没有使用get_or_create
,因为ItemStaticInv
(static_account_label
,static_agreement_label
...)上的某些属性可以更新,而不是唯一的。在创建发票之前,草稿具有相同的结构,并且可以更改数据,包括这些标签。
class ItemInv(managers.Model):
"""
Relation between Item and Invoice
"""
item = models.ForeignKey('Item')
invoice = models.ForeignKey('Invoice')
[...]
class Meta:
unique_together = ('item', 'invoice')
def save(self, *args, **kwargs):
iteminv = super(ItemInv, self).save(*args, **kwargs)
# If the item does not have the static information, one should be created
self.item.get_invoice_static(self.invoice)
return iteminv
class Item(managers.Model):
"""
Item to be put on an invoice
"""
serial = models.AutoField(primary_key=True) # Software license
product = models.ForeignKey('Product', verbose_name='Product')
account = models.ForeignKey('Account', verbose_name='Account', related_name='items')
[...]
def get_invoice_static(self, document):
try:
return self.document_labels.get(invoice=document)
except ObjectDoesNotExist:
return ItemStaticLabel.objects.create(
item=self,
invoice=document,
static_account_label=str(self.account),
static_customer_label=str(self.account.customer),
static_agreement_label=str(self.account.agreement)
)
class ItemStaticLabel(managers.Model):
"""
We must store static information about the Item on an invoice.
"""
item = models.ForeignKey('Item', related_name='document_labels')
invoice = models.ForeignKey('Invoice', related_name='item_labels')
static_account_label = models.CharField(max_length=256, null=True, blank=True)
static_customer_label = models.CharField(max_length=256, null=True, blank=True)
static_agreement_label = models.CharField(max_length=256, null=True, blank=True)
static_expiration_label = models.DateField(max_length=256, null=True, blank=True)
class Meta:
unique_together = ('item', 'invoice')
这有几个缺陷,但这个系统是由其他人开发的,正在生产中。我能做的结构变化的数量非常有限。因此,我必须尝试了解导致此问题的原因。
Traceback:
File "/home/apps/sites/invoice/source/v3/models.py" in get_invoice_static
1105. return self.document_labels.get(invoice=document)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/db/models/manager.py" in manager_method
85. return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/db/models/query.py" in get
380. self.model._meta.object_name
During handling of the above exception (ItemStaticLabel matching query does not exist.), another exception occurred:
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/db/backends/utils.py" in execute
65. return self.cursor.execute(sql, params)
The above exception (duplicate key value violates unique constraint "v3_itemstaticlabel_item_id_f9405967_uniq"
DETAIL: Key (item_id, invoice_id)=(1840, 1578) already exists.
) was the direct cause of the following exception:
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/core/handlers/exception.py" in inner
41. response = get_response(request)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/core/handlers/base.py" in _legacy_get_response
249. response = self._get_response(request)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/core/handlers/base.py" in _get_response
187. response = self.process_exception_by_middleware(e, request)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/core/handlers/base.py" in _get_response
185. response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/usr/local/lib/python3.4/contextlib.py" in inner
30. return func(*args, **kwds)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/views/generic/base.py" in view
68. return self.dispatch(request, *args, **kwargs)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/views/generic/base.py" in dispatch
88. return handler(request, *args, **kwargs)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/views/generic/detail.py" in get
116. context = self.get_context_data(object=self.object)
File "/home/apps/sites/invoice/source/web/views/invoice.py" in get_context_data
280. context['document_html'] = services.document_to_html(obj)
File "/home/apps/sites/invoice/source/v3/services.py" in document_to_html
1550. 'account': set_[0].get_invoice_static(document).static_account_label,
File "/home/apps/sites/invoice/source/v3/models.py" in get_invoice_static
1112. static_agreement_label=str(self.account.agreement)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/db/models/manager.py" in manager_method
85. return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/db/models/query.py" in create
394. obj.save(force_insert=True, using=self.db)
File "/home/apps/sites/invoice/source/v3/managers.py" in save
724. super().save(*args, **kwargs)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/db/models/base.py" in save
808. force_update=force_update, update_fields=update_fields)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/db/models/base.py" in save_base
838. updated = self._save_table(raw, cls, force_insert, force_update, using, update_fields)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/db/models/base.py" in _save_table
924. result = self._do_insert(cls._base_manager, using, fields, update_pk, raw)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/db/models/base.py" in _do_insert
963. using=using, raw=raw)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/db/models/manager.py" in manager_method
85. return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/db/models/query.py" in _insert
1076. return query.get_compiler(using=using).execute_sql(return_id)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/db/models/sql/compiler.py" in execute_sql
1107. cursor.execute(sql, params)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/db/backends/utils.py" in execute
65. return self.cursor.execute(sql, params)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/db/utils.py" in __exit__
94. six.reraise(dj_exc_type, dj_exc_value, traceback)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/utils/six.py" in reraise
685. raise value.with_traceback(tb)
File "/home/apps/sites/invoice/env/lib/python3.4/site-packages/django/db/backends/utils.py" in execute
65. return self.cursor.execute(sql, params)
Exception Type: IntegrityError at /invoice-menu/customer/251/invoices/1578/
Exception Value: duplicate key value violates unique constraint "v3_itemstaticlabel_item_id_f9405967_uniq"
DETAIL: Key (item_id, invoice_id)=(1840, 1578) already exists.
class InvoiceDetailView(DetailView):
model = Invoice
template_name = 'invoice_detail.html'
pk_url_kwarg = 'invoice_key'
context_object_name = 'invoice'
def get_context_data(self, **kwargs):
context = super(InvoiceDetailView, self).get_context_data(**kwargs)
obj = context[self.context_object_name]
obj = models.Invoice.objects.filter(pk=obj.pk).select_related(
'customer',
'customer__original',
'currency',
).prefetch_related(
'citems',
'citems__assocs',
'citems__assocs__product',
'iteminv_set',
'iteminv_set__item',
).first()
context['document_html'] = services.document_to_html(obj)
return context
def document_to_html(document):
"""
Renders the invoice to html.
"""
from v3.models import Item, Maintenance, Invoice, CustomInvoiceLine
from web.collection_utils import collapse_list
data = {}
items = Item.objects.on_invoice(document)
mains = Maintenance.objects.on_invoice(document)
rows = get_invoice_product_rows(document)
# merge the lists
rows['itemrows'].extend(rows['mainrows'])
include_summary = len(rows['itemrows']) > 0 and not Invoice.objects.filter(creditted_by=document)
# calc VAT lines
vats = {}
for ir in rows['itemrows']:
row = ir['line']
vat = ir['objs'][0].vat
rate = ir['objs'][0].vat_rate
total = (Decimal(rate / 100) * ir['rowtotal']).quantize(Decimal('.01'), rounding=ROUND_HALF_UP)
try:
vats[vat]['rownums'].append(row)
vats[vat]['total'] += total
except:
vats[vat] = dict(rownums=[row, ], message=ir['objs'][0].vat_message, total=total, rate=rate)
for mr in rows['mainrows']:
row = mr['line']
vat = mr['objs'][0].vat
rate = mr['objs'][0].vat_rate
total = (Decimal(rate / 100) * mr['rowtotal']).quantize(Decimal('.01'), rounding=ROUND_HALF_UP)
try:
vats[vat]['rownums'].append(row)
# Changed it to not add any value because that will produce wrong vat output.
vats[vat]['total'] += 0
except:
vats[vat] = dict(rownums=[row, ], message=mr['objs'][0].vat_message, total=total, rate=rate)
for cr in rows['citemrows']:
row = cr['line']
vat = cr['obj'].vat
rate = cr['obj'].vat_rate
total = (Decimal(rate / 100) * cr['rowtotal']).quantize(Decimal('.01'), rounding=ROUND_HALF_UP)
try:
vats[vat]['rownums'].append(row)
vats[vat]['total'] += total
except:
vats[vat] = [row, ]
vats[vat] = dict(rownums=[row, ], message=cr['obj'].vat_message, total=total, rate=rate)
vats = [(k, collapse_list(v['rownums']), v['message'], v['total'], v['rate']) for (k, v) in vats.items()]
vats = sorted(vats, key=lambda x: x[1])
# Gather data for the license agreement overview
# A list of lists, where the inner lists contain items that match on product and account
matched_item_sets = []
items = list(items) + list([m.serial for m in mains])
for item in items:
# Find a set to add this item to
for set_ in matched_item_sets:
if item.product == set_[0].product and item.account == set_[0].account:
if item not in set_:
set_.append(item)
break
else:
# No matching set found. Create a new one
matched_item_sets.append([item])
# Package the item sets nicely with meta information
summary = []
for set_ in matched_item_sets:
summary.append({
'account': set_[0].get_invoice_static(document).static_account_label,
'owner': set_[0].get_invoice_static(document).static_customer_label,
'agreement': set_[0].get_invoice_static(document).static_agreement_label,
'product': set_[0].product,
'licenses': collapse_list(set_),
})
data['inv'] = document
data['lines'] = rows['itemrows']
data['citems'] = rows['citemrows']
data['vats'] = vats
data['lagree'] = summary
data['includeSummary'] = include_summary
data['cilines'] = CustomInvoiceLine.objects.filter(invoice=document, on_invoice=True)
data['base_url'] = settings.SITE_BASE_URL
tpl = document.template
tpl = DjangoTemplate(tpl.content)
return tpl.render(Context(data))