-
Notifications
You must be signed in to change notification settings - Fork 10
feat: add reusable CSV/XLS export utility and filtered download routes to journal page classes #1417
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: add reusable CSV/XLS export utility and filtered download routes to journal page classes #1417
Changes from all commits
554c1b8
8a96741
1c4933d
05eed2e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,11 @@ | |
| from wagtailcaptcha.models import WagtailCaptchaEmailForm | ||
|
|
||
| from collection.models import Collection | ||
| from core.home.utils.export_journals import ( | ||
| generate_csv_response, | ||
| generate_xls_response, | ||
| get_scielo_journals_data, | ||
| ) | ||
| from core.home.utils.get_social_networks import get_social_networks | ||
| from journal.choices import STUDY_AREA | ||
| from journal.models import OwnerHistory, SciELOJournal | ||
|
|
@@ -79,6 +84,23 @@ def _default_context(context): | |
| context["page_about"] = get_page_about() | ||
|
|
||
|
|
||
| class JournalDownloadMixin: | ||
| def get_export_filters(self, request): | ||
| return None | ||
|
|
||
| @re_path(r"^download-csv/$", name="download_csv") | ||
| def download_csv(self, request): | ||
| filters = self.get_export_filters(request) | ||
| journals_data = get_scielo_journals_data(filters) | ||
| return generate_csv_response(journals_data) | ||
|
|
||
| @re_path(r"^download-xls/$", name="download_xls") | ||
| def download_xls(self, request): | ||
| filters = self.get_export_filters(request) | ||
| journals_data = get_scielo_journals_data(filters) | ||
| return generate_xls_response(journals_data) | ||
|
|
||
|
|
||
| def get_page_about(): | ||
| try: | ||
| locale = _get_current_locale() | ||
|
|
@@ -221,7 +243,15 @@ def get_context(self, request, *args, **kwargs): | |
| return context | ||
|
|
||
|
|
||
| class ListPageJournal(Page): | ||
| class ListPageJournal(JournalDownloadMixin, RoutablePageMixin, Page): | ||
| def get_export_filters(self, request): | ||
| search_term = request.GET.get("search_term", "") | ||
| starts_with_letter = request.GET.get("start_with_letter", "") | ||
| active_or_discontinued = list(request.GET.get("tab", "")) | ||
| return default_journal_filter( | ||
| search_term, starts_with_letter, active_or_discontinued | ||
| ) | ||
|
Comment on lines
+247
to
+253
|
||
|
|
||
| def get_context(self, request, *args, **kwargs): | ||
| context = super().get_context(request, *args, **kwargs) | ||
| search_term = request.GET.get("search_term", "") | ||
|
|
@@ -237,7 +267,22 @@ def get_context(self, request, *args, **kwargs): | |
| return context | ||
|
|
||
|
|
||
| class ListPageJournalByPublisher(Page): | ||
| class ListPageJournalByPublisher(JournalDownloadMixin, RoutablePageMixin, Page): | ||
| def get_export_filters(self, request): | ||
| search_term = request.GET.get("search_term", "") | ||
| starts_with_letter = request.GET.get("start_with_letter", "") | ||
| active_or_discontinued = list(request.GET.get("tab", "")) | ||
| filters = Q(status__in=SCIELO_STATUS_CHOICES) | ||
| if search_term: | ||
| filters &= Q(journal__title__icontains=search_term) | Q( | ||
| journal__owner_history__institution__institution__institution_identification__name__icontains=search_term | ||
| ) | ||
| if starts_with_letter: | ||
| filters &= Q(journal__title__istartswith=starts_with_letter) | ||
| if active_or_discontinued: | ||
| filters &= Q(status__in=active_or_discontinued) | ||
| return filters | ||
|
Comment on lines
+271
to
+284
|
||
|
|
||
| def get_context(self, request, *args, **kwargs): | ||
| context = super().get_context(request, *args, **kwargs) | ||
| search_term = request.GET.get("search_term", "") | ||
|
|
@@ -283,7 +328,15 @@ def get_context(self, request, *args, **kwargs): | |
| return context | ||
|
|
||
|
|
||
| class ListPageJournalByCategory(RoutablePageMixin, Page): | ||
| class ListPageJournalByCategory(JournalDownloadMixin, RoutablePageMixin, Page): | ||
| def get_export_filters(self, request): | ||
| search_term = request.GET.get("search_term", "") | ||
| starts_with_letter = request.GET.get("start_with_letter", "") | ||
| active_or_discontinued = list(request.GET.get("tab", "")) | ||
| return default_journal_filter( | ||
| search_term, starts_with_letter, active_or_discontinued | ||
| ) | ||
|
|
||
| def get_context(self, request, *args, **kwargs): | ||
| context = super().get_context(request, *args, **kwargs) | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,90 @@ | ||||||
| import csv | ||||||
| import logging | ||||||
|
|
||||||
| import xlwt | ||||||
| from django.http import HttpResponse | ||||||
| from django.utils import timezone | ||||||
|
|
||||||
| from journal.models import SciELOJournal | ||||||
|
|
||||||
| logger = logging.getLogger(__name__) | ||||||
|
|
||||||
| HEADERS = ["journals", "scielo_url", "publisher"] | ||||||
|
||||||
|
|
||||||
|
|
||||||
| def get_scielo_journals_data(filters=None): | ||||||
| try: | ||||||
| qs = SciELOJournal.objects.all() | ||||||
| if filters is not None: | ||||||
| qs = qs.filter(filters) | ||||||
| scielo_journals = qs.values( | ||||||
| "journal__title", | ||||||
| "collection__domain", | ||||||
| "journal__owner_history__institution__institution__institution_identification__name", | ||||||
| "issn_scielo", | ||||||
| ) | ||||||
|
||||||
| ) | |
| ).distinct() |
Copilot
AI
Mar 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The exported column headers are journals/publisher, but the internal data keys are title/owner. This mismatch makes the utility harder to reuse correctly and increases the chance of future mistakes. Consider aligning the dict keys to the export contract (e.g., use journals and publisher everywhere) and then read those same keys in both generate_csv_response and generate_xls_response.
Copilot
AI
Mar 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Logging the exception message without a traceback makes diagnosing production issues harder. Prefer logger.exception("Error fetching scielo journals data") (or logger.error(..., exc_info=True)) so the traceback is captured.
Copilot
AI
Mar 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The exported column headers are journals/publisher, but the internal data keys are title/owner. This mismatch makes the utility harder to reuse correctly and increases the chance of future mistakes. Consider aligning the dict keys to the export contract (e.g., use journals and publisher everywhere) and then read those same keys in both generate_csv_response and generate_xls_response.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The key new behavior is “export respects the same filters as the listing page” via
get_export_filters(request)+ routable endpoints. There are good unit tests for the export utility, but there’s no coverage shown for the routable download routes applying real request query params (e.g.,search_term,start_with_letter,tab) end-to-end. Add an integration-style test that requests the page’sdownload-csv/(and/ordownload-xls/) with query parameters and asserts the response contains only the filtered journal(s).