Skip to content

Commit 2ebd2ad

Browse files
author
Janek Mangold
committed
feat: add base models, filters, management command, and admin utilities
1 parent 2d79212 commit 2ebd2ad

File tree

10 files changed

+378
-2
lines changed

10 files changed

+378
-2
lines changed

django/hooks/post_gen_project.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,29 @@
33

44
project_slug = "{{ cookiecutter.project_slug }}"
55
project_path = Path(project_slug)
6+
common_path = Path("common")
67
use_drf = "{{ cookiecutter.use_drf }}" == "y"
78

8-
def _remove_file(file_path: Path) -> None:
9+
def _remove_file(file_path: Path):
910
"""Remove a file if it exists."""
1011
file_path.unlink(missing_ok=True)
1112

13+
def _remove_dir(dir_path: Path):
14+
"""Remove a directory if it exists."""
15+
if dir_path.is_dir():
16+
for item in dir_path.iterdir():
17+
if item.is_file():
18+
item.unlink(missing_ok=True)
19+
elif item.is_dir():
20+
_remove_dir(item)
21+
dir_path.rmdir()
1222

1323
def handle_drf():
1424
if use_drf:
1525
return
1626

1727
_remove_file(project_path / "drf_exception_handler.py")
28+
_remove_dir(common_path / "drf")
1829

1930
def main():
2031
handle_drf()
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from django.contrib import admin
2+
3+
4+
class ReadOnlyTabularInline(admin.TabularInline):
5+
"""
6+
A read-only tabular inline for Django admin.
7+
"""
8+
9+
can_delete = False
10+
extra = 0
11+
show_change_link = True
12+
13+
def get_readonly_fields(self, request, obj=None):
14+
"""
15+
Override to make all fields readonly.
16+
"""
17+
return [field.name for field in self.model._meta.fields]
18+
19+
def has_add_permission(self, request, obj=None):
20+
return False
21+
22+
def has_change_permission(self, request, obj=None):
23+
return False
24+
25+
def has_delete_permission(self, request, obj=None):
26+
return False
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .filters import MultipleValuesFilter
2+
from .renderers import BinaryRenderer
3+
from .view_set_mixins import MultipleSerializersMixin
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from django.forms import Field
2+
from django_filters import Filter, FilterSet, MultipleChoiceFilter
3+
from django_filters.widgets import QueryArrayWidget
4+
5+
6+
class MultipleValuesField(Field):
7+
widget = QueryArrayWidget
8+
9+
10+
class MultipleValuesFilter(MultipleChoiceFilter):
11+
field_class = MultipleValuesField
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from typing import Any, Dict, Optional, Union
2+
3+
from rest_framework.renderers import BaseRenderer
4+
5+
6+
class BinaryRenderer(BaseRenderer):
7+
"""Renderer for binary content like Excel files."""
8+
9+
media_type = "application/vnd.ms-excel"
10+
format = "bin"
11+
12+
def render(
13+
self, data: Any, accepted_media_type: Optional[str] = None, renderer_context: Optional[Dict[str, Any]] = None
14+
) -> Union[bytes, str, None]:
15+
return data
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
class MultipleSerializersMixin:
2+
"""
3+
Mixin for ViewSets that allows to use multiple serializers
4+
for different actions. Defaults to serializer_class for all actions.
5+
"""
6+
7+
serializer_classes = {}
8+
'''The keys should be the actions names: "list", "retrieve", "create", "update", "partial_update", "destroy"'''
9+
10+
def get_serializer_class(self):
11+
"""
12+
Return the class to use for the serializer.
13+
Defaults to using `self.serializer_classes`.
14+
"""
15+
try:
16+
return self.serializer_classes[self.action]
17+
except KeyError:
18+
assert self.serializer_class is not None, (
19+
f"Serializer class for action '{self.action}' not found and no default serializer class set."
20+
)
21+
return self.serializer_class
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
from abc import ABC
2+
from typing import Callable, Union
3+
4+
from django.core.management.base import BaseCommand as DjangoCommand
5+
from django.core.management.base import CommandParser
6+
7+
8+
class BaseCommand(DjangoCommand, ABC):
9+
"""BaseCommand that can be inherited and customized."""
10+
11+
quiet: bool = False
12+
"""If True, the print statements are quiet."""
13+
14+
def __init__(self, stdout=None, stderr=None, no_color=False, force_color=False, quiet=False):
15+
self.quiet = quiet
16+
super().__init__(stdout=stdout, stderr=stderr, no_color=no_color, force_color=force_color)
17+
18+
def add_arguments(self, parser: CommandParser) -> None:
19+
parser.add_argument(
20+
"--quiet",
21+
action="store_true",
22+
default=False,
23+
help="Print no information.",
24+
)
25+
26+
def _print_s(
27+
self,
28+
txt,
29+
ending=None,
30+
):
31+
"""
32+
Print given `txt` in SUCCESS style to `self.stdout` (generally green).
33+
"""
34+
self._print(txt=txt, style=self.style.SUCCESS, ending=ending)
35+
36+
def _print_w(
37+
self,
38+
txt,
39+
ending=None,
40+
):
41+
"""
42+
Print given `txt` in WARNING style to `self.stdout` (generally yellow).
43+
"""
44+
self._print(txt=txt, style=self.style.WARNING, ending=ending)
45+
46+
def _print_e(
47+
self,
48+
txt,
49+
ending=None,
50+
):
51+
"""
52+
Print given `txt` in ERROR style to `self.stdout` (generally red).
53+
"""
54+
self._print(txt=txt, style=self.style.ERROR, ending=ending)
55+
56+
def _print_n(
57+
self,
58+
txt,
59+
ending=None,
60+
):
61+
"""
62+
Print given `txt` in NOTICE style to `self.stdout` (generally red, but lighter).
63+
"""
64+
self._print(txt=txt, style=self.style.NOTICE, ending=ending)
65+
66+
def _print(
67+
self,
68+
txt,
69+
style: Union[None, Callable] = None,
70+
ending=None,
71+
):
72+
if self.quiet:
73+
return
74+
if style is not None:
75+
self.stdout.write(style(txt), ending=ending)
76+
else:
77+
self.stdout.write(txt, ending=ending)
78+
79+
def confirm_action(self, message: str) -> bool:
80+
response = input(f"{message} (y/n): ").strip().lower()
81+
return response == "y"
82+
83+
def handle(self, *args, **options):
84+
"""
85+
Override this function to implement the command. Call
86+
`super().handle(*args, **options)` first in order to add parsing for
87+
`BaseCommand` arguments.
88+
"""
89+
90+
if options["quiet"]:
91+
self.quiet = True
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from django.db import models
2+
3+
class TimestampModel(models.Model):
4+
created_at = models.DateTimeField(auto_now_add=True)
5+
updated_at = models.DateTimeField(auto_now=True)
6+
7+
class Meta:
8+
abstract = True

0 commit comments

Comments
 (0)