Skip to content
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

Add more base viewsets for import/export #88

Merged
merged 1 commit into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,14 @@ ENV/
.ruff_cache/

# IDE settings
.vscode/
.vscode/*
!.vscode/recommended_settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/boilerplate-words.txt
!.vscode/project-related-words.txt
!.vscode/cspell.json
.idea/

# Test files
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ repos:
stages: [pre-push]
- id: tests
name: run tests
entry: inv pytest.run --params="--cov=."
entry: inv pytest.run --params="--numprocesses auto --create-db --cov=."
language: system
pass_filenames: false
types: [python]
Expand Down
57 changes: 57 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Python: Django",
"type": "debugpy",
"request": "launch",
"program": "${workspaceFolder}/test_project/manage.py",
"preLaunchTask": "Launch containers and wait for DB",
"args": [
"runserver_plus",
"localhost:8000",
],
"django": true,
"justMyCode": false
},
{
"name": "Python: Django With SQL Logs",
"type": "debugpy",
"request": "launch",
"program": "${workspaceFolder}/test_project/manage.py",
"preLaunchTask": "Launch containers and wait for DB",
"args": [
"runserver_plus",
"localhost:8000",
"--print-sql"
],
"django": true,
"justMyCode": false
},
{
"name": "Python: Celery",
"type": "debugpy",
"request": "launch",
"module": "celery",
"preLaunchTask": "Launch containers and wait for DB",
"args": [
"--app",
"test_project.celery_app.app",
"worker",
"--beat",
"--scheduler=django",
"--loglevel=info",
],
"justMyCode": false
},
{
"name": "Python: Debug Tests",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"purpose": ["debug-test"],
"console": "integratedTerminal",
"justMyCode": false
},
]
}
26 changes: 26 additions & 0 deletions .vscode/recommended_settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"files.exclude": {
"**/__pycache__": true,
"**/.pytest_cache": true,
"**/.mypy_cache": true,
"**/.ruff_cache": true,
"**/htmlcov": true,
},

"editor.rulers": [79],

"editor.bracketPairColorization.enabled": true,

"python.analysis.typeCheckingMode": "off",

"python.analysis.inlayHints.functionReturnTypes": true,
"mypy.enabled": false,

"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,

"[python]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "charliermarsh.ruff"
}
}
15 changes: 15 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Launch containers and wait for DB",
"type": "shell",
"command": "inv django.wait-for-database",
"problemMatcher": [],
"group": {
"kind": "build",
"isDefault": false
}
}
]
}
6 changes: 6 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
History
=======

UNRELEASED
------------------

* Add base import/export views that only allow users to work with their own jobs (`ImportJobForUserViewSet` and `ExportJobForUserViewSet`).
* Small actions definition refactor in `ExportJobViewSet/ExportJobViewSet` to allow easier overriding.

1.2.0 (2024-12-26)
------------------
* Fix issue with slow export duration (https://github.com/saritasa-nest/django-import-export-extensions/issues/79):
Expand Down
18 changes: 15 additions & 3 deletions import_export_extensions/api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
from .serializers.export_job import CreateExportJob, ExportJobSerializer
from .serializers.import_job import CreateImportJob, ImportJobSerializer
from .serializers.progress import ProgressInfoSerializer, ProgressSerializer
from .mixins import LimitQuerySetToCurrentUserMixin
from .serializers import (
CreateExportJob,
CreateImportJob,
ExportJobSerializer,
ImportJobSerializer,
ProgressInfoSerializer,
ProgressSerializer,
)
from .views import (
ExportJobForUserViewSet,
ExportJobViewSet,
ImportJobForUserViewSet,
ImportJobViewSet,
)
10 changes: 10 additions & 0 deletions import_export_extensions/api/mixins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class LimitQuerySetToCurrentUserMixin:
"""Make queryset to return only current user jobs."""

def get_queryset(self):
"""Return user's jobs."""
return (
super()
.get_queryset()
.filter(created_by_id=getattr(self.request.user, "pk", None))
)
13 changes: 11 additions & 2 deletions import_export_extensions/api/serializers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,11 @@
from .export_job import ExportJobSerializer, get_create_export_job_serializer
from .import_job import ImportJobSerializer, get_create_import_job_serializer
from .export_job import (
CreateExportJob,
ExportJobSerializer,
get_create_export_job_serializer,
)
from .import_job import (
CreateImportJob,
ImportJobSerializer,
get_create_import_job_serializer,
)
from .progress import ProgressInfoSerializer, ProgressSerializer
10 changes: 8 additions & 2 deletions import_export_extensions/api/views/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
from .export_job import ExportJobViewSet
from .import_job import ImportJobViewSet
from .export_job import (
ExportJobForUserViewSet,
ExportJobViewSet,
)
from .import_job import (
ImportJobForUserViewSet,
ImportJobViewSet,
)
56 changes: 33 additions & 23 deletions import_export_extensions/api/views/export_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import django_filters

from ... import models, resources
from .. import mixins as core_mixins
from .. import serializers


Expand All @@ -37,27 +38,11 @@ def __new__(cls, name, bases, attrs, **kwargs):
attrs,
**kwargs,
)
# Skip if it is a base viewset, since none of needed class attrs are
# specified
if name == "ExportJobViewSet":
# Skip if it is has no resource_class specified
if not hasattr(viewset, "resource_class"):
return viewset

