Skip to content

Commit 3266946

Browse files
authored
Merge pull request #97 from openagri-eu/reports
Reports inital implementation
2 parents 0a7ed08 + 659887a commit 3266946

File tree

6 files changed

+231
-1
lines changed

6 files changed

+231
-1
lines changed

farm_calendar/settings.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,18 @@ def default_crsf_from_allowed_host_format(host_list):
113113
}
114114

115115

116+
117+
118+
# REPORTING_API_ROOT = config('REPORTING_API_ROOT', default='http://localhost:8011/api/v1/')
119+
REPORTING_API_ROOT = config('REPORTING_API_ROOT')
120+
REPORTING_ENDPOINTS = {
121+
'irrigation': f'{REPORTING_API_ROOT}openagri-report/irrigation-report/',
122+
'compost': f'{REPORTING_API_ROOT}openagri-report/compost-report/',
123+
'livestock': f'{REPORTING_API_ROOT}openagri-report/animal-report/',
124+
'report_result_base': f'{REPORTING_API_ROOT}openagri-report/',
125+
}
126+
127+
116128
AUTHENTICATION_BACKENDS = (
117129
'farm_calendar.utils.auth_backends.CustomJWTAuthenticationBackend',
118130
)

farm_management/urls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
PesticideListView,
3737
PesticideUpdateView,
3838
AnimalGroupAutocomplete,
39+
PrepareReportView,
3940
)
4041

4142

@@ -72,4 +73,5 @@
7273

7374
path("pesticides/", PesticideListView.as_view(), name="pesticides"),
7475
path("pesticides/<uuid:pk>/", PesticideUpdateView.as_view(), name="pesticide_edit"),
76+
path("reports/<str:report_type>/", PrepareReportView.as_view(), name="prepare_report"),
7577
]

farm_management/views/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
from .farms import FarmView
44
from .provisory_ajax_handlers import AjaxHandlerView
55
from .farm_assets import *
6-
from .farm_materials import *
6+
from .farm_materials import *
7+
from .reports import *

