Media was unreachable from either portal, which is why the certificate button on the buyer compliance page returned page-not-found and no document row had anything to click. The cause was structural: the portal and ops hosts proxy location / to the app with a path prefix, so a stored /media/... URL arrives as /portal/media/... and matches no route. Add a download view per portal behind the same role gate as the page that links to it — buyer-visible documents and published certificates for buyers, anything the ops list shows for operations — resolving the stored path safely under MEDIA_ROOT so a traversal or an absolute path is refused. Point the seven templates that list files at the new routes. Also add a portal-wide render sweep: nothing in the suite was fetching the ops or portal pages, so a view could lose its template_name and 500 in front of a user with every test still green.
62 lines
2.7 KiB
HTML
62 lines
2.7 KiB
HTML
{% extends 'portal/base_portal.html' %}
|
|
{% block title %}Compliance - IXG Buyer Portal{% endblock %}
|
|
{% block content %}
|
|
|
|
<div class="page-header d-flex justify-content-between align-items-center">
|
|
<h2>Compliance</h2>
|
|
<span class="text-secondary">Approved evidence for your batches</span>
|
|
</div>
|
|
|
|
<div class="alert alert-light border py-2 small">
|
|
<i class="bi bi-info-circle"></i> Shea butter is not listed in EUDR Annex I; the due-diligence status shown here is
|
|
evidence and buyer assurance provided by IXG, not an EU-mandated filing.
|
|
</div>
|
|
|
|
<div class="row g-3">
|
|
<div class="col-md-7">
|
|
<div class="stat-card">
|
|
<h6 class="mb-3"><i class="bi bi-file-earmark-medical"></i> Certificates of Analysis (approved)</h6>
|
|
<table class="table table-sm mb-0">
|
|
<thead><tr><th>Batch</th><th>Product</th><th>Issued</th><th>Download</th></tr></thead>
|
|
<tbody>
|
|
{% for c in approved_coas %}
|
|
<tr>
|
|
<td><strong>{{ c.batch.batch_reference }}</strong></td>
|
|
<td>Unrefined Shea Butter</td>
|
|
<td>{{ c.issue_date|date:'d/m/Y'|default:'-' }}</td>
|
|
<td><a href="{% url 'portal:coa_download' c.id %}" target="_blank" class="btn btn-sm btn-outline-success"><i class="bi bi-download"></i> PDF</a></td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr><td colspan="4" class="text-muted text-center py-3">No approved certificates available</td></tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-5">
|
|
<div class="stat-card">
|
|
<h6 class="mb-3"><i class="bi bi-shield-check"></i> EUDR Due Diligence Status</h6>
|
|
<table class="table table-sm mb-0">
|
|
<thead><tr><th>Batch</th><th>Status</th><th>Risk</th></tr></thead>
|
|
<tbody>
|
|
{% for c in eudr_cases %}
|
|
<tr>
|
|
<td><strong>{{ c.batch.batch_reference }}</strong></td>
|
|
<td><span class="badge
|
|
{% if c.status == 'approved' %}bg-success
|
|
{% elif c.status == 'rejected' %}bg-danger
|
|
{% elif c.status == 'complete_for_review' %}bg-primary
|
|
{% elif c.status == 'mitigation_required' or c.status == 'risk_identified' %}bg-warning text-dark
|
|
{% else %}bg-secondary{% endif %}">{{ c.get_status_display }}</span></td>
|
|
<td><span class="badge {% if c.risk_level == 'high' %}bg-danger{% elif c.risk_level == 'low' %}bg-success{% else %}bg-info text-dark{% endif %}">{{ c.get_risk_level_display }}</span></td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr><td colspan="3" class="text-muted text-center py-3">No due-diligence cases recorded</td></tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% endblock %}
|