Skip to content

Commit

Permalink
feat: converter for export view
Browse files Browse the repository at this point in the history
  • Loading branch information
marcosgabarda committed Oct 1, 2021
1 parent 7ccfeac commit 9ec42e2
Show file tree
Hide file tree
Showing 7 changed files with 53 additions and 17 deletions.
6 changes: 6 additions & 0 deletions HISTORY.rst
Expand Up @@ -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)
+++++++++++++++++

Expand Down
2 changes: 1 addition & 1 deletion 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
2 changes: 1 addition & 1 deletion 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"
9 changes: 7 additions & 2 deletions backoffice_extensions/helpers.py
Expand Up @@ -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.
"""
Expand All @@ -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 = {}
Expand Down
46 changes: 34 additions & 12 deletions 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
Expand All @@ -15,7 +16,7 @@
)

if TYPE_CHECKING:
from django.db.models import QuerySet
from django.http import HttpRequest, HttpResponse


class BackOfficeViewMixin:
Expand Down Expand Up @@ -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:
Expand All @@ -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)
Expand All @@ -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 = []

Expand All @@ -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"
Expand All @@ -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
Expand All @@ -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
3 changes: 3 additions & 0 deletions backoffice_extensions/templates/backoffice/bases/list.html
Expand Up @@ -15,6 +15,9 @@
<div style="margin-left: auto; margin-right: 1rem;">
{% include "backoffice/partials/search.html" %}
</div>
{% block actions %}
{% endblock actions %}
{# Deprecated, keep export block for compatibility #}
{% block export %}
{% endblock export %}
</div>
Expand Down
2 changes: 1 addition & 1 deletion 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 <marcos@dekalabs.com>"]
Expand Down

0 comments on commit 9ec42e2

Please sign in to comment.