farm_management/views/reports.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from django.conf import settings
2+
from django.utils.translation import gettext_lazy as _
3+
from django.urls import reverse_lazy
4+
from django.contrib.auth.mixins import LoginRequiredMixin
5+
from django.views.generic.base import TemplateView
6+
from farm_calendar.utils.jwt_utils import get_token_from_jwt_request
7+
8+
9+
10+
class PrepareReportView(TemplateView, LoginRequiredMixin):
11+
template_name = "farm_management/reports/prepare_report.html"
12+
13+
REPORT_TYPE = {
14+
'livestock': _('Livestock'),
15+
'irrigation': _('Irrigation'),
16+
'composting': _('Composting'),
17+
}
18+
19+
def get_context_data(self, **kwargs):
20+
context = super().get_context_data(**kwargs)
21+
readable_report_type = self.REPORT_TYPE[kwargs.get('report_type')]
22+
report_endpoint = settings.REPORTING_ENDPOINTS.get(kwargs.get('report_type'))
23+
report_result_base_endpoint = settings.REPORTING_ENDPOINTS.get('report_result_base')
24+
context['readable_report_type'] = readable_report_type
25+
context['report_endpoint'] = report_endpoint
26+
context['report_result_base_endpoint'] = report_result_base_endpoint
27+
token = get_token_from_jwt_request(self.request)
28+
context['access_token'] = token
29+
return context
30+
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
{% extends 'base.html' %}
2+
{% load static %}
3+
{% load crispy_forms_tags %}
4+
5+
{% block extra_css %}
6+
<style>
7+
.report-status {
8+
display: none;
9+
margin-top: 15px;
10+
}
11+
.progress {
12+
height: 20px;
13+
margin-bottom: 15px;
14+
}
15+
</style>
16+
{% endblock extra_css %}
17+
18+
19+
{% block page_content_title %}Prepare report for {{ readable_report_type }}{% endblock %}
20+
21+
{% block page_content_rows %}
22+
<!-- Page Content Rows with Card -->
23+
<div class="row">
24+
<div class="col-xl-8 col-lg-8">
25+
<div class="card shadow mb-4">
26+
27+
<!-- Card Header - Dropdown -->
28+
<div class="card-header py-3 d-flex flex-row align-items-center justify-content-between">
29+
<h6 class="m-0 font-weight-bold text-primary">{{ readable_report_type }} Report</h6>
30+
</div>
31+
32+
<!-- Card Body -->
33+
<div class="card-body">
34+
<form id="reportForm" method="post">
35+
<button type="submit" class="btn btn-success">Prepare Report</button>
36+
</form>
37+
38+
<div id="reportStatus" class="report-status">
39+
<div class="progress">
40+
<div id="progressBar" class="progress-bar progress-bar-striped progress-bar-animated"
41+
role="progressbar" style="width: 0%"></div>
42+
</div>
43+
<div id="statusMessage" class="alert alert-info">
44+
Preparing your report...
45+
</div>
46+
</div>
47+
</div>
48+
49+
50+
{% endblock %}
51+
52+
53+
54+
55+
{% block on_dom_load_js %}
56+
57+
let checkInterval;
58+
let reportUuid = null;
59+
const accessToken = '{{ access_token }}'; // From context
60+
61+
$('#reportForm').on('submit', function(e) {
62+
e.preventDefault();
63+
64+
$('#reportStatus').show();
65+
$('#progressBar').css('width', '10%');
66+
67+
// Disable submit button to prevent multiple requests
68+
$(this).find('button[type="submit"]').prop('disabled', true);
69+
70+
$.ajax({
71+
url: '{{ report_endpoint }}',
72+
type: 'POST',
73+
crossDomain: true,
74+
headers: {
75+
'Authorization': `Bearer ${accessToken}`
76+
},
77+
success: function(response) {
78+
reportUuid = response.uuid;
79+
$('#progressBar').css('width', '30%');
80+
$('#statusMessage').text('Report generation started. Waiting for completion...');
81+
82+
// Start checking for report completion every 3 seconds
83+
checkInterval = setInterval(checkReportStatus, 3000);
84+
},
85+
error: function(xhr) {
86+
alertify.error('Failed to start report generation: ' + (xhr.responseJSON?.detail || xhr.statusText));
87+
$('#reportForm button[type="submit"]').prop('disabled', false);
88+
$('#reportStatus').hide();
89+
}
90+
});
91+
});
92+
93+
function checkReportStatus() {
94+
if (!reportUuid) return;
95+
96+
// Construct the download URL
97+
const downloadUrl = '{{ report_result_base_endpoint }}' + reportUuid;
98+
99+
// Try to download the file
100+
$.ajax({
101+
url: downloadUrl,
102+
type: 'GET',
103+
crossDomain: true,
104+
headers: {
105+
'Authorization': `Bearer ${accessToken}`
106+
},
107+
xhrFields: {
108+
responseType: 'blob' // Important for file download
109+
},
110+
success: function(data, status, xhr) {
111+
const contentType = xhr.getResponseHeader('content-type');
112+
if (contentType === 'application/pdf' || contentType === 'application/octet-stream') {
113+
clearInterval(checkInterval);
114+
$('#progressBar').css('width', '100%');
115+
$('#statusMessage').text('Report ready! Downloading...');
116+
117+
// Create a download link and trigger click
118+
const blob = new Blob([data], {type: contentType});
119+
const link = document.createElement('a');
120+
link.href = window.URL.createObjectURL(blob);
121+
link.download = '{{ report_type }}_report_' + reportUuid + '.pdf';
122+
document.body.appendChild(link);
123+
link.click();
124+
document.body.removeChild(link);
125+
126+
// Reset form after a short delay
127+
setTimeout(function() {
128+
$('#reportStatus').hide();
129+
$('#reportForm button[type="submit"]').prop('disabled', false);
130+
$('#progressBar').css('width', '0%');
131+
reportUuid = null;
132+
}, 2000);
133+
} else {
134+
// Increment progress bar (but don't go over 90% until download)
135+
const currentWidth = parseInt($('#progressBar').css('width'));
136+
if (currentWidth < 90) {
137+
$('#progressBar').css('width', (currentWidth + 5) + '%');
138+
}
139+
}
140+
},
141+
error: function(xhr) {
142+
if (xhr.status === 404) {
143+
// Report not ready yet - this is expected
144+
const currentWidth = parseInt($('#progressBar').css('width'));
145+
if (currentWidth < 90) {
146+
$('#progressBar').css('width', (currentWidth + 2) + '%');
147+
}
148+
} else {
149+
// Other error - stop checking
150+
clearInterval(checkInterval);
151+
alertify.error('Error checking report status: ' + (xhr.responseJSON?.detail || xhr.statusText));
152+
$('#reportForm button[type="submit"]').prop('disabled', false);
153+
}
154+
}
155+
});
156+
}
157+
{% endblock on_dom_load_js %}
158+
159+
{% block body_end_extra_scripts %}
160+
<!-- alertifyjs js -->
161+
<script src="{% static 'libs/alertifyjs/build/alertify.min.js' %}"></script>
162+
{% endblock body_end_extra_scripts %}

templates/sidebar.html

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,29 @@
2121
</a>
2222
</li>
2323

24+
<!-- Divider -->
25+
<hr class="sidebar-divider">
26+
<!-- Heading -->
27+
<div class="sidebar-heading">
28+
Overview
29+
</div>
30+
31+
32+
<li class="nav-item{% if active_page == 'report' %} active{% endif %}">
33+
<a class="nav-link collapsed" href="#" data-toggle="collapse" data-target="#collapseReports"
34+
aria-expanded="true" aria-controls="collapseReports">
35+
<i class="fas fa-fw fa-file"></i>
36+
<span>Prepare Report</span>
37+
</a>
38+
<div id="collapseReports" class="collapse" aria-labelledby="headingTwo" data-parent="#accordionSidebar">
39+
<div class="bg-white py-2 collapse-inner rounded">
40+
<a class="collapse-item" href="{% url 'prepare_report' report_type='livestock' %}">Livestock</a>
41+
<a class="collapse-item" href="{% url 'prepare_report' report_type='irrigation' %}">Irrigation</a>
42+
<a class="collapse-item" href="{% url 'prepare_report' report_type='composting' %}">Composting</a>
43+
</div>
44+
</div>
45+
</li>
46+
2447
<!-- Divider -->
2548
<hr class="sidebar-divider">
2649
<!-- Heading -->

0 commit comments

Comments
 (0)