Skip to content
This repository has been archived by the owner on Jun 24, 2024. It is now read-only.

Excel export with new api #843

Merged
merged 21 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
2823383
Merge branch 'feature/1123' into feature/pandas_w_new_api
AlexandreJunod Jun 19, 2023
f913655
adapt pandas with new api, and filter selected_forms per sheet
AlexandreJunod Jun 19, 2023
b15e913
Merge branch 'feature/1123' into feature/pandas_w_new_api
AlexandreJunod Jun 19, 2023
28b6e8c
Merge branch 'feature/1123' into feature/pandas_w_new_api
AlexandreJunod Jun 21, 2023
40171e4
fix export security
AlexandreJunod Jun 21, 2023
a144bf9
rename variables
AlexandreJunod Jun 21, 2023
b40ba9b
add parent excel export for validators and normal users
AlexandreJunod Jun 22, 2023
a7e8e6c
Merge branch 'develop' into feature/pandas_w_new_api
AlexandreJunod Jun 22, 2023
3cdc0cf
fix migration leaf nodes
AlexandreJunod Jun 22, 2023
9a5b813
fix tests
AlexandreJunod Jun 26, 2023
5361055
fix conflicts
monodo Jun 28, 2023
3b861f0
Delete geocity_export_2023-06-19.csv
AlexandreJunod Aug 14, 2023
6711ed7
Merge branch 'develop' into feature/pandas_w_new_api
AlexandreJunod Sep 25, 2023
35e05b4
fix export excel
AlexandreJunod Sep 27, 2023
ef3b9ff
Merge branch 'develop' into feature/pandas_w_new_api
AlexandreJunod Jan 5, 2024
444b4fe
add second button for export
AlexandreJunod Jan 8, 2024
0dd5ffa
update according to review
AlexandreJunod Jan 10, 2024
acd8c06
update comment
AlexandreJunod Jan 10, 2024
87b94af
fix tests
AlexandreJunod Jan 12, 2024
7aa3a2d
remove code that has been comited by mistake in wrong branch
AlexandreJunod Jan 15, 2024
c3d90e8
check if user is backoffice of entity for advanced export
AlexandreJunod Jan 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion geocity/apps/submissions/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class BaseSubmissionFilterSet(django_filters.FilterSet):
created_at = django_filters.DateFilter(
field_name="created_at",
lookup_expr="date",
label=_("Date de création le"),
label=_("Date de création"),
widget=DatePickerInput(
attrs={"placeholder": "ex: 15/02/2019"},
options={"format": "DD/MM/YYYY", "locale": "fr"},
Expand Down Expand Up @@ -131,6 +131,7 @@ class Meta:
"forms__category",
"forms",
"created_at",
"sent_date",
"starts_at_min",
"ends_at_max",
"author__last_name",
Expand Down
3 changes: 3 additions & 0 deletions geocity/apps/submissions/management/commands/fixturize.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from geocity import settings
from geocity.apps.accounts.models import *
from geocity.apps.accounts.users import get_integrator_permissions
from geocity.apps.api.services import convert_string_to_api_key
from geocity.apps.forms.models import *
from geocity.apps.reports.models import *
from geocity.apps.submissions.contact_type_choices import *
Expand Down Expand Up @@ -306,6 +307,7 @@ def create_form(

form_obj = Form.objects.create(
name=form,
api_name=convert_string_to_api_key(form),
category=form_category_obj,
is_public=True,
notify_services=True,
Expand Down Expand Up @@ -433,6 +435,7 @@ def create_field(self, field, integrator_group):
field, created = Field.objects.get_or_create(
integrator=integrator,
name=name,
api_name=convert_string_to_api_key(name),
placeholder=placeholder,
help_text=help_text,
input_type=input_type,
Expand Down
72 changes: 72 additions & 0 deletions geocity/apps/submissions/tables.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import collections
import json
from datetime import datetime
from io import BytesIO as IO

import django_tables2 as tables
import pandas
from django.conf import settings
from django.http import FileResponse
from django.template.defaultfilters import floatformat
from django.utils import timezone
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
from django_tables2.export.views import ExportMixin
from django_tables2_column_shifter.tables import ColumnShiftTable

from ..api.serializers import SubmissionPrintSerializer
from . import models
from .payments.models import Transaction

Expand Down Expand Up @@ -363,3 +371,67 @@ class Meta:
"status",
)
template_name = "django_tables2/bootstrap.html"


class PandasExportMixin(ExportMixin):
def create_export(self, export_format):

if not export_format in ["xlsx"]:
raise NotImplementedError

records = {}
submissions_qs = self.get_table_data().filter()

# # Make sure there will be no bypass
# submissions_list = submissions_qs.values_list("id", flat=True)
# visible_submissions_for_user = models.Submission.objects.filter_for_user(
# self.request.user,
# ).values_list("id", flat=True)

# if not all(item in visible_submissions_for_user for item in submissions_list):
# raise SuspiciousOperation
AlexandreJunod marked this conversation as resolved.
Show resolved Hide resolved

for submission in submissions_qs:
list_selected_forms = list(
submission.selected_forms.values_list("form_id", flat=True)
)
sheet_name = "_".join(map(str, list_selected_forms))
ordered_dict = SubmissionPrintSerializer(submission).data
ordered_dict.move_to_end("geometry")
data_dict = dict(ordered_dict)
data_str = json.dumps(data_dict)
record = json.loads(data_str, object_pairs_hook=collections.OrderedDict)

if sheet_name not in records.keys():
records[sheet_name] = record
else:
record_list = []
record_list.append(records[sheet_name])
record_list.append(record)
records[sheet_name] = record_list

now = timezone.now()

if export_format == "xlsx":
excel_file = IO()
xlwriter = pandas.ExcelWriter(excel_file)

for key in records:
dataframe = pandas.json_normalize(records[key])
dataframe.to_excel(xlwriter, sheet_name=key)

xlwriter.close()
excel_file.seek(0)
filename = f"geocity_export_{now:%Y-%m-%d}.xlsx"

content_type = "content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'"

response = FileResponse(
excel_file,
filename=filename,
as_attachment=False,
)
response["Content-Type"] = content_type
response["Content-Disposition"] = f'attachment; filename="{filename}"'

return response
4 changes: 3 additions & 1 deletion geocity/apps/submissions/templatetags/submissions_extras.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import json
import os.path
from datetime import datetime, timezone
from datetime import date, datetime, timezone

from django import template
from django.forms import modelformset_factory
Expand Down Expand Up @@ -122,6 +122,8 @@ def human_field_value(value):
return _("Oui") if value else _("Non")
elif isinstance(value, float):
return floatformat(value, arg=-2)
elif isinstance(value, date):
return value.strftime("%d.%m.%Y")
elif isinstance(value, list):
return render_to_string("submissions/_field_value_list.html", {"values": value})
elif not value:
Expand Down
6 changes: 3 additions & 3 deletions geocity/apps/submissions/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
from django.views.generic.edit import DeleteView
from django.views.generic.list import ListView
from django_filters.views import FilterView
from django_tables2.export.views import ExportMixin
from django_tables2.views import SingleTableMixin

from geocity.apps.accounts.decorators import (
Expand Down Expand Up @@ -73,6 +72,7 @@
)
from .tables import (
CustomFieldValueAccessibleSubmission,
PandasExportMixin,
TransactionsTable,
get_custom_dynamic_table,
)
Expand Down Expand Up @@ -1615,7 +1615,7 @@ def submission_media_download(request, property_value_id):
@method_decorator(login_required, name="dispatch")
@method_decorator(check_mandatory_2FA, name="dispatch")
@method_decorator(permanent_user_required, name="dispatch")
class SubmissionList(ExportMixin, SingleTableMixin, FilterView):
class SubmissionList(PandasExportMixin, SingleTableMixin, FilterView):
AlexandreJunod marked this conversation as resolved.
Show resolved Hide resolved
paginate_by = int(os.environ["PAGINATE_BY"])
template_name = "submissions/submissions_list.html"

Expand All @@ -1638,7 +1638,7 @@ def get_queryset(self):
)
)
.order_by(
F("sent_date").desc(nulls_last=True),
F("sent_date").desc(nulls_last=False),
F("created_at").desc(nulls_last=True),
)
)
Expand Down
2 changes: 2 additions & 0 deletions geocity_export_2023-06-19.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
;id;type;properties.additional_decision_information;properties.contacts.autres.contact_type_display;properties.contacts.autres.id;properties.contacts.autres.first_name;properties.contacts.autres.last_name;properties.contacts.autres.company_name;properties.contacts.autres.vat_number;properties.contacts.autres.address;properties.contacts.autres.zipcode;properties.contacts.autres.city;properties.contacts.autres.phone;properties.contacts.autres.email;properties.creditor_type;properties.status_display;properties.author.first_name;properties.author.last_name;properties.author.address;properties.author.zipcode;properties.author.user_id;properties.author.city;properties.author.company_name;properties.author.vat_number;properties.author.iban;properties.author.phone_first;properties.author.phone_second;properties.author.email;properties.geotime_aggregated.start_date;properties.geotime_aggregated.end_date;properties.geotime_aggregated.comments;properties.geotime_aggregated.external_links;properties.submission_id;properties.submission_status;properties.submission_shortname;properties.submission_administrative_entity.id;properties.submission_administrative_entity.name;properties.submission_administrative_entity.ofs_id;properties.submission_administrative_entity.link;properties.submission_administrative_entity.phone;properties.submission_forms;properties.submission_meta_types;properties.submission_forms_names.8;properties.submission_current_inquiry;properties.submission_price;properties.submission_sent_date;properties.submission_fields.permis_de_fouille.title.form;properties.submission_fields.permis_de_fouille.title.category;properties.submission_fields.permis_de_fouille.title.form_category;properties.submission_fields.permis_de_fouille.fields.largeur_m.name;properties.submission_fields.permis_de_fouille.fields.largeur_m.value;properties.submission_fields.permis_de_fouille.fields.hauteur_m.name;properties.submission_fields.permis_de_fouille.fields.hauteur_m.value;properties.submission_fields.permis_de_fouille.fields.commentaire.name;properties.submission_fields.permis_de_fouille.fields.commentaire.value;properties.submission_fields.permis_de_fouille.fields.adresse.name;properties.submission_fields.permis_de_fouille.fields.adresse.value;properties.submission_fields.permis_de_fouille.fields.adresse_avec_geocodage.name;properties.submission_fields.permis_de_fouille.fields.adresse_avec_geocodage.value;properties.submission_fields.permis_de_fouille.fields.impact_sur_la_chaussee.name;properties.submission_fields.permis_de_fouille.fields.impact_sur_la_chaussee.value;properties.submission_fields.permis_de_fouille.fields.a_moins_de_3m_dun_arbre.name;properties.submission_fields.permis_de_fouille.fields.a_moins_de_3m_dun_arbre.value;properties.amend_fields.permis_de_fouille.title.form;properties.amend_fields.permis_de_fouille.title.category;properties.amend_fields.permis_de_fouille.title.form_category;geometry.type;geometry.coordinates
0;281;Feature;;Autres;1;first_entity-pilot-0;Demo;;;Place Pestalozzi 2;1234;first_entity;012 345 67 89;[email protected];Auteur de la demande, first_entity-pilot-0 Demo;Envoyée, en attente de traitement;first_entity-pilot-0;Demo;Place Pestalozzi 2;1234;5;first_entity;;;;012 345 67 89;012 345 67 89;[email protected];24.06.2023 09:39;25.06.2023 09:39;;;281;1;;1;first_entity;5938;https://mapnv.ch;;[8];[0];Permis de fouille (Chantier);;;19.06.2023 09:40;Permis de fouille;Chantier;Permis de fouille (Chantier);Largeur [m];3.0;Hauteur [m];5.0;Commentaire;abcdefgh;Adresse;Allée Johann-Heinrich-Pestalozzi Johann-Heinrich-Pestalozzi-Allee 2, 2503 Biel/Bienne;Adresse avec géocodage;Allée Johann-Heinrich-Pestalozzi Johann-Heinrich-Pestalozzi-Allee 2, 2503 Biel/Bienne;Impact sur la chaussée;True;À moins de 3m d'un arbre;non;Permis de fouille;Chantier;Permis de fouille (Chantier);Polygon;[[[2538045.14, 1181481.84], [2538045.14, 1219706.25], [2586407.25, 1219706.25], [2586407.25, 1181481.84], [2538045.14, 1181481.84]]]