def start(self: "ExportJobViewSet", request: Request):
"""Validate request data and start ExportJob."""
serializer = self.get_serializer(
data=request.data,
filter_kwargs=request.query_params,
)
serializer.is_valid(raise_exception=True)
export_job = serializer.save()
return response.Response(
data=self.get_detail_serializer_class()(
instance=export_job,
).data,
status=status.HTTP_201_CREATED,
)

viewset.start = decorators.action(
decorators.action(
methods=["POST"],
detail=False,
queryset=viewset.resource_class.get_model_queryset(),
Expand All @@ -67,7 +52,11 @@ def start(self: "ExportJobViewSet", request: Request):
filter_backends=[
django_filters.rest_framework.DjangoFilterBackend,
],
)(start)
)(viewset.start)
decorators.action(
methods=["POST"],
detail=True,
)(viewset.cancel)
# Correct specs of drf-spectacular if it is installed
with contextlib.suppress(ImportError):
from drf_spectacular.utils import extend_schema, extend_schema_view
Expand Down Expand Up @@ -110,8 +99,8 @@ class ExportJobViewSet(
permission_classes = (permissions.IsAuthenticated,)
queryset = models.ExportJob.objects.all()
serializer_class = serializers.ExportJobSerializer
resource_class: type[resources.CeleryModelResource] | None = None
filterset_class: django_filters.rest_framework.FilterSet = None
resource_class: type[resources.CeleryModelResource]
filterset_class: django_filters.rest_framework.FilterSet | None = None
search_fields = ("id",)
ordering = (
"id",
Expand Down Expand Up @@ -154,7 +143,21 @@ def get_export_create_serializer_class(self):
self.resource_class,
)

@decorators.action(methods=["POST"], detail=True)
def start(self, request: Request):
"""Validate request data and start ExportJob."""
serializer = self.get_serializer(
data=request.data,
filter_kwargs=request.query_params,
)
serializer.is_valid(raise_exception=True)
export_job = serializer.save()
return response.Response(
data=self.get_detail_serializer_class()(
instance=export_job,
).data,
status=status.HTTP_201_CREATED,
)

def cancel(self, *args, **kwargs):
"""Cancel export job that is in progress."""
job: models.ExportJob = self.get_object()
Expand All @@ -169,3 +172,10 @@ def cancel(self, *args, **kwargs):
status=status.HTTP_200_OK,
data=serializer.data,
)


class ExportJobForUserViewSet(
core_mixins.LimitQuerySetToCurrentUserMixin,
ExportJobViewSet,
):
"""Viewset for providing export feature to users."""
30 changes: 23 additions & 7 deletions import_export_extensions/api/views/import_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
)

from ... import models, resources
from .. import mixins as core_mixins
from .. import serializers


Expand All @@ -32,11 +33,23 @@ def __new__(cls, name, bases, attrs, **kwargs):
attrs,
**kwargs,
)
# Skip if it is a base viewset, since none of needed class attrs are
# specified
if name == "ImportJobViewSet":
# Skip if it is has no resource_class specified
if not hasattr(viewset, "resource_class"):
return viewset

decorators.action(
methods=["POST"],
detail=False,
)(viewset.start)
decorators.action(
methods=["POST"],
detail=True,
)(viewset.confirm)
decorators.action(
methods=["POST"],
detail=True,
)(viewset.cancel)

# Correct specs of drf-spectacular if it is installed
with contextlib.suppress(ImportError):
from drf_spectacular.utils import extend_schema, extend_schema_view
Expand Down Expand Up @@ -89,7 +102,7 @@ class ImportJobViewSet(
permission_classes = (permissions.IsAuthenticated,)
queryset = models.ImportJob.objects.all()
serializer_class = serializers.ImportJobSerializer
resource_class: type[resources.CeleryModelResource] | None = None
resource_class: type[resources.CeleryModelResource]
search_fields = ("id",)
ordering = (
"id",
Expand Down Expand Up @@ -132,7 +145,6 @@ def get_import_create_serializer_class(self):
self.resource_class,
)

@decorators.action(methods=["POST"], detail=False)
def start(self, request, *args, **kwargs):
"""Validate request data and start ImportJob."""
serializer = self.get_serializer(data=request.data)
Expand All @@ -147,7 +159,6 @@ def start(self, request, *args, **kwargs):
status=status.HTTP_201_CREATED,
)

@decorators.action(methods=["POST"], detail=True)
def confirm(self, *args, **kwargs):
"""Confirm import job that has `parsed` status."""
job: models.ImportJob = self.get_object()
Expand All @@ -163,7 +174,6 @@ def confirm(self, *args, **kwargs):
data=serializer.data,
)

@decorators.action(methods=["POST"], detail=True)
def cancel(self, *args, **kwargs):
"""Cancel import job that is in progress."""
job: models.ImportJob = self.get_object()
Expand All @@ -178,3 +188,9 @@ def cancel(self, *args, **kwargs):
status=status.HTTP_200_OK,
data=serializer.data,
)

class ImportJobForUserViewSet(
core_mixins.LimitQuerySetToCurrentUserMixin,
ImportJobViewSet,
):
"""Viewset for providing import feature to users."""
1 change: 1 addition & 0 deletions invocations/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ def init(context: invoke.Context, clean: bool = False):
"""Prepare env for working with project."""
saritasa_invocations.print_success("Setting up git config")
saritasa_invocations.git.setup(context)
saritasa_invocations.system.copy_vscode_settings(context)
saritasa_invocations.print_success("Initial assembly of all dependencies")
saritasa_invocations.poetry.install(context)
if clean:
Expand Down
Loading
Loading