We've been implementing KeinaSystem, an agricultural management system with Django/Next.js and Docker:
Completed:
- Day 1-2: Docker setup, Django configuration (REST, JWT, CORS, PostGIS, Japan timezone)
- Day 3: Data models (OfficialKyosaiField, OfficialChusankanField, Field with ManyToMany, Crop, Variety, Plan)
- Day 4: Import API endpoints for ODS files
- Day 5: init_crops command, serializers, ViewSets, summary/copy APIs
Current Issue:
- Day 6: PDF generation (/api/reports/kyosai/2025/) is returning HTTP 500 error
Next Step:
Debug the PDF generation error by checking backend container logs to see the specific exception.
Want me to check the container logs to diagnose the PDF 500 error?
This commit is contained in:
Akira
2026-02-15 12:53:05 +09:00
parent 543de30b1c
commit 30aca109d7
6 changed files with 304 additions and 1 deletions

View File

@@ -10,6 +10,12 @@ RUN apt-get update && apt-get install -y \
libgdal-dev \
libgeos-dev \
libproj-dev \
fonts-noto-cjk \
libgirepository1.0-dev \
libcairo2 \
libpango-1.0-0 \
libpangocairo-1.0-0 \
gir1.2-pango-1.0 \
&& rm -rf /var/lib/apt/lists/*
ENV CPLUS_INCLUDE_PATH=/usr/include/gdal

View File

@@ -0,0 +1,108 @@
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>中山間地域直接支払申请书</title>
<style>
body {
font-family: "Noto Sans CJK JP", "Hiragino Kaku Gothic ProN", sans-serif;
font-size: 10pt;
line-height: 1.5;
}
h1 {
text-align: center;
font-size: 14pt;
margin-bottom: 20pt;
}
h2 {
font-size: 12pt;
border-bottom: 1px solid #333;
margin-top: 15pt;
}
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 10pt;
}
th, td {
border: 1px solid #333;
padding: 5pt;
text-align: left;
vertical-align: top;
}
th {
background-color: #f0f0f0;
}
.info-row {
display: flex;
margin-bottom: 5pt;
}
.info-label {
font-weight: bold;
width: 100pt;
}
.crop-table th {
width: 30%;
}
.crop-table td {
width: 70%;
}
</style>
</head>
<body>
<h1>中山間地域直接支払申请书 - {{ year }}年度</h1>
{% for item in data %}
<h2>{{ item.chusankan.c_id }} - {{ item.chusankan.oaza }}{{ item.chusankan.aza }}</h2>
<div class="info-row">
<span class="info-label">大字:</span>
<span>{{ item.chusankan.oaza }}</span>
</div>
<div class="info-row">
<span class="info-label">字:</span>
<span>{{ item.chusankan.aza }}</span>
</div>
<div class="info-row">
<span class="info-label">地番:</span>
<span>{{ item.chusankan.chiban }}</span>
</div>
<div class="info-row">
<span class="info-label">面積:</span>
<span>{{ item.chusankan.area }} ha</span>
</div>
<div class="info-row">
<span class="info-label">支払金額:</span>
<span>{{ item.chusankan.payment_amount|default:"-" }} 円</span>
</div>
<div class="info-row">
<span class="info-label">関連圃場数:</span>
<span>{{ item.field_count }}</span>
</div>
<div class="info-row">
<span class="info-label">作付面積合計:</span>
<span>{{ item.total_area|floatformat:4 }} 反</span>
</div>
{% if item.crops %}
<table class="crop-table">
<thead>
<tr>
<th>作物</th>
<th>品種</th>
</tr>
</thead>
<tbody>
{% for crop in item.crops %}
<tr>
<td>{{ crop.name }}</td>
<td>{{ crop.variety }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% endfor %}
</body>
</html>

View File

@@ -0,0 +1,96 @@
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>水稲共済申请书</title>
<style>
body {
font-family: "Noto Sans CJK JP", "Hiragino Kaku Gothic ProN", sans-serif;
font-size: 10pt;
line-height: 1.5;
}
h1 {
text-align: center;
font-size: 14pt;
margin-bottom: 20pt;
}
h2 {
font-size: 12pt;
border-bottom: 1px solid #333;
margin-top: 15pt;
}
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 10pt;
}
th, td {
border: 1px solid #333;
padding: 5pt;
text-align: left;
vertical-align: top;
}
th {
background-color: #f0f0f0;
}
.info-row {
display: flex;
margin-bottom: 5pt;
}
.info-label {
font-weight: bold;
width: 100pt;
}
.crop-table th {
width: 30%;
}
.crop-table td {
width: 70%;
}
</style>
</head>
<body>
<h1>水稲共済申请书 - {{ year }}年度</h1>
{% for item in data %}
<h2>{{ item.kyosai.k_num }} - {{ item.kyosai.kanji_name }}</h2>
<div class="info-row">
<span class="info-label">住所:</span>
<span>{{ item.kyosai.address }}</span>
</div>
<div class="info-row">
<span class="info-label">面積:</span>
<span>{{ item.kyosai.area }} ha</span>
</div>
<div class="info-row">
<span class="info-label">関連圃場数:</span>
<span>{{ item.field_count }}</span>
</div>
<div class="info-row">
<span class="info-label">作付面積合計:</span>
<span>{{ item.total_area|floatformat:4 }} 反</span>
</div>
{% if item.crops %}
<table class="crop-table">
<thead>
<tr>
<th>作物</th>
<th>品種</th>
</tr>
</thead>
<tbody>
{% for crop in item.crops %}
<tr>
<td>{{ crop.name }}</td>
<td>{{ crop.variety }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% endfor %}
</body>
</html>

View File

@@ -0,0 +1,7 @@
from django.urls import path
from . import views
urlpatterns = [
path('kyosai/<int:year>/', views.generate_kyosai_pdf, name='kyosai_pdf'),
path('chusankan/<int:year>/', views.generate_chusankan_pdf, name='chusankan_pdf'),
]

View File

@@ -1,3 +1,88 @@
from django.shortcuts import render
from django.template.loader import render_to_string
from django.http import HttpResponse
from weasyprint import HTML
from apps.fields.models import OfficialKyosaiField, OfficialChusankanField, Field
from apps.plans.models import Plan
# Create your views here.
def generate_kyosai_pdf(request, year):
kyosai_fields = OfficialKyosaiField.objects.all()
data = []
for kyosai in kyosai_fields:
related_fields = kyosai.fields.all()
plans = Plan.objects.filter(field__in=related_fields, year=year)
crops = {}
total_area = 0
for plan in plans:
crop_name = plan.crop.name
if crop_name not in crops:
crops[crop_name] = {
'name': crop_name,
'variety': plan.variety.name,
'count': 0
}
crops[crop_name]['count'] += 1
total_area += float(plan.field.area_tan)
data.append({
'kyosai': kyosai,
'fields': related_fields,
'crops': list(crops.values()),
'total_area': total_area,
'field_count': related_fields.count()
})
html_string = render_to_string('reports/kyosai_template.html', {
'year': year,
'data': data
})
pdf = HTML(string=html_string).write_pdf()
response = HttpResponse(pdf, content_type='application/pdf')
response['Content-Disposition'] = f'attachment; filename="kyosai_{year}.pdf"'
return response
def generate_chusankan_pdf(request, year):
chusankan_fields = OfficialChusankanField.objects.all()
data = []
for chusankan in chusankan_fields:
related_fields = chusankan.fields.all()
plans = Plan.objects.filter(field__in=related_fields, year=year)
crops = {}
total_area = 0
for plan in plans:
crop_name = plan.crop.name
if crop_name not in crops:
crops[crop_name] = {
'name': crop_name,
'variety': plan.variety.name,
'count': 0
}
crops[crop_name]['count'] += 1
total_area += float(plan.field.area_tan)
data.append({
'chusankan': chusankan,
'fields': related_fields,
'crops': list(crops.values()),
'total_area': total_area,
'field_count': related_fields.count()
})
html_string = render_to_string('reports/chusankan_template.html', {
'year': year,
'data': data
})
pdf = HTML(string=html_string).write_pdf()
response = HttpResponse(pdf, content_type='application/pdf')
response['Content-Disposition'] = f'attachment; filename="chusankan_{year}.pdf"'
return response

View File

@@ -21,4 +21,5 @@ urlpatterns = [
path('admin/', admin.site.urls),
path('api/fields/', include('apps.fields.urls')),
path('api/plans/', include('apps.plans.urls')),
path('api/reports/', include('apps.reports.urls')),
]