django-bananas 2.1__py3-none-any.whl → 2.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
bananas/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- VERSION = (2, 1, 0, "final", 0)
1
+ VERSION = (2, 3, 0, "final", 0)
2
2
 
3
3
 
4
4
  def get_version() -> str:
@@ -63,9 +63,9 @@ class BananasAPI:
63
63
  if admin is not None:
64
64
  meta.update(
65
65
  {
66
- key: getattr(admin, key) # type: ignore[misc, call-overload]
66
+ key: getattr(admin, key)
67
67
  for key in filter(
68
- lambda key: key in meta, # type: ignore[arg-type]
68
+ lambda key: key in meta,
69
69
  admin.__dict__.keys(),
70
70
  )
71
71
  }
@@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Any, Type, cast
3
3
  from rest_framework.viewsets import ViewSetMixin
4
4
 
5
5
  if TYPE_CHECKING:
6
- from ..mixins import BananasAPI
6
+ from bananas.admin.api.mixins import BananasAPI
7
7
 
8
8
 
9
9
  class BananasBaseRouter:
@@ -13,7 +13,8 @@ from rest_framework.request import Request
13
13
  from rest_framework.routers import SimpleRouter
14
14
  from rest_framework.schemas.coreapi import is_custom_action
15
15
 
16
- from ..versioning import BananasVersioning
16
+ from bananas.admin.api.versioning import BananasVersioning
17
+
17
18
  from .base import BananasBaseRouter
18
19
 
19
20
 
@@ -1,7 +1,7 @@
1
1
  from django.urls import include, re_path
2
2
 
3
- from .. import views
4
- from ..router import register, router
3
+ from bananas.admin.api import views
4
+ from bananas.admin.api.router import register, router
5
5
 
6
6
  register(views.LoginAPI)
7
7
  register(views.LogoutAPI)
@@ -1,5 +1,5 @@
1
1
  from types import ModuleType
2
- from typing import Dict, Sequence
2
+ from typing import ClassVar, Dict, Sequence
3
3
 
4
4
  from rest_framework.request import Request
5
5
  from rest_framework.versioning import NamespaceVersioning
@@ -10,12 +10,11 @@ __versions__ = [v1_0]
10
10
 
11
11
 
12
12
  class BananasVersioning(NamespaceVersioning):
13
-
14
13
  default_version: str = v1_0.__version__
15
14
  allowed_versions: Sequence[str] = tuple(
16
15
  version.__version__ for version in __versions__
17
16
  )
18
- version_map: Dict[str, ModuleType] = {
17
+ version_map: ClassVar[Dict[str, ModuleType]] = {
19
18
  version.__version__: version for version in __versions__
20
19
  }
21
20
 
@@ -1,3 +1,5 @@
1
+ from typing import ClassVar, List
2
+
1
3
  from django.contrib.auth import (
2
4
  login as auth_login,
3
5
  logout as auth_logout,
@@ -11,7 +13,8 @@ from rest_framework.permissions import AllowAny
11
13
  from rest_framework.request import Request
12
14
  from rest_framework.response import Response
13
15
 
14
- from .i18n import RawTranslationCatalog
16
+ from bananas.admin.i18n import RawTranslationCatalog
17
+
15
18
  from .mixins import BananasAPI
16
19
  from .permissions import IsAnonymous
17
20
  from .schemas import schema
@@ -29,8 +32,7 @@ class BananasAdminAPI(BananasAPI, viewsets.GenericViewSet):
29
32
 
30
33
 
31
34
  class LoginAPI(BananasAdminAPI):
32
-
33
- name = _("Log in")
35
+ name = _("Log in") # type: ignore[assignment]
34
36
  basename = "login"
35
37
  permission_classes = (IsAnonymous,)
36
38
  serializer_class = AuthenticationSerializer # Placeholder for schema
@@ -51,7 +53,7 @@ class LoginAPI(BananasAdminAPI):
51
53
  login_form = AuthenticationForm(request, data=request.data)
52
54
 
53
55
  if not login_form.is_valid():
54
- raise serializers.ValidationError(login_form.errors)
56
+ raise serializers.ValidationError(login_form.errors) # type: ignore[arg-type]
55
57
 
56
58
  auth_login(request, login_form.get_user())
57
59
 
@@ -60,8 +62,7 @@ class LoginAPI(BananasAdminAPI):
60
62
 
61
63
 
62
64
  class LogoutAPI(BananasAPI, viewsets.ViewSet):
63
-
64
- name = _("Log out")
65
+ name = _("Log out") # type: ignore[assignment]
65
66
  basename = "logout"
66
67
 
67
68
  class Admin:
@@ -77,11 +78,10 @@ class LogoutAPI(BananasAPI, viewsets.ViewSet):
77
78
 
78
79
 
79
80
  class MeAPI(BananasAdminAPI):
80
-
81
81
  serializer_class = UserSerializer
82
82
 
83
83
  class Admin:
84
- exclude_tags = ["navigation"]
84
+ exclude_tags: ClassVar[List[str]] = ["navigation"]
85
85
 
86
86
  @schema(responses={200: UserSerializer})
87
87
  def list(self, request: Request) -> Response:
@@ -93,8 +93,7 @@ class MeAPI(BananasAdminAPI):
93
93
 
94
94
 
95
95
  class ChangePasswordAPI(BananasAdminAPI):
96
-
97
- name = _("Change password")
96
+ name = _("Change password") # type: ignore[assignment]
98
97
  basename = "change_password"
99
98
  serializer_class = PasswordChangeSerializer # Placeholder for schema
100
99
 
@@ -112,22 +111,21 @@ class ChangePasswordAPI(BananasAdminAPI):
112
111
  password_form = PasswordChangeForm(request.user, data=request.data)
113
112
 
114
113
  if not password_form.is_valid():
115
- raise serializers.ValidationError(password_form.errors)
114
+ raise serializers.ValidationError(password_form.errors) # type: ignore[arg-type]
116
115
 
117
116
  password_form.save()
118
- update_session_auth_hash(request, password_form.user)
117
+ update_session_auth_hash(request, password_form.user) # type: ignore[arg-type]
119
118
 
120
119
  return Response(status=status.HTTP_204_NO_CONTENT)
121
120
 
122
121
 
123
122
  class TranslationAPI(BananasAdminAPI):
124
-
125
- name = _("Translation catalog")
123
+ name = _("Translation catalog") # type: ignore[assignment]
126
124
  basename = "i18n"
127
125
  permission_classes = (AllowAny,)
128
126
 
129
127
  class Admin:
130
- exclude_tags = ["navigation"]
128
+ exclude_tags: ClassVar[List[str]] = ["navigation"]
131
129
 
132
130
  @schema(responses={200: ""})
133
131
  def list(self, request: Request) -> Response:
@@ -2,6 +2,7 @@ import re
2
2
  from typing import (
3
3
  Any,
4
4
  Callable,
5
+ ClassVar,
5
6
  Dict,
6
7
  List,
7
8
  Optional,
@@ -31,7 +32,7 @@ from django.utils.safestring import SafeText, mark_safe
31
32
  from django.utils.translation import gettext_lazy as _
32
33
  from django.views.generic import View
33
34
 
34
- from ..environment import env
35
+ from bananas.environment import env
35
36
 
36
37
  __all__ = ["ModelAdminView", "ViewTool", "AdminView", "register", "site"]
37
38
 
@@ -41,7 +42,7 @@ MT = TypeVar("MT", bound=Model)
41
42
 
42
43
  class ExtendedAdminSite(AdminSite):
43
44
  enable_nav_sidebar = False
44
- default_settings = {
45
+ default_settings: ClassVar[Dict[str, Any]] = {
45
46
  "INHERIT_REGISTERED_MODELS": env.get_bool(
46
47
  "DJANGO_ADMIN_INHERIT_REGISTERED_MODELS", True
47
48
  ),
@@ -84,10 +85,8 @@ class ModelAdminView(ModelAdmin):
84
85
  @cached_property
85
86
  def access_permission(self) -> str:
86
87
  meta = self.model._meta
87
- return "{app_label}.{codename}".format(
88
- app_label=meta.app_label,
89
- codename=meta.permissions[0][0], # First perm codename
90
- )
88
+ codename = meta.permissions[0][0] # First perm codename
89
+ return f"{meta.app_label}.{codename}"
91
90
 
92
91
  def get_urls(self) -> List[URLPattern]:
93
92
  app_label = self.model._meta.app_label
@@ -122,7 +121,8 @@ class ModelAdminView(ModelAdmin):
122
121
 
123
122
  admin_login_url = reverse_lazy("admin:login")
124
123
  view = user_passes_test(
125
- lambda u: u.is_active and u.is_staff, login_url=admin_login_url
124
+ lambda u: u.is_active and hasattr(u, "is_staff") and u.is_staff,
125
+ login_url=admin_login_url,
126
126
  )(view)
127
127
  view = permission_required(perm, login_url=admin_login_url)(view)
128
128
  return view
@@ -241,9 +241,9 @@ def register(
241
241
  )
242
242
  # The first permission here is expected to be
243
243
  # the general access permission.
244
- permissions = tuple(
245
- [(access_perm_codename, access_perm_name)]
246
- + list(getattr(inner_view, "permissions", []))
244
+ permissions = (
245
+ (access_perm_codename, access_perm_name),
246
+ *list(getattr(inner_view, "permissions", [])),
247
247
  )
248
248
 
249
249
  model = type(
@@ -303,9 +303,11 @@ class ViewTool:
303
303
 
304
304
 
305
305
  class AdminView(View):
306
- tools: Optional[List[Union[Tuple[str, str], Tuple[str, str, str], ViewTool]]] = None
307
- action: Optional[str] = None
308
- admin: Optional[ModelAdminView] = None
306
+ tools: ClassVar[
307
+ Optional[List[Union[Tuple[str, str], Tuple[str, str, str], ViewTool]]]
308
+ ] = None
309
+ action: ClassVar[Optional[str]] = None
310
+ admin: ClassVar[Optional[ModelAdminView]] = None
309
311
 
310
312
  label: str
311
313
  verbose_name: str
@@ -354,8 +356,8 @@ class AdminView(View):
354
356
  # Mypy doesn't change type on a len(...) call
355
357
  # See: https://github.com/python/mypy/issues/1178
356
358
  if len(tool) == 3:
357
- tool, perm = cast(Tuple[str, str, str], tool)[:-1], tool[-1]
358
- text, link = cast(Tuple[str, str], tool)
359
+ tool, perm = tool[:-1], tool[-1]
360
+ text, link = tool
359
361
  tool = ViewTool(text, link, perm=perm)
360
362
  else:
361
363
  # Assume ViewTool
@@ -4,7 +4,6 @@ from django.views.i18n import JavaScriptCatalog
4
4
 
5
5
 
6
6
  class RawTranslationCatalog(JavaScriptCatalog):
7
-
8
7
  domain = "django"
9
8
 
10
9
  def render_to_response(self, context: Dict[str, Any], **response_kwargs: Any) -> Dict[str, Any]: # type: ignore[override]
bananas/drf/fencing.py CHANGED
@@ -5,6 +5,7 @@ from functools import wraps
5
5
  from typing import (
6
6
  Any,
7
7
  Callable,
8
+ Final,
8
9
  FrozenSet,
9
10
  Generic,
10
11
  List,
@@ -24,7 +25,7 @@ from rest_framework.request import Request
24
25
  from rest_framework.response import Response
25
26
  from rest_framework.serializers import BaseSerializer, ModelSerializer
26
27
  from rest_framework.viewsets import GenericViewSet
27
- from typing_extensions import Final, Protocol, final
28
+ from typing_extensions import Protocol, final
28
29
 
29
30
  from bananas.admin.api.schemas.yasg import BananasSwaggerSchema
30
31
  from bananas.models import TimeStampedModel
@@ -55,14 +56,18 @@ class Fence(abc.ABC, Generic[InstanceType, TokenType]):
55
56
  compare: Callable[[TokenType, TokenType], bool],
56
57
  get_version: Callable[[InstanceType], Optional[TokenType]],
57
58
  openapi_parameter: openapi.Parameter,
58
- rejection: Exception = errors.PreconditionFailed(
59
- "The resource does not fulfill the given preconditions"
60
- ),
59
+ rejection: Optional[Exception] = None,
61
60
  ) -> None:
62
61
  self._get_token: Final = get_token
63
62
  self._compare: Final = compare
64
63
  self._get_version: Final = get_version
65
- self._rejection: Final = rejection
64
+ self._rejection: Final = (
65
+ rejection
66
+ if rejection is not None
67
+ else errors.PreconditionFailed(
68
+ "The resource does not fulfill the given preconditions"
69
+ )
70
+ )
66
71
  self.openapi_parameter: Final = openapi_parameter
67
72
 
68
73
  def check(self, request: Request, instance: InstanceType) -> bool:
@@ -93,7 +98,7 @@ class FenceAwareSwaggerAutoSchema(BananasSwaggerSchema):
93
98
  isinstance(self.view, FencedUpdateModelMixin)
94
99
  and self.method in self.update_methods
95
100
  ):
96
- return parameters + [self.view.fence.openapi_parameter]
101
+ return [*parameters, self.view.fence.openapi_parameter]
97
102
  return parameters
98
103
 
99
104
 
@@ -141,7 +146,7 @@ def header_date_parser(header: str) -> Callable[[Request], datetime.datetime]:
141
146
  try:
142
147
  return parse_header_datetime(request, header)
143
148
  except HeaderError as e:
144
- raise e.as_api_error()
149
+ raise e.as_api_error() from e
145
150
 
146
151
  return parse
147
152
 
@@ -183,7 +188,7 @@ def header_etag_parser(header: str) -> Callable[[Request], FrozenSet[str]]:
183
188
  try:
184
189
  return parse_header_etags(request, header)
185
190
  except HeaderError as e:
186
- raise e.as_api_error()
191
+ raise e.as_api_error() from e
187
192
 
188
193
  return parse
189
194
 
bananas/drf/utils.py CHANGED
@@ -39,8 +39,8 @@ class InvalidHeader(HeaderError):
39
39
  def parse_header_datetime(request: Request, header: str) -> datetime.datetime:
40
40
  try:
41
41
  value = request.headers[header]
42
- except KeyError:
43
- raise MissingHeader(header)
42
+ except KeyError as exc:
43
+ raise MissingHeader(header) from exc
44
44
  try:
45
45
  return datetime.datetime.fromtimestamp(
46
46
  parse_http_date(value), tz=datetime.timezone.utc
@@ -60,8 +60,8 @@ def clean_tags(tags: Iterable[str]) -> Iterable[str]:
60
60
  def parse_header_etags(request: Request, header: str) -> FrozenSet[str]:
61
61
  try:
62
62
  parts = request.headers[header].split(",")
63
- except KeyError:
64
- raise MissingHeader(header)
63
+ except KeyError as exc:
64
+ raise MissingHeader(header) from exc
65
65
  tags = frozenset(clean_tags(parts))
66
66
  if not tags:
67
67
  raise InvalidHeader(header)
bananas/environment.py CHANGED
@@ -5,6 +5,7 @@ from typing import (
5
5
  Any,
6
6
  Callable,
7
7
  Dict,
8
+ Final,
8
9
  Generic,
9
10
  Iterable,
10
11
  List,
@@ -18,7 +19,7 @@ from typing import (
18
19
  )
19
20
 
20
21
  from django.conf import global_settings
21
- from typing_extensions import Final, Protocol, overload
22
+ from typing_extensions import Protocol, overload
22
23
 
23
24
  __all__ = ["env", "parse_bool", "parse_int", "parse_tuple", "parse_list", "parse_set"]
24
25
 
@@ -165,8 +166,8 @@ def get_parser(typ: Type[P]) -> Callable[[str], P]:
165
166
  set: parse_set,
166
167
  }[typ],
167
168
  )
168
- except KeyError:
169
- raise NotImplementedError("Unsupported setting type: %r", typ)
169
+ except KeyError as exc:
170
+ raise NotImplementedError("Unsupported setting type: %r", typ) from exc
170
171
 
171
172
 
172
173
  def get_settings() -> Dict[str, Any]:
@@ -189,8 +190,8 @@ def get_settings() -> Dict[str, Any]:
189
190
  if key:
190
191
  if key in UNSUPPORTED_ENV_SETTINGS:
191
192
  raise ValueError(
192
- 'Django setting "{}" can not be '
193
- "configured through environment.".format(key)
193
+ f'Django setting "{key}" can not be '
194
+ "configured through environment."
194
195
  )
195
196
 
196
197
  default_value = getattr(global_settings, key, UNDEFINED)
@@ -201,9 +202,9 @@ def get_settings() -> Dict[str, Any]:
201
202
  parse = get_parser(SETTINGS_TYPES[key])
202
203
  else:
203
204
  # Determine parser by django setting type
204
- parse = get_parser(type(default_value))
205
+ parse = get_parser(type(default_value)) # type: ignore[type-var]
205
206
 
206
- value = parse(value)
207
+ value = parse(value) # type: ignore[assignment]
207
208
 
208
209
  settings[key] = value
209
210
 
@@ -253,11 +254,7 @@ class EnvironWrapper:
253
254
  try:
254
255
  return parser(value)
255
256
  except ValueError:
256
- log.warning(
257
- "Unable to parse environment variable {key}={value}".format(
258
- key=key, value=value
259
- )
260
- )
257
+ log.warning(f"Unable to parse environment variable {key}={value}")
261
258
  return default
262
259
 
263
260
  @overload
@@ -19,7 +19,7 @@ def collect_urls(
19
19
  pattern = urls.pattern.regex.pattern
20
20
  for x in urls.url_patterns:
21
21
  res += collect_urls(
22
- x, namespace=urls.namespace or namespace, prefix=prefix + [pattern]
22
+ x, namespace=urls.namespace or namespace, prefix=[*prefix, pattern]
23
23
  )
24
24
  return res
25
25
  elif isinstance(urls, URLPattern):
@@ -30,7 +30,7 @@ def collect_urls(
30
30
  [
31
31
  ("namespace", namespace),
32
32
  ("name", urls.name),
33
- ("pattern", prefix + [pattern]),
33
+ ("pattern", [*prefix, pattern]),
34
34
  ("lookup_str", lookup_str),
35
35
  ("default_args", dict(urls.default_args or {})),
36
36
  ]
@@ -3,7 +3,6 @@ from django.core.management.base import BaseCommand, CommandError
3
3
 
4
4
 
5
5
  class Command(BaseCommand):
6
-
7
6
  help = "Create admin permissions"
8
7
 
9
8
  def handle(self, *args: object, **options: object) -> None:
@@ -29,11 +28,13 @@ class Command(BaseCommand):
29
28
  )
30
29
 
31
30
  if created:
32
- print(f"Found new admin view: {ct.name} [{ct.app_label}]")
31
+ self.stdout.write(
32
+ f"Found new admin view: {ct.name} [{ct.app_label}]"
33
+ )
33
34
 
34
35
  for codename, name in model._meta.permissions:
35
36
  p, created = Permission.objects.update_or_create(
36
37
  codename=codename, content_type=ct, defaults={"name": name}
37
38
  )
38
39
  if created:
39
- print(f"Created permission: {name}")
40
+ self.stdout.write(f"Created permission: {name}")
bananas/models.py CHANGED
@@ -4,12 +4,11 @@ import math
4
4
  import os
5
5
  import uuid
6
6
  from itertools import chain
7
- from typing import Any, Dict, Mapping, Optional, Sized
7
+ from typing import Any, ClassVar, Dict, Final, Mapping, Optional, Sized
8
8
 
9
9
  from django.core.exceptions import ValidationError
10
10
  from django.db import models
11
11
  from django.utils.translation import gettext_lazy as _
12
- from typing_extensions import Final
13
12
 
14
13
 
15
14
  class Missing:
@@ -20,7 +19,6 @@ MISSING: Final = Missing()
20
19
 
21
20
 
22
21
  class ModelDict(Dict[str, Any]):
23
-
24
22
  _nested: Optional[Dict[str, "ModelDict"]] = None
25
23
 
26
24
  def __getattr__(self, item: str) -> Any:
@@ -120,9 +118,7 @@ class ModelDict(Dict[str, Any]):
120
118
  )
121
119
  else:
122
120
  raise AttributeError(
123
- "{!r} does not have {!r} attribute".format(
124
- previous_value, _field
125
- )
121
+ f"{previous_value!r} does not have {_field!r} attribute"
126
122
  )
127
123
 
128
124
  elif value is None:
@@ -180,7 +176,7 @@ class UUIDModel(models.Model):
180
176
  class SecretField(models.CharField):
181
177
  description = _("Generates and stores a random key.")
182
178
 
183
- default_error_messages = {
179
+ default_error_messages = { # noqa: RUF012
184
180
  "random-is-none": _("%(cls)s.get_random_bytes returned None"),
185
181
  "random-too-short": _(
186
182
  "Too few random bytes received from "
bananas/query.py CHANGED
@@ -74,7 +74,7 @@ class ModelDictQuerySetMixin:
74
74
  fields += tuple(named_fields.values())
75
75
 
76
76
  clone = self.values(*fields)
77
- clone._iterable_class = ModelDictIterable # type: ignore[attr-defined]
77
+ clone._iterable_class = ModelDictIterable # type: ignore[assignment]
78
78
 
79
79
  # QuerySet._hints is a dict object used by db router
80
80
  # to aid deciding which db should get a request. Currently
bananas/secrets.py CHANGED
@@ -1,7 +1,7 @@
1
1
  import os
2
- from typing import Optional
2
+ from typing import Final, Optional
3
3
 
4
- from typing_extensions import Final, overload
4
+ from typing_extensions import overload
5
5
 
6
6
  from .environment import env
7
7
 
bananas/url.py CHANGED
@@ -27,11 +27,9 @@ Currently supported engines are:
27
27
  You can add your own by running ``register(scheme, module_name)`` before
28
28
  parsing.
29
29
  """
30
- from typing import Any, Dict, List, Mapping, NamedTuple, Optional, Tuple, Union
30
+ from typing import Any, Dict, Final, List, Mapping, NamedTuple, Optional, Tuple, Union
31
31
  from urllib.parse import parse_qs, unquote_plus, urlsplit
32
32
 
33
- from typing_extensions import Final
34
-
35
33
 
36
34
  class Alias:
37
35
  """
@@ -97,8 +95,8 @@ def resolve(
97
95
  ), "Multiple levels of aliases are not supported"
98
96
 
99
97
  return result
100
- except KeyError:
101
- raise KeyError("No matches for engine %s" % key)
98
+ except KeyError as exc:
99
+ raise KeyError("No matches for engine %s" % key) from exc
102
100
 
103
101
 
104
102
  def get_engine(scheme: str) -> str:
@@ -128,12 +126,12 @@ def get_engine(scheme: str) -> str:
128
126
 
129
127
  try:
130
128
  engine, extra = engine
131
- except ValueError:
129
+ except ValueError as exc:
132
130
  # engine was not a list of length 2
133
131
  raise ValueError(
134
132
  "django-bananas.url' engine "
135
133
  "configuration is invalid: %r" % ENGINE_MAPPING
136
- )
134
+ ) from exc
137
135
 
138
136
  assert isinstance(
139
137
  extra, Mapping
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2014-2021 5 Monkeys
3
+ Copyright (c) 2014-2025 5 Monkeys
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,17 +1,25 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: django-bananas
3
- Version: 2.1
3
+ Version: 2.3
4
4
  Summary: Django Bananas - Django extensions the monkey way
5
5
  Home-page: https://github.com/5monkeys/django-bananas
6
6
  License: MIT License
7
7
  Classifier: Development Status :: 5 - Production/Stable
8
8
  Classifier: Programming Language :: Python
9
9
  Classifier: Programming Language :: Python :: 3
10
- Classifier: Programming Language :: Python :: 3.6
11
- Classifier: Programming Language :: Python :: 3.7
12
10
  Classifier: Programming Language :: Python :: 3.8
13
11
  Classifier: Programming Language :: Python :: 3.9
14
12
  Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Framework :: Django
17
+ Classifier: Framework :: Django :: 3.2
18
+ Classifier: Framework :: Django :: 4.0
19
+ Classifier: Framework :: Django :: 4.1
20
+ Classifier: Framework :: Django :: 4.2
21
+ Classifier: Framework :: Django :: 5.0
22
+ Classifier: Framework :: Django :: 5.1
15
23
  Classifier: Intended Audience :: Developers
16
24
  Classifier: License :: OSI Approved :: MIT License
17
25
  Classifier: Operating System :: OS Independent
@@ -19,21 +27,23 @@ Classifier: Framework :: Django
19
27
  Requires-Python: >=3.6
20
28
  Description-Content-Type: text/x-rst; charset=UTF-8
21
29
  License-File: LICENSE
22
- Requires-Dist: Django (>=2.2)
23
- Requires-Dist: typing-extensions (>=3.7.4.3)
24
- Requires-Dist: drf-yasg (>=1.20.0)
25
- Provides-Extra: dev
26
- Requires-Dist: mypy ; extra == 'dev'
27
- Requires-Dist: django-stubs ; extra == 'dev'
28
- Requires-Dist: djangorestframework-stubs ; extra == 'dev'
29
- Requires-Dist: pytest-mypy-plugins ; extra == 'dev'
30
- Requires-Dist: pre-commit ; extra == 'dev'
30
+ Requires-Dist: Django>=2.2
31
+ Requires-Dist: typing-extensions>=3.7.4.3
31
32
  Provides-Extra: drf
32
- Requires-Dist: djangorestframework (>=3.10) ; extra == 'drf'
33
- Requires-Dist: drf-yasg (>=1.20.0) ; extra == 'drf'
33
+ Requires-Dist: djangorestframework>=3.10; extra == "drf"
34
+ Requires-Dist: drf-yasg>=1.20.0; extra == "drf"
34
35
  Provides-Extra: test
35
- Requires-Dist: tox ; extra == 'test'
36
- Requires-Dist: coverage ; extra == 'test'
36
+ Requires-Dist: tox; extra == "test"
37
+ Requires-Dist: coverage[toml]; extra == "test"
38
+ Provides-Extra: dev
39
+ Requires-Dist: mypy; extra == "dev"
40
+ Requires-Dist: types-setuptools; extra == "dev"
41
+ Requires-Dist: django-stubs; extra == "dev"
42
+ Requires-Dist: djangorestframework-stubs; extra == "dev"
43
+ Requires-Dist: pytest; extra == "dev"
44
+ Requires-Dist: pytest-cov; extra == "dev"
45
+ Requires-Dist: pytest-django; extra == "dev"
46
+ Requires-Dist: pre-commit; extra == "dev"
37
47
 
38
48
  ================================================================================
39
49
  :banana: Django Bananas - Django extensions the monkey way
@@ -69,9 +79,12 @@ with the ``drf`` extra to keep those in sync:
69
79
 
70
80
  Currently tested only for
71
81
 
72
- - Django 2.2 under Python 3.7-3.9
73
- - Django 3.2 under Python 3.7-3.9
82
+ - Django 3.2 under Python 3.8-3.10
74
83
  - Django 4.0 under Python 3.8-3.10
84
+ - Django 4.1 under Python 3.8-3.13
85
+ - Django 4.2 under Python 3.8-3.13
86
+ - Django 5.0 under Python 3.10-3.13
87
+ - Django 5.1 under Python 3.10-3.13
75
88
 
76
89
  Pull requests welcome!
77
90
 
@@ -274,7 +287,6 @@ feature requires installation with the ``drf`` extra.
274
287
 
275
288
 
276
289
  class CustomAdminAPI(BananasAdminAPI):
277
-
278
290
  name = lazy_title(_("custom"))
279
291
 
280
292
  @schema(query_serializer=SomeSerializer, responses={200: SomeSerializer})
@@ -283,7 +295,6 @@ feature requires installation with the ``drf`` extra.
283
295
 
284
296
 
285
297
  class SomeModelAdminAPI(BananasAPI, viewsets.ModelViewSet):
286
-
287
298
  serializer_class = SomeModelSerializer
288
299
 
289
300
  def list(self, request):
@@ -592,4 +603,4 @@ and select specific tests with the ``test`` argument to ``make test``:
592
603
 
593
604
  .. code-block:: bash
594
605
 
595
- make test test='tests.test_admin.APITest.test_logout'
606
+ make test test='-k test_logout'
@@ -1,37 +1,37 @@
1
- bananas/__init__.py,sha256=jvyZDszPjJoAvEieSuSyLq-Zp-o8mFBMqhgb4c3hZVo,686
2
- bananas/environment.py,sha256=oBxSsFEMeTsizpe_qB3kkTOIvkxqImmRiv1WhZGHw4I,7874
1
+ bananas/__init__.py,sha256=UQwvaK8O20FU-3GqsRhVtGqD_eQhKmZy3qq6HJQ-q6o,686
2
+ bananas/environment.py,sha256=5vH_ujuZczzzkVLgEXDEB1T_qIQOMKw5X6NFBeQv-Wk,7844
3
3
  bananas/lazy.py,sha256=XFaqACJ6K5s_MZcxK_5OLQU35F7c7jB3KsdAtHuYQ6k,140
4
- bananas/models.py,sha256=V0XQwgqWe6CAD2MDP4qN9Hx0B7wVHHRZMF41-4yx7Ws,8877
4
+ bananas/models.py,sha256=HFjwheewWCmOBlp4uK8e3UyxCc50Rfx7DO5yIFct2pQ,8801
5
5
  bananas/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- bananas/query.py,sha256=2xfM1I2-kX3ZFnBwbEmsHoS2loQ6-e3_qRAwRrV8Z3w,3571
7
- bananas/secrets.py,sha256=JlTM2mecnvwRmUANTbxMgtyTnRoW3TCrb5uY9KFuW24,1029
6
+ bananas/query.py,sha256=ZtLtJZZVt5_JUDaFxkstpwaHNnIcjfDM98EmCOCuzJY,3569
7
+ bananas/secrets.py,sha256=h6E98SDIFx2kj3dOHdNox2hX9PZF6kpFNTmJzsw8IpU,1029
8
8
  bananas/settings.py,sha256=HGZBxm5BMwHJc9PpQpvkzAuasXvePa-q_4tgeup9DPA,139
9
- bananas/url.py,sha256=SlnGoHRGnh4j6lvGuNniVz7U5eFYMyDlN_9PWvQX9mQ,7904
9
+ bananas/url.py,sha256=afaLJToWVjSHHEnCPA8LpAoHGLMo32R0DEq9_RvS6CU,7906
10
10
  bananas/admin/__init__.py,sha256=0Dzu51GjYjbpnNf5ByaKTx5xiYZ0vxfhW1EssFuiQ90,173
11
- bananas/admin/extension.py,sha256=N58PJ5XDvCd15Ux_7RlrLF3UoLCMS8LQjE65QBkmZn0,13597
11
+ bananas/admin/extension.py,sha256=Q1_AXXOHIenTdc0WlYZ3zx5lUU7FZA9Z9YsGuG8xaBE,13617
12
+ bananas/admin/i18n.py,sha256=WxK_LKnPjoFgoNiNuZdr5wk3LoMXdu7acaIsZNsOxc0,301
12
13
  bananas/admin/api/__init__.py,sha256=86dXxHQKnQk-2ZOT2znTBasX-k7nPKlxslwYVZlzu4g,54
13
- bananas/admin/api/i18n.py,sha256=ZcZj3PeP2q4O5uZisK7Qk9TMVA6MQKCE2RKMZSwFHCU,302
14
- bananas/admin/api/mixins.py,sha256=F2-DPkVCqgtqMNZT8jeP_ODm5TjhgzSUKmDd046yicA,5350
14
+ bananas/admin/api/mixins.py,sha256=NroiouccAD6MORoFdS-I3Xnjsmm69_JhdQRT5Roe240,5287
15
15
  bananas/admin/api/permissions.py,sha256=sIIHfuJERYvdSJQ8YwNV4W3tj1DPlICi1snAe5RGq2g,375
16
16
  bananas/admin/api/router.py,sha256=VWn6O97sbsoPZtKPLPxPDn_raO7Gy8B62N1s7-mGHhU,570
17
17
  bananas/admin/api/serializers.py,sha256=XziZ1-6h4hn1qgts3qB8W18ctpl6h8vP3dkqa7WxLe8,2964
18
18
  bananas/admin/api/urls.py,sha256=fch3C5BhI6d12U9un8I28fFiHZWTWFH5XAhAzXPURxY,398
19
- bananas/admin/api/versioning.py,sha256=ATCoCf1uuY69lse-c0qNaBUOrQGIadAMk16UslyeUgo,882
20
- bananas/admin/api/views.py,sha256=QVQz1XuNQyOzlfQPoJPSclufUUKp5INXqcX19L1mJ1g,3971
19
+ bananas/admin/api/versioning.py,sha256=ciyelTzGZtJDPnigRZAJ0Gk1ZHfGxM9FXctS-OoyJ4Y,901
20
+ bananas/admin/api/views.py,sha256=lJpBU5fRISxO1F8NXChSqZ0_0BBg-6YG_7o90Kg7BWs,4247
21
21
  bananas/admin/api/schemas/__init__.py,sha256=n4vZ3Sd1H23krxIIUWvktHimmdfh1n0K-J4z2-QsZxI,336
22
- bananas/admin/api/schemas/base.py,sha256=EjYWpLmb1q1y2qbkhNcAFaiq-acT07PsVn89IKAIdp8,433
22
+ bananas/admin/api/schemas/base.py,sha256=XFfSQE_VeUraB_LIge1xEtVYOaTGfx6aDq6eahd6D2o,449
23
23
  bananas/admin/api/schemas/decorators.py,sha256=Zwp7AwV_6Y0iWVcsO8VjNHHttYMcdGnrfr7ElwEzJzE,492
24
- bananas/admin/api/schemas/yasg.py,sha256=Af5QKnc-UUMP6AqdGBgcVyfERw_70WnNs-Ho1LUCUzc,6019
24
+ bananas/admin/api/schemas/yasg.py,sha256=FpdsUSeuOuH_Rgm_C0H4Q3Tb6rG3McvJ7B43R1ALfbw,6036
25
25
  bananas/admin/api/v1_0/__init__.py,sha256=1M909E2Ei8-GI0Xox9t5NfO3xpPG2F13znjlx_jXGZc,59
26
- bananas/admin/api/v1_0/urls.py,sha256=nfH-6G3qzg3e0AJXxZLfEV0zyXqkNh8fJw_Tb5ALDpg,664
26
+ bananas/admin/api/v1_0/urls.py,sha256=8N-fFqycB-SnzTMQQk-TTM1b7GuRGXlPtnRFyv6J2rk,695
27
27
  bananas/drf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
28
  bananas/drf/errors.py,sha256=JDZYblAio27rh-nAOtC_4CoVw_lFYIdmmd4bTrgxaQc,427
29
- bananas/drf/fencing.py,sha256=WJ_Hw5XgI3XrianquYO2ggtAB9tyRFqeCqm_7-0OTzM,7159
30
- bananas/drf/utils.py,sha256=cHLKsWl_yjpR3lO0gPvapKXg9M5_JpJEKi5wcswpwJg,1792
29
+ bananas/drf/fencing.py,sha256=5DPQTS_NUTHiCIwQbQV927v6UcRrWbtOr7tg4e3Sj3w,7278
30
+ bananas/drf/utils.py,sha256=Cg7oBNNPuFwIHwHldSk3za87oesK6sivCbiRWP4zj0s,1824
31
31
  bananas/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
32
  bananas/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
- bananas/management/commands/show_urls.py,sha256=Ng63v7YM5tw1M00mzJc6xdlcCS0w7NMg2TFG1djLGRU,2220
34
- bananas/management/commands/syncpermissions.py,sha256=EBADBkFf6AMMoNSXUrNonG2Lt31nKqHh8erT3Lk0zuY,1496
33
+ bananas/management/commands/show_urls.py,sha256=X6ipP04XN1pdrT2Ne5ozbMx068SQ8Io-HpO6Xi7v7d0,2220
34
+ bananas/management/commands/syncpermissions.py,sha256=fmsJpikAPAwvK8Yfkj4A53fdUOz-y0rHEF7xsRiy8W0,1565
35
35
  bananas/static/admin/bananas/css/bananas.css,sha256=5WOuhE-vhrqKaXuwat56DTq7MPb79q7ajym0Nx-3vcU,17740
36
36
  bananas/static/admin/bananas/css/banansive.css,sha256=u9aYmL_ccDO9rDevOtckqhERDNs0HSazQ-4HJ9TukXI,3783
37
37
  bananas/static/admin/bananas/img/django.svg,sha256=4S1Cjntojkcoj17oTNufVtnlflhoKsRuQ9O7hTqyx6Q,5291
@@ -40,8 +40,8 @@ bananas/static/admin/bananas/js/bananas.js,sha256=1Xv6JEiTZYeaJSTxkKyMLwIRJ40SnR
40
40
  bananas/static/admin/css/responsive.css,sha256=UAMABM7h5rRZ8FocJw4bfx36Q5XtIDm4ddXN8lMC4SQ,17894
41
41
  bananas/templates/admin/base_site.html,sha256=Hq4n99zOuGnzsHl1v1xmYI6uFKk1oaDVcY_bsaMFlMA,7793
42
42
  bananas/templates/admin/view.html,sha256=WezwD9qGPch5S29intLVzbY1zVRop4zdIdm4pf7usVw,114
43
- django_bananas-2.1.dist-info/LICENSE,sha256=bsI-BPMDuol2ZykVXKKQiwufT0JexcSLsU2Nlx3D-zc,1081
44
- django_bananas-2.1.dist-info/METADATA,sha256=vnPHeyIiy_b6Qr1oEpVXnEJoQSrs8FYSXtUoA98Rk9o,18099
45
- django_bananas-2.1.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
46
- django_bananas-2.1.dist-info/top_level.txt,sha256=5JKVcC99qGcHeOm5WigbcrQbMcsWr6N2A0tF9ZoY6j8,8
47
- django_bananas-2.1.dist-info/RECORD,,
43
+ django_bananas-2.3.dist-info/LICENSE,sha256=ezKzVU8KUSkiuP6I5I1PEfOUn33aerXQ_fU8_4Qavos,1081
44
+ django_bananas-2.3.dist-info/METADATA,sha256=hPzC3Q9W_uWl9HJidDCGa4tDxWC31JhPAwvBCahFDXA,18581
45
+ django_bananas-2.3.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
46
+ django_bananas-2.3.dist-info/top_level.txt,sha256=5JKVcC99qGcHeOm5WigbcrQbMcsWr6N2A0tF9ZoY6j8,8
47
+ django_bananas-2.3.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.37.1)
2
+ Generator: setuptools (76.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5