from django.contrib.contenttypes.models import ContentType from django.http import HttpResponse, HttpResponseBadRequest from django.shortcuts import get_object_or_404, render from django.views.decorators.http import require_GET, require_POST from .models import CustomAttribute, EntityFieldConfig, Freigabe ENTITY_TYPES = None # lazy-loaded to avoid circular imports def _entity_types(): global ENTITY_TYPES if ENTITY_TYPES is None: from vergabe_teilnahme.apps.aufgaben.models import Aufgabe from vergabe_teilnahme.apps.ausschreibungen.models import Ausschreibung from vergabe_teilnahme.apps.bibliothek.models import Nachweis, Referenz from vergabe_teilnahme.apps.lose.models import Anforderung, Los from vergabe_teilnahme.apps.partner.models import Subunternehmer ENTITY_TYPES = { 'ausschreibung': Ausschreibung, 'los': Los, 'anforderung': Anforderung, 'aufgabe': Aufgabe, 'subunternehmer': Subunternehmer, 'nachweis': Nachweis, 'referenz': Referenz, } return ENTITY_TYPES def custom_404(request, exception=None): return render(request, 'errors/404.html', status=404) def custom_500(request): return render(request, 'errors/500.html', status=500) # ── Freigaben ───────────────────────────────────────────────────────────────── @require_GET def freigabe_modal(request): ctx = { 'content_type_id': request.GET.get('ct', ''), 'object_id': request.GET.get('oid', ''), 'freigabe_typ': request.GET.get('typ', ''), 'freigabe_typ_choices': Freigabe.TYP_CHOICES, } return render(request, 'partials/freigabe_modal.html', ctx) @require_POST def freigabe_erteilen(request): ct_id = request.POST.get('content_type_id') object_id = request.POST.get('object_id') freigabe_typ = request.POST.get('freigabe_typ') if not (ct_id and object_id and freigabe_typ): return HttpResponseBadRequest() ct = get_object_or_404(ContentType, pk=ct_id) Freigabe.objects.create( content_type=ct, object_id=object_id, freigabe_typ=freigabe_typ, freigebende_person=request.user, status='erteilt', kommentar=request.POST.get('kommentar', ''), ) return render(request, 'partials/freigabe_success.html', {}) # ── EntityFieldConfig ───────────────────────────────────────────────────────── def feld_konfiguration_liste(request, entity_type): types = _entity_types() if entity_type not in types: from django.http import Http404 raise Http404 model = types[entity_type] raw_felder = [f for f in model._meta.get_fields() if hasattr(f, 'column') and not f.name.startswith('_')] konfigurationen = { cfg.field_name: cfg for cfg in EntityFieldConfig.objects.filter(entity_type=entity_type) } felder = [ {'feld': f, 'cfg': konfigurationen.get(f.name)} for f in raw_felder ] return render(request, 'core/feld_konfiguration.html', { 'entity_type': entity_type, 'entity_types': list(_entity_types().keys()), 'felder': felder, }) @require_POST def feld_konfiguration_toggle(request, entity_type, field_name): cfg, _ = EntityFieldConfig.objects.get_or_create( entity_type=entity_type, field_name=field_name ) cfg.is_hidden = request.POST.get('is_hidden') == 'true' cfg.display_label = request.POST.get('display_label', '') cfg.save() return render(request, 'core/partials/feld_zeile.html', { 'cfg': cfg, 'field_name': field_name, 'entity_type': entity_type, }) # ── CustomAttributes ────────────────────────────────────────────────────────── def custom_attributes_panel(request, content_type_id, object_id): ct = get_object_or_404(ContentType, pk=content_type_id) attrs = CustomAttribute.objects.filter(content_type=ct, object_id=object_id) return render(request, 'core/partials/custom_attributes.html', { 'attrs': attrs, 'ct_id': content_type_id, 'oid': object_id, }) @require_POST def custom_attribute_neu(request, content_type_id, object_id): from django.utils.text import slugify ct = get_object_or_404(ContentType, pk=content_type_id) label = request.POST.get('label', '').strip() if label: CustomAttribute.objects.create( content_type=ct, object_id=object_id, key=slugify(label), label=label, value=request.POST.get('value', ''), data_type=request.POST.get('data_type', 'text'), ) attrs = CustomAttribute.objects.filter(content_type=ct, object_id=object_id) return render(request, 'core/partials/custom_attributes.html', { 'attrs': attrs, 'ct_id': content_type_id, 'oid': object_id, }) @require_POST def custom_attribute_bearbeiten(request, content_type_id, object_id, attr_pk): attr = get_object_or_404(CustomAttribute, pk=attr_pk, object_id=object_id) attr.label = request.POST.get('label', attr.label).strip() or attr.label attr.value = request.POST.get('value', attr.value) attr.data_type = request.POST.get('data_type', attr.data_type) attr.save() ct = ContentType.objects.get(pk=content_type_id) attrs = CustomAttribute.objects.filter(content_type=ct, object_id=object_id) return render(request, 'core/partials/custom_attributes.html', { 'attrs': attrs, 'ct_id': content_type_id, 'oid': object_id, }) @require_POST def custom_attribute_loeschen(request, content_type_id, object_id, attr_pk): attr = get_object_or_404(CustomAttribute, pk=attr_pk, object_id=object_id) attr.delete() ct = ContentType.objects.get(pk=content_type_id) attrs = CustomAttribute.objects.filter(content_type=ct, object_id=object_id) return render(request, 'core/partials/custom_attributes.html', { 'attrs': attrs, 'ct_id': content_type_id, 'oid': object_id, }) @require_POST def custom_attribute_sort(request, content_type_id, object_id, attr_pk): direction = request.POST.get('direction', 'down') attr = get_object_or_404(CustomAttribute, pk=attr_pk, object_id=object_id) ct = ContentType.objects.get(pk=content_type_id) attrs = list(CustomAttribute.objects.filter(content_type=ct, object_id=object_id)) idx = next((i for i, a in enumerate(attrs) if a.pk == attr.pk), None) if idx is not None: swap = idx - 1 if direction == 'up' else idx + 1 if 0 <= swap < len(attrs): old, new = attrs[swap].sort_order, attrs[idx].sort_order attrs[idx].sort_order, attrs[swap].sort_order = old, new attrs[idx].save(update_fields=['sort_order']) attrs[swap].save(update_fields=['sort_order']) attrs = CustomAttribute.objects.filter(content_type=ct, object_id=object_id) return render(request, 'core/partials/custom_attributes.html', { 'attrs': attrs, 'ct_id': content_type_id, 'oid': object_id, }) # ── Globale Suche ───────────────────────────────────────────────────────────── @require_GET def suche(request): q = request.GET.get('q', '').strip() if not q or len(q) < 2: return HttpResponse('') from vergabe_teilnahme.apps.ausschreibungen.models import Ausschreibung from vergabe_teilnahme.apps.bibliothek.models import Nachweis, Referenz from vergabe_teilnahme.apps.dokumente.models import Dokument from vergabe_teilnahme.apps.marktbegleiter.models import Marktbegleiter ausschreibungen = Ausschreibung.objects.filter(titel__icontains=q)[:5] dokumente = Dokument.objects.filter(dateiname__icontains=q).select_related('ausschreibung')[:5] nachweise = Nachweis.objects.filter(titel__icontains=q)[:5] referenzen = Referenz.objects.filter(referenztitel__icontains=q)[:5] marktbegleiter = Marktbegleiter.objects.filter(name__icontains=q)[:5] has_results = any([ausschreibungen, dokumente, nachweise, referenzen, marktbegleiter]) if not has_results: return render(request, 'partials/search_results.html', {'q': q, 'empty': True}) return render(request, 'partials/search_results.html', { 'q': q, 'ausschreibungen': ausschreibungen, 'dokumente': dokumente, 'nachweise': nachweise, 'referenzen': referenzen, 'marktbegleiter': marktbegleiter, })