diff --git a/HISTORY.rst b/HISTORY.rst index ddf8e02..92ff4be 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,12 @@ History ------- +2.2.0 (2021-10-1) ++++++++++++++++++ + +* Feat: changed name for actions block in list template +* Feat: added custom converters to export view mixin + 2.1.1 (2021-9-27) +++++++++++++++++ diff --git a/Makefile b/Makefile index aa20f21..ec61f0f 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # Common commands to handle the project. # ------------------------------------------------------------------------------ -check: +lint: poetry run isort backoffice_extensions --profile black poetry run black backoffice_extensions poetry run mypy backoffice_extensions diff --git a/backoffice_extensions/__init__.py b/backoffice_extensions/__init__.py index 2f89473..b9f0264 100644 --- a/backoffice_extensions/__init__.py +++ b/backoffice_extensions/__init__.py @@ -1,7 +1,7 @@ """Django app made to create backoffices to help the administration of a site, like the Django Admin site, but for final users. """ -__version__ = "2.1.1" +__version__ = "2.2.0" default_app_config = "backoffice_extensions.apps.BackofficeAppConfig" diff --git a/backoffice_extensions/helpers.py b/backoffice_extensions/helpers.py index 21ad92f..4ffa5d7 100644 --- a/backoffice_extensions/helpers.py +++ b/backoffice_extensions/helpers.py @@ -2,11 +2,14 @@ import csv import datetime import io +from typing import Any, Dict, Optional from django.utils import timezone -def create_csv_from_data(data, stream=None): +def create_csv_from_data( + data: Dict, stream: Optional["io.StringIO"] = None +) -> "io.StringIO": """Creates a CSV stream using the given dict, in where the keys are the columns and each value is a list of results. """ @@ -20,7 +23,9 @@ def create_csv_from_data(data, stream=None): return stream -def age_range_filter(field, min_age=None, max_age=None): +def age_range_filter( + field: Any, min_age: Optional[int] = None, max_age: Optional[int] = None +) -> Dict: """Returns the filter for the age range.""" current = timezone.now().date() _filter = {} diff --git a/backoffice_extensions/mixins.py b/backoffice_extensions/mixins.py index d3087f8..ce91eca 100644 --- a/backoffice_extensions/mixins.py +++ b/backoffice_extensions/mixins.py @@ -1,7 +1,8 @@ import collections from functools import reduce -from typing import TYPE_CHECKING, AnyStr, Dict, List, Optional +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple +from django.db import models from django.db.models import Q from django.http import HttpResponse from django.urls import NoReverseMatch, reverse @@ -15,7 +16,7 @@ ) if TYPE_CHECKING: - from django.db.models import QuerySet + from django.http import HttpRequest, HttpResponse class BackOfficeViewMixin: @@ -60,7 +61,7 @@ class SearchListMixin: search_param: str = "search" search_fields: List = [] - def _search_filter(self, queryset) -> "QuerySet": + def _search_filter(self, queryset) -> "models.QuerySet": """Applies search filtering to queryset.""" search_query = self.request.GET.get(self.search_param) # type: ignore if search_query: @@ -74,7 +75,7 @@ def _search_filter(self, queryset) -> "QuerySet": ).distinct() return queryset - def get_queryset(self) -> "QuerySet": + def get_queryset(self) -> "models.QuerySet": """Checks the 'search' variable to allow generic search.""" queryset = super().get_queryset() # type: ignore queryset = self._search_filter(queryset) @@ -90,11 +91,11 @@ def get_extra_context(self) -> Dict: return context -class ExportMixin: +class CSVExportMixin: """Mixin to allow export CSV data.""" filename: str = "data.csv" - queryset: "QuerySet" = None + queryset: "models.QuerySet" = None filterset_class = None fields: List = [] @@ -103,7 +104,7 @@ def __init__(self, *args, **kwargs): if self.queryset is None: raise NotImplementedError("You should specify the queryset attribute.") - def get_csv_response(self, data): + def get_csv_response(self, data: Dict) -> "HttpResponse": response = HttpResponse(content_type="text/csv") response[ "Content-Disposition" @@ -114,11 +115,27 @@ def get_csv_response(self, data): def get_filename(self) -> str: return self.filename - def get_queryset(self) -> "QuerySet": + def get_queryset(self) -> "models.QuerySet": return self.queryset - def get(self, request, *args, **kwargs): - items: "QuerySet" = self.get_queryset() + def _default_convert_value(self, value: Any) -> Any: + """Default value converter.""" + if isinstance(value, models.Manager): + value = ", ".join([str(element) for element in value.all()]) + elif hasattr(value, "__call__"): + value = value() + return value + + def convert_value(self, value: Any) -> Tuple[Any, bool]: + """Function to handle the value. It should return a tuple where the first + value is the handled value and the second is if the value was handled or not. + + This method should be overwrite by children classes. + """ + return value, False + + def get(self, request: "HttpRequest", *args, **kwargs) -> "HttpResponse": + items: "models.QuerySet" = self.get_queryset() if self.filterset_class: _filter = self.filterset_class(request.GET, queryset=items, request=request) items = _filter.qs @@ -133,7 +150,12 @@ def get(self, request, *args, **kwargs): for item in items.iterator(): for field in fields: value = getattr(item, field) - if hasattr(value, "__call__"): - value = value() + value, converted = self.convert_value(value) + # If not handled, uses the default value converters + if not converted: + value = self._default_convert_value(value) data[field].append(value) return self.get_csv_response(data=data) + + +ExportMixin = CSVExportMixin # Alias for compatibility diff --git a/backoffice_extensions/templates/backoffice/bases/list.html b/backoffice_extensions/templates/backoffice/bases/list.html index e912750..85f46ce 100644 --- a/backoffice_extensions/templates/backoffice/bases/list.html +++ b/backoffice_extensions/templates/backoffice/bases/list.html @@ -15,6 +15,9 @@
{% include "backoffice/partials/search.html" %}
+ {% block actions %} + {% endblock actions %} + {# Deprecated, keep export block for compatibility #} {% block export %} {% endblock export %} diff --git a/pyproject.toml b/pyproject.toml index cabda7e..d37f22e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "django-backoffice-extensions" -version = "2.1.1" +version = "2.2.0" description = "A set of views, tools and helpers to create a backoffice using Django." readme = "README.rst" authors = ["Marcos Gabarda "]