fix: Import mit fehlendem Mandanten laut abbrechen statt still verwerfen
Beim ZIP-Import wurden Tenant/TenantGroup bisher per Slug per .filter().first() aufgelöst. Existierte der referenzierte Mandant auf der Zielinstanz nicht, wurde das Feld stillschweigend auf None gesetzt. Das führte bei Instanzen mit Mandanten-Pflichtfeld-Validierung zu einer generischen ValidationError beim document.save(), ohne Hinweis darauf, welches Dokument oder welcher Mandant betroffen war. _resolve_tenant()/_resolve_tenant_group() werfen nun eine ArchiveFailure mit Dokument-/Ordnertitel und fehlendem Slug, sobald ein referenzierter Mandant nicht gefunden wird.
This commit is contained in:
@@ -123,6 +123,48 @@ def _read_json(archive, name):
|
|||||||
raise ArchiveFailure("Das Archiv enthält kein gültiges manifest.json.") from exc
|
raise ArchiveFailure("Das Archiv enthält kein gültiges manifest.json.") from exc
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_tenant_group(slug, context):
|
||||||
|
"""Look up a tenant group by slug for an imported record.
|
||||||
|
|
||||||
|
Returns None if no slug was recorded (i.e. the object genuinely had no
|
||||||
|
tenant group). Raises ArchiveFailure if a slug *was* recorded but no
|
||||||
|
matching TenantGroup exists on this instance, instead of silently
|
||||||
|
dropping the assignment - a missing tenant group later tripping over
|
||||||
|
an unrelated validation rule further downstream is far more confusing
|
||||||
|
than failing here with a precise message.
|
||||||
|
"""
|
||||||
|
if not slug:
|
||||||
|
return None
|
||||||
|
group = TenantGroup.objects.filter(slug=slug).first()
|
||||||
|
if group is None:
|
||||||
|
raise ArchiveFailure(
|
||||||
|
f"{context}: Die Mandantengruppe „{slug}“ existiert auf dieser Instanz nicht. "
|
||||||
|
"Bitte die Mandantengruppe vorher anlegen oder den Import mit einem angepassten "
|
||||||
|
"manifest.json wiederholen."
|
||||||
|
)
|
||||||
|
return group
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_tenant(slug, context):
|
||||||
|
"""Look up a tenant by slug for an imported record.
|
||||||
|
|
||||||
|
Same reasoning as _resolve_tenant_group(): fail loudly and specifically
|
||||||
|
instead of leaving the field empty and letting a generic "a tenant is
|
||||||
|
required" validation error surface later with no indication of which
|
||||||
|
tenant or which record was actually the problem.
|
||||||
|
"""
|
||||||
|
if not slug:
|
||||||
|
return None
|
||||||
|
tenant = Tenant.objects.filter(slug=slug).first()
|
||||||
|
if tenant is None:
|
||||||
|
raise ArchiveFailure(
|
||||||
|
f"{context}: Der Mandant „{slug}“ existiert auf dieser Instanz nicht. "
|
||||||
|
"Bitte den Mandanten vorher anlegen oder den Import mit einem angepassten "
|
||||||
|
"manifest.json wiederholen."
|
||||||
|
)
|
||||||
|
return tenant
|
||||||
|
|
||||||
|
|
||||||
def _category_from_path(names, tenancy_path=None):
|
def _category_from_path(names, tenancy_path=None):
|
||||||
parent = None
|
parent = None
|
||||||
tenancy_path = tenancy_path or []
|
tenancy_path = tenancy_path or []
|
||||||
@@ -136,8 +178,9 @@ def _category_from_path(names, tenancy_path=None):
|
|||||||
category = DocumentCategory.objects.create(name=name, slug=slug, parent=parent)
|
category = DocumentCategory.objects.create(name=name, slug=slug, parent=parent)
|
||||||
tenancy = tenancy_path[index] if index < len(tenancy_path) and isinstance(tenancy_path[index], dict) else {}
|
tenancy = tenancy_path[index] if index < len(tenancy_path) and isinstance(tenancy_path[index], dict) else {}
|
||||||
if tenancy:
|
if tenancy:
|
||||||
category.tenant_group = TenantGroup.objects.filter(slug=tenancy.get("tenant_group")).first()
|
context = f"Ordner „{name}“"
|
||||||
category.tenant = Tenant.objects.filter(slug=tenancy.get("tenant")).first()
|
category.tenant_group = _resolve_tenant_group(tenancy.get("tenant_group"), context)
|
||||||
|
category.tenant = _resolve_tenant(tenancy.get("tenant"), context)
|
||||||
category.save(update_fields=("tenant_group", "tenant", "last_updated"))
|
category.save(update_fields=("tenant_group", "tenant", "last_updated"))
|
||||||
parent = category
|
parent = category
|
||||||
return parent
|
return parent
|
||||||
@@ -191,8 +234,9 @@ def import_archive(upload, update_existing=False):
|
|||||||
document.body_format = record.get("body_format") if record.get("body_format") in {"html", "markdown"} else "html"
|
document.body_format = record.get("body_format") if record.get("body_format") in {"html", "markdown"} else "html"
|
||||||
document.is_published = bool(record.get("is_published", True))
|
document.is_published = bool(record.get("is_published", True))
|
||||||
document.category = category
|
document.category = category
|
||||||
document.tenant_group = TenantGroup.objects.filter(slug=record.get("tenant_group")).first()
|
context = f"Dokument „{document.title}“"
|
||||||
document.tenant = Tenant.objects.filter(slug=record.get("tenant")).first()
|
document.tenant_group = _resolve_tenant_group(record.get("tenant_group"), context)
|
||||||
|
document.tenant = _resolve_tenant(record.get("tenant"), context)
|
||||||
document.save()
|
document.save()
|
||||||
for assignment in record.get("assignments") or []:
|
for assignment in record.get("assignments") or []:
|
||||||
label = f"{assignment.get('app_label')}.{assignment.get('model')}"
|
label = f"{assignment.get('app_label')}.{assignment.get('model')}"
|
||||||
@@ -236,4 +280,4 @@ def import_archive(upload, update_existing=False):
|
|||||||
if item.get("source_url"):
|
if item.get("source_url"):
|
||||||
document.body = document.body.replace(str(item["source_url"]), attachment.file.url)
|
document.body = document.body.replace(str(item["source_url"]), attachment.file.url)
|
||||||
document.save(update_fields=("body", "last_updated"))
|
document.save(update_fields=("body", "last_updated"))
|
||||||
return {"created": created, "updated": updated, "skipped_assignments": skipped_assignments}
|
return {"created": created, "updated": updated, "skipped_assignments": skipped_assignments}
|
||||||
Reference in New Issue
Block a user