django-bananas 2.3__py3-none-any.whl → 3.0__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, 3, 0, "final", 0)
1
+ VERSION = (3, 0, 0, "final", 0)
2
2
 
3
3
 
4
4
  def get_version() -> str:
@@ -114,7 +114,7 @@ class ChangePasswordAPI(BananasAdminAPI):
114
114
  raise serializers.ValidationError(password_form.errors) # type: ignore[arg-type]
115
115
 
116
116
  password_form.save()
117
- update_session_auth_hash(request, password_form.user) # type: ignore[arg-type]
117
+ update_session_auth_hash(request, password_form.user)
118
118
 
119
119
  return Response(status=status.HTTP_204_NO_CONTENT)
120
120
 
@@ -133,12 +133,12 @@ class ModelAdminView(ModelAdmin):
133
133
  return perm
134
134
 
135
135
  def has_module_permission(self, request: HttpRequest) -> bool:
136
- return request.user.has_perm(self.access_permission)
136
+ return bool(request.user.has_perm(self.access_permission))
137
137
 
138
138
  def has_change_permission(
139
139
  self, request: HttpRequest, obj: Optional[MT] = None
140
140
  ) -> bool:
141
- return request.user.has_perm(self.access_permission)
141
+ return bool(request.user.has_perm(self.access_permission))
142
142
 
143
143
  # TODO: Remove obj?
144
144
  def has_add_permission(
@@ -175,8 +175,7 @@ def register(
175
175
  *,
176
176
  admin_site: Optional[AdminSite] = None,
177
177
  admin_class: Type[ModelAdmin] = ModelAdminView,
178
- ) -> Type["AdminView"]:
179
- ...
178
+ ) -> Type["AdminView"]: ...
180
179
 
181
180
 
182
181
  # Call with parenthesis: @register()
@@ -186,8 +185,7 @@ def register(
186
185
  *,
187
186
  admin_site: Optional[AdminSite] = None,
188
187
  admin_class: Type[ModelAdmin] = ModelAdminView,
189
- ) -> Callable[[Type["AdminView"]], Type["AdminView"]]:
190
- ...
188
+ ) -> Callable[[Type["AdminView"]], Type["AdminView"]]: ...
191
189
 
192
190
 
193
191
  def register(
@@ -236,7 +234,7 @@ def register(
236
234
  inner_view.verbose_name = verbose_name
237
235
 
238
236
  access_perm_codename = "can_access_" + model_name.lower()
239
- access_perm_name = _("Can access {verbose_name}").format(
237
+ access_perm_name = str(_("Can access {verbose_name}")).format(
240
238
  verbose_name=verbose_name
241
239
  )
242
240
  # The first permission here is expected to be
@@ -387,7 +385,7 @@ class AdminView(View):
387
385
 
388
386
  def has_permission(self, perm: str) -> bool:
389
387
  perm = self.get_permission(perm)
390
- return self.request.user.has_perm(perm)
388
+ return bool(self.request.user.has_perm(perm))
391
389
 
392
390
  def has_access(self) -> bool:
393
391
  assert self.admin is not None
bananas/drf/fencing.py CHANGED
@@ -106,15 +106,13 @@ _MT_co = TypeVar("_MT_co", bound=Model, covariant=True)
106
106
 
107
107
 
108
108
  class UsesQuerySet(Protocol[_MT_co]):
109
- def get_queryset(self) -> "QuerySet[_MT_co]":
110
- ...
109
+ def get_queryset(self) -> "QuerySet[_MT_co]": ...
111
110
 
112
111
 
113
112
  class FencedUpdateModelMixin(UpdateModelMixin, abc.ABC):
114
113
  @property
115
114
  @abc.abstractmethod
116
- def fence(self) -> Fence:
117
- ...
115
+ def fence(self) -> Fence: ...
118
116
 
119
117
  # django-restframework uses an "advanced self-type" on self in
120
118
  # perform_update() which subtly breaks subclassing. We try to remedy this by
@@ -197,7 +195,7 @@ T = TypeVar("T")
197
195
 
198
196
 
199
197
  def as_set(
200
- fn: Callable[[InstanceType], Optional[T]]
198
+ fn: Callable[[InstanceType], Optional[T]],
201
199
  ) -> Callable[[InstanceType], Optional[FrozenSet[T]]]:
202
200
  @wraps(fn)
203
201
  def wrapper(instance: InstanceType) -> Optional[FrozenSet[T]]:
@@ -208,7 +206,7 @@ def as_set(
208
206
 
209
207
 
210
208
  def allow_if_match(
211
- version_getter: Callable[[InstanceType], Optional[str]]
209
+ version_getter: Callable[[InstanceType], Optional[str]],
212
210
  ) -> Fence[InstanceType, FrozenSet[str]]:
213
211
  return Fence(
214
212
  get_token=header_etag_parser("If-Match"),
bananas/environment.py CHANGED
@@ -27,8 +27,7 @@ __all__ = ["env", "parse_bool", "parse_int", "parse_tuple", "parse_list", "parse
27
27
  log = logging.getLogger(__name__)
28
28
 
29
29
 
30
- class Undefined:
31
- ...
30
+ class Undefined: ...
32
31
 
33
32
 
34
33
  UNDEFINED: Final = Undefined()
@@ -95,12 +94,10 @@ Q = TypeVar("Q", covariant=True)
95
94
 
96
95
 
97
96
  class _Instantiable(Protocol[Q]):
98
- def __init__(self, value: Iterable[Q]) -> None:
99
- ...
97
+ def __init__(self, value: Iterable[Q]) -> None: ...
100
98
 
101
99
 
102
- class _InstantiableIterable(Iterable[Q], _Instantiable[Q], Generic[Q]):
103
- ...
100
+ class _InstantiableIterable(Iterable[Q], _Instantiable[Q], Generic[Q]): ...
104
101
 
105
102
 
106
103
  T = TypeVar("T", bound=_InstantiableIterable)
@@ -128,23 +125,19 @@ P = TypeVar("P", bound=Union[Builtin, tuple, list, set])
128
125
 
129
126
 
130
127
  @overload
131
- def get_parser(typ: Type[B]) -> Callable[[str], B]:
132
- ...
128
+ def get_parser(typ: Type[B]) -> Callable[[str], B]: ...
133
129
 
134
130
 
135
131
  @overload
136
- def get_parser(typ: Type[tuple]) -> Callable[[str], Tuple[str, ...]]:
137
- ...
132
+ def get_parser(typ: Type[tuple]) -> Callable[[str], Tuple[str, ...]]: ...
138
133
 
139
134
 
140
135
  @overload
141
- def get_parser(typ: Type[list]) -> Callable[[str], List[str]]:
142
- ...
136
+ def get_parser(typ: Type[list]) -> Callable[[str], List[str]]: ...
143
137
 
144
138
 
145
139
  @overload
146
- def get_parser(typ: Type[set]) -> Callable[[str], Set[str]]:
147
- ...
140
+ def get_parser(typ: Type[set]) -> Callable[[str], Set[str]]: ...
148
141
 
149
142
 
150
143
  def get_parser(typ: Type[P]) -> Callable[[str], P]:
@@ -238,12 +231,12 @@ class EnvironWrapper:
238
231
  get: Callable[..., str]
239
232
 
240
233
  @overload
241
- def parse(self, parser: Callable[[str], S], key: str, default: None) -> Optional[S]:
242
- ...
234
+ def parse(
235
+ self, parser: Callable[[str], S], key: str, default: None
236
+ ) -> Optional[S]: ...
243
237
 
244
238
  @overload
245
- def parse(self, parser: Callable[[str], S], key: str, default: S) -> S:
246
- ...
239
+ def parse(self, parser: Callable[[str], S], key: str, default: S) -> S: ...
247
240
 
248
241
  def parse(
249
242
  self, parser: Callable[[str], S], key: str, default: Optional[S] = None
@@ -258,56 +251,48 @@ class EnvironWrapper:
258
251
  return default
259
252
 
260
253
  @overload
261
- def get_bool(self, key: str, default: U) -> Union[bool, U]:
262
- ...
254
+ def get_bool(self, key: str, default: U) -> Union[bool, U]: ...
263
255
 
264
256
  @overload
265
- def get_bool(self, key: str, default: None = None) -> Optional[bool]:
266
- ...
257
+ def get_bool(self, key: str, default: None = None) -> Optional[bool]: ...
267
258
 
268
259
  def get_bool(self, key: str, default: object = None) -> object:
269
260
  return self.parse(parse_bool, key, default=default)
270
261
 
271
262
  @overload
272
- def get_int(self, key: str, default: U) -> Union[int, U]:
273
- ...
263
+ def get_int(self, key: str, default: U) -> Union[int, U]: ...
274
264
 
275
265
  @overload
276
- def get_int(self, key: str, default: None = None) -> Optional[int]:
277
- ...
266
+ def get_int(self, key: str, default: None = None) -> Optional[int]: ...
278
267
 
279
268
  def get_int(self, key: str, default: object = None) -> object:
280
269
  return self.parse(parse_int, key, default=default)
281
270
 
282
271
  @overload
283
- def get_tuple(self, key: str, default: U) -> Union[Tuple[str, ...], U]:
284
- ...
272
+ def get_tuple(self, key: str, default: U) -> Union[Tuple[str, ...], U]: ...
285
273
 
286
274
  @overload
287
- def get_tuple(self, key: str, default: None = None) -> Optional[Tuple[str, ...]]:
288
- ...
275
+ def get_tuple(
276
+ self, key: str, default: None = None
277
+ ) -> Optional[Tuple[str, ...]]: ...
289
278
 
290
279
  def get_tuple(self, key: str, default: object = None) -> object:
291
280
  return self.parse(parse_tuple, key, default=default)
292
281
 
293
282
  @overload
294
- def get_list(self, key: str, default: U) -> Union[List[str], U]:
295
- ...
283
+ def get_list(self, key: str, default: U) -> Union[List[str], U]: ...
296
284
 
297
285
  @overload
298
- def get_list(self, key: str, default: None = None) -> Optional[List[str]]:
299
- ...
286
+ def get_list(self, key: str, default: None = None) -> Optional[List[str]]: ...
300
287
 
301
288
  def get_list(self, key: str, default: object = None) -> object:
302
289
  return self.parse(parse_list, key, default=default)
303
290
 
304
291
  @overload
305
- def get_set(self, key: str, default: U) -> Union[Set[str], U]:
306
- ...
292
+ def get_set(self, key: str, default: U) -> Union[Set[str], U]: ...
307
293
 
308
294
  @overload
309
- def get_set(self, key: str, default: None = None) -> Optional[Set[str]]:
310
- ...
295
+ def get_set(self, key: str, default: None = None) -> Optional[Set[str]]: ...
311
296
 
312
297
  def get_set(self, key: str, default: object = None) -> object:
313
298
  return self.parse(parse_set, key, default=default)
bananas/models.py CHANGED
@@ -11,8 +11,7 @@ from django.db import models
11
11
  from django.utils.translation import gettext_lazy as _
12
12
 
13
13
 
14
- class Missing:
15
- ...
14
+ class Missing: ...
16
15
 
17
16
 
18
17
  MISSING: Final = Missing()
bananas/query.py CHANGED
@@ -11,6 +11,7 @@ from typing import (
11
11
  Type,
12
12
  TypeVar,
13
13
  Union,
14
+ cast,
14
15
  )
15
16
 
16
17
  from django.db.models import Model
@@ -38,12 +39,16 @@ class ModelDictIterable:
38
39
  query = queryset.query
39
40
  compiler = query.get_compiler(queryset.db)
40
41
 
41
- field_names: List[str] = list(query.values_select)
42
- extra_names: List[str] = list(query.extra_select)
43
- annotation_names: List[str] = list(query.annotation_select)
42
+ if hasattr(query, "selected") and query.selected:
43
+ names = list(query.selected)
44
+ else:
45
+ extra_names: List[str] = list(query.extra_select)
46
+ field_names: List[str] = list(query.values_select)
47
+ annotation_names: List[str] = list(query.annotation_select)
48
+
49
+ # Modified super(); rename fields given in queryset.values() kwargs
50
+ names = extra_names + field_names + annotation_names
44
51
 
45
- # Modified super(); rename fields given in queryset.values() kwargs
46
- names = extra_names + field_names + annotation_names
47
52
  if self.named_fields:
48
53
  names = self.rename_fields(names)
49
54
 
@@ -62,8 +67,7 @@ _MT_co = TypeVar("_MT_co", bound=Model, covariant=True)
62
67
  class IsQuerySet(Protocol[_MT_co]):
63
68
  def values(
64
69
  self, *fields: Union[str, Combinable], **expressions: Any
65
- ) -> "_QuerySet[_MT_co, ModelDict]":
66
- ...
70
+ ) -> "_QuerySet[_MT_co, dict[str, Any]]": ...
67
71
 
68
72
 
69
73
  class ModelDictQuerySetMixin:
@@ -73,7 +77,7 @@ class ModelDictQuerySetMixin:
73
77
  if named_fields:
74
78
  fields += tuple(named_fields.values())
75
79
 
76
- clone = self.values(*fields)
80
+ clone = cast("_QuerySet[_MT_co, ModelDict]", self.values(*fields))
77
81
  clone._iterable_class = ModelDictIterable # type: ignore[assignment]
78
82
 
79
83
  # QuerySet._hints is a dict object used by db router
@@ -92,7 +96,7 @@ _MT = TypeVar("_MT", bound=Model)
92
96
 
93
97
  if TYPE_CHECKING:
94
98
 
95
- class ModelDictQuerySet( # type: ignore[misc]
99
+ class ModelDictQuerySet(
96
100
  ModelDictQuerySetMixin,
97
101
  QuerySet[_MT],
98
102
  IsQuerySet[_MT],
bananas/secrets.py CHANGED
@@ -9,13 +9,11 @@ BANANAS_SECRETS_DIR_ENV_KEY: Final = "BANANAS_SECRETS_DIR"
9
9
 
10
10
 
11
11
  @overload
12
- def get_secret(secret_name: str, default: str) -> str:
13
- ...
12
+ def get_secret(secret_name: str, default: str) -> str: ...
14
13
 
15
14
 
16
15
  @overload
17
- def get_secret(secret_name: str) -> Optional[str]:
18
- ...
16
+ def get_secret(secret_name: str) -> Optional[str]: ...
19
17
 
20
18
 
21
19
  def get_secret(secret_name: str, default: Optional[str] = None) -> Optional[str]:
@@ -75,6 +75,7 @@ body:not(.login) #header {
75
75
  }
76
76
 
77
77
  #branding {
78
+ width: 100%;
78
79
  float: none;
79
80
  }
80
81
 
@@ -113,6 +114,7 @@ body.login #branding a:active {
113
114
  }
114
115
 
115
116
  #branding a.logo {
117
+ width: 100%;
116
118
  padding-top: 15px;
117
119
  line-height: calc(70px - 15px * 2) /* fallback */;
118
120
  line-height: var(--topbar-height-inner);
@@ -144,8 +146,8 @@ body.login #branding a:active {
144
146
  height: 100%;
145
147
  }
146
148
  #header .searchable nav {
147
- height: calc(100% - 45px) /* fallback */;
148
- height: calc(100% - var(--searchbar-height));
149
+ height: calc(100% - 45px - 15px) /* fallback */;
150
+ height: calc(100% - var(--searchbar-height) - 15px);
149
151
  }
150
152
  #header nav > ul {
151
153
  overflow-y: auto;
@@ -171,7 +173,7 @@ body.login #branding a:active {
171
173
  margin-top: 15px;
172
174
  }
173
175
  #header .filtered-results nav > ul:first-of-type > li:first-of-type {
174
- margin-top: 30px;
176
+ margin-top: 15px;
175
177
  }
176
178
 
177
179
  #header nav > ul > li > a,
@@ -381,6 +383,11 @@ body.popup #title .back-arrow {
381
383
  background-color: transparent;
382
384
  }
383
385
 
386
+ #content h2:first-of-type {
387
+ font-size: 22px;
388
+ margin-top: 0;
389
+ }
390
+
384
391
  #content .object-tools {
385
392
  position: fixed;
386
393
  z-index: 3000;
@@ -527,6 +534,7 @@ input[type="button"]:disabled,
527
534
  .timelist a:hover,
528
535
  .timelist a:focus,
529
536
  .calendar td.selected a {
537
+ color: white;
530
538
  background-color: #417690 /* fallback */;
531
539
  background-color: var(--theme-color);
532
540
  background-image: linear-gradient(to bottom, rgba(255, 255, 255, 0.25), rgba(255, 255, 255, 0.25)) /* fallback */;
@@ -572,6 +580,8 @@ textarea:disabled {
572
580
  background: none;
573
581
  color: #333;
574
582
  font-size: 22px;
583
+ border: 0;
584
+ text-transform: unset;
575
585
  }
576
586
 
577
587
  .module > h2 a.section:link,
@@ -604,26 +614,6 @@ a.active.selector-clearall:hover {
604
614
  /* TODO: selector-icons.svg is hardcoded blue on hover */
605
615
  }
606
616
 
607
- @supports (background-blend-mode: overlay) {
608
- .datetimeshortcuts .date-icon,
609
- .datetimeshortcuts .clock-icon {
610
- background: none;
611
- background-color: #447e9b /* fallback */;
612
- background-color: var(--secondary-color);
613
- background-blend-mode: overlay;
614
- }
615
-
616
- .datetimeshortcuts .date-icon {
617
- -webkit-mask-image: url(../../img/icon-calendar.svg);
618
- mask-image: url(../../img/icon-calendar.svg);
619
- }
620
-
621
- .datetimeshortcuts .clock-icon {
622
- -webkit-mask-image: url(../../img/icon-clock.svg);
623
- mask-image: url(../../img/icon-clock.svg);
624
- }
625
- }
626
-
627
617
  .calendar caption,
628
618
  .calendarbox h2 {
629
619
  color: white;
@@ -634,12 +624,15 @@ a.active.selector-clearall:hover {
634
624
  filter: invert(1);
635
625
  }
636
626
 
637
- .calendarbox .calendarnav-previous {
638
- background-position: 0 -15px;
627
+ .calendar-cancel a {
628
+ color: var(--button-fg);
639
629
  }
640
630
 
641
- .calendarbox .calendarnav-next {
642
- background-position: 0 -45px;
631
+ .clockbox h2 {
632
+ font-size: 1.1em;
633
+ color: white;
634
+ background: var(--theme-color);
635
+ background-image: var(--bg-lighten-25);
643
636
  }
644
637
 
645
638
  #header #changelist-filter,
@@ -685,6 +678,21 @@ a.active.selector-clearall:hover {
685
678
  margin: 0;
686
679
  }
687
680
 
681
+ #header #changelist-filter h3 .viewlink {
682
+ background: none;
683
+ }
684
+ #header #changelist-filter h3 .viewlink:hover {
685
+ color: var(--lighten-75);
686
+ }
687
+
688
+ #changelist-filter #changelist-filter-extra-actions {
689
+ border-bottom: 0;
690
+ }
691
+
692
+ #changelist-filter details[open] > summary::before {
693
+ color: var(--lighten-25);
694
+ }
695
+
688
696
  #header #object-tools h2 {
689
697
  margin: 0;
690
698
  }
@@ -735,6 +743,11 @@ body:not(.popup) #changelist #toolbar {
735
743
  margin-right: 0;
736
744
  }
737
745
 
746
+ .nav-global #toolbar {
747
+ border: 0;
748
+ margin-bottom: 25px;
749
+ }
750
+
738
751
  .nav-global #toolbar form {
739
752
  display: block;
740
753
  }
@@ -764,7 +777,9 @@ body:not(.popup) #changelist #toolbar {
764
777
  background-image: var(--bg-lighten-33);
765
778
  color: #fff;
766
779
  line-height: 25px;
767
- height: 25px;
780
+ height: 45px;
781
+ padding: 10px 0;
782
+ box-sizing: border-box;
768
783
  }
769
784
 
770
785
  #changelist .results ~ .actions {
@@ -778,6 +793,7 @@ body:not(.popup) #changelist #toolbar {
778
793
  }
779
794
 
780
795
  #changelist .actions label {
796
+ padding-left: 10px;
781
797
  vertical-align: top;
782
798
  }
783
799
 
@@ -806,3 +822,7 @@ body:not(.popup) #changelist #toolbar {
806
822
  #changelist .actions span.question {
807
823
  margin: 0 0 0 10px;
808
824
  }
825
+
826
+ .selector-chosen-title {
827
+ background: var(--theme-color);
828
+ }
@@ -48,17 +48,20 @@ body:not(.popup) #header #object-tools {
48
48
  padding: 0;
49
49
  border-radius: 0;
50
50
  border: 0;
51
- background: #417690 /* fallback */;
52
- background: var(--theme-color);
53
- background-image: url(../../img/search.svg), linear-gradient(to bottom, rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.15));
51
+ background: white;
52
+ background-image: url(../../img/search.svg);
54
53
  background-position: center center;
55
54
  background-repeat: no-repeat;
56
55
  box-shadow: none;
57
56
  text-indent: -20000px;
58
57
  }
59
58
 
59
+ #changelist .changelist-form-container:has(#changelist-filter) > div {
60
+ max-width: 100%;
61
+ }
62
+
60
63
  #header #changelist-search input[type="submit"]:hover {
61
- background-image: url(../../img/search.svg), linear-gradient(to bottom, rgba(0, 0, 0, 0.35), rgba(0, 0, 0, 0.35));
64
+ background-image: url(../../img/search.svg), linear-gradient(to bottom, rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.15));
62
65
  }
63
66
 
64
67
  #changelist-search > div {
@@ -123,6 +126,12 @@ body:not(.popup) #header #object-tools {
123
126
  transform: translateX(var(--sidebar-width));
124
127
  }
125
128
 
129
+ html.is-sidebarOpen #header nav {
130
+ position: unset;
131
+ width: var(--sidebar-width);
132
+ transform: translateY(-15px);
133
+ }
134
+
126
135
  body:not(.popup) #content .object-tools {
127
136
  display: none;
128
137
  }
@@ -140,6 +149,14 @@ body:not(.popup) #header #object-tools {
140
149
  margin-right: 0;
141
150
  }
142
151
 
152
+ #changelist > .changelist-form-container {
153
+ max-width: 100vw;
154
+ }
155
+
156
+ html.is-sidebarOpen #changelist > .changelist-form-container {
157
+ max-width: calc(100vw - var(--sidebar-width));
158
+ }
159
+
143
160
  #changelist .actions {
144
161
  padding: 10px;
145
162
  left: 0;
@@ -120,7 +120,10 @@
120
120
  {% endif %}
121
121
 
122
122
  <li>
123
- <a href="{% url 'admin:logout' %}">
123
+ <form id="logout-form" method="post" action="{% url 'admin:logout' %}">
124
+ {% csrf_token %}
125
+ </form>
126
+ <a href="" onclick="document.getElementById('logout-form').submit();">
124
127
  {% trans 'Log out' %}
125
128
  </a>
126
129
  </li>
bananas/url.py CHANGED
@@ -27,6 +27,7 @@ Currently supported engines are:
27
27
  You can add your own by running ``register(scheme, module_name)`` before
28
28
  parsing.
29
29
  """
30
+
30
31
  from typing import Any, Dict, Final, List, Mapping, NamedTuple, Optional, Tuple, Union
31
32
  from urllib.parse import parse_qs, unquote_plus, urlsplit
32
33
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: django-bananas
3
- Version: 2.3
3
+ Version: 3.0
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
@@ -13,21 +13,21 @@ Classifier: Programming Language :: Python :: 3.10
13
13
  Classifier: Programming Language :: Python :: 3.11
14
14
  Classifier: Programming Language :: Python :: 3.12
15
15
  Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
16
17
  Classifier: Framework :: Django
17
- Classifier: Framework :: Django :: 3.2
18
- Classifier: Framework :: Django :: 4.0
19
- Classifier: Framework :: Django :: 4.1
20
18
  Classifier: Framework :: Django :: 4.2
21
19
  Classifier: Framework :: Django :: 5.0
22
20
  Classifier: Framework :: Django :: 5.1
21
+ Classifier: Framework :: Django :: 5.2
22
+ Classifier: Framework :: Django :: 6.0
23
23
  Classifier: Intended Audience :: Developers
24
24
  Classifier: License :: OSI Approved :: MIT License
25
25
  Classifier: Operating System :: OS Independent
26
26
  Classifier: Framework :: Django
27
- Requires-Python: >=3.6
27
+ Requires-Python: >=3.8
28
28
  Description-Content-Type: text/x-rst; charset=UTF-8
29
29
  License-File: LICENSE
30
- Requires-Dist: Django>=2.2
30
+ Requires-Dist: Django>=4.2
31
31
  Requires-Dist: typing-extensions>=3.7.4.3
32
32
  Provides-Extra: drf
33
33
  Requires-Dist: djangorestframework>=3.10; extra == "drf"
@@ -44,6 +44,7 @@ Requires-Dist: pytest; extra == "dev"
44
44
  Requires-Dist: pytest-cov; extra == "dev"
45
45
  Requires-Dist: pytest-django; extra == "dev"
46
46
  Requires-Dist: pre-commit; extra == "dev"
47
+ Dynamic: license-file
47
48
 
48
49
  ================================================================================
49
50
  :banana: Django Bananas - Django extensions the monkey way
@@ -79,12 +80,11 @@ with the ``drf`` extra to keep those in sync:
79
80
 
80
81
  Currently tested only for
81
82
 
82
- - Django 3.2 under Python 3.8-3.10
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
83
+ - Django 4.2 under Python 3.8-3.12
86
84
  - Django 5.0 under Python 3.10-3.13
87
85
  - Django 5.1 under Python 3.10-3.13
86
+ - Django 5.2 under Python 3.10-3.14
87
+ - Django 6.0 under Python 3.12-3.14
88
88
 
89
89
  Pull requests welcome!
90
90
 
@@ -575,12 +575,12 @@ Testing and development requirements can be installed using package extras
575
575
  ``drf`` extra when installing ``dev``.
576
576
 
577
577
  To get started, setup a virtualenv and then install test requirements and run
578
- tests and checks on Python 3.9/Django 3.1 with:
578
+ tests and typehints on Python 3.14/Django 6.0 with:
579
579
 
580
580
  .. code-block:: bash
581
581
 
582
582
  python3 -m pip install -e .[test]
583
- TOXENV=py39-django31,checks python3 -m tox
583
+ TOXENV=py314-django60,type-check python3 -m tox
584
584
 
585
585
  You can install development requirements into your virtualenv. Linting and
586
586
  formatting uses pre-commit which you could also install on a system level.
@@ -1,14 +1,14 @@
1
- bananas/__init__.py,sha256=UQwvaK8O20FU-3GqsRhVtGqD_eQhKmZy3qq6HJQ-q6o,686
2
- bananas/environment.py,sha256=5vH_ujuZczzzkVLgEXDEB1T_qIQOMKw5X6NFBeQv-Wk,7844
1
+ bananas/__init__.py,sha256=yml9BS9jMFFoXxex1qXrBIwFeYdTP692zPJd-QQYXZY,686
2
+ bananas/environment.py,sha256=1UwUNjSbnLlWh0XN41_AdEc0TVXQbbswkM34KNPzNwk,7744
3
3
  bananas/lazy.py,sha256=XFaqACJ6K5s_MZcxK_5OLQU35F7c7jB3KsdAtHuYQ6k,140
4
- bananas/models.py,sha256=HFjwheewWCmOBlp4uK8e3UyxCc50Rfx7DO5yIFct2pQ,8801
4
+ bananas/models.py,sha256=upapLi3Zvk073G4oMXCobh7RRXDhBLQ0kjPTWWAeYCM,8797
5
5
  bananas/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- bananas/query.py,sha256=ZtLtJZZVt5_JUDaFxkstpwaHNnIcjfDM98EmCOCuzJY,3569
7
- bananas/secrets.py,sha256=h6E98SDIFx2kj3dOHdNox2hX9PZF6kpFNTmJzsw8IpU,1029
6
+ bananas/query.py,sha256=DJeuDY41JRX_45r3hS-1XBN9wThm4f-8PUh6yy8-N5k,3726
7
+ bananas/secrets.py,sha256=WDBZapuPzDGHUwO1J_UjFuoljFkgbLKYy0uFTIl53Fk,1021
8
8
  bananas/settings.py,sha256=HGZBxm5BMwHJc9PpQpvkzAuasXvePa-q_4tgeup9DPA,139
9
- bananas/url.py,sha256=afaLJToWVjSHHEnCPA8LpAoHGLMo32R0DEq9_RvS6CU,7906
9
+ bananas/url.py,sha256=63vR_bK0Py9X-8dA7seEPVrQaU8FRSIOLwUb8XIUDAk,7907
10
10
  bananas/admin/__init__.py,sha256=0Dzu51GjYjbpnNf5ByaKTx5xiYZ0vxfhW1EssFuiQ90,173
11
- bananas/admin/extension.py,sha256=Q1_AXXOHIenTdc0WlYZ3zx5lUU7FZA9Z9YsGuG8xaBE,13617
11
+ bananas/admin/extension.py,sha256=pfAjvKrGpnHZoX5YQSXM9-6GojFvLtUkwgN9OhHIekI,13632
12
12
  bananas/admin/i18n.py,sha256=WxK_LKnPjoFgoNiNuZdr5wk3LoMXdu7acaIsZNsOxc0,301
13
13
  bananas/admin/api/__init__.py,sha256=86dXxHQKnQk-2ZOT2znTBasX-k7nPKlxslwYVZlzu4g,54
14
14
  bananas/admin/api/mixins.py,sha256=NroiouccAD6MORoFdS-I3Xnjsmm69_JhdQRT5Roe240,5287
@@ -17,7 +17,7 @@ bananas/admin/api/router.py,sha256=VWn6O97sbsoPZtKPLPxPDn_raO7Gy8B62N1s7-mGHhU,5
17
17
  bananas/admin/api/serializers.py,sha256=XziZ1-6h4hn1qgts3qB8W18ctpl6h8vP3dkqa7WxLe8,2964
18
18
  bananas/admin/api/urls.py,sha256=fch3C5BhI6d12U9un8I28fFiHZWTWFH5XAhAzXPURxY,398
19
19
  bananas/admin/api/versioning.py,sha256=ciyelTzGZtJDPnigRZAJ0Gk1ZHfGxM9FXctS-OoyJ4Y,901
20
- bananas/admin/api/views.py,sha256=lJpBU5fRISxO1F8NXChSqZ0_0BBg-6YG_7o90Kg7BWs,4247
20
+ bananas/admin/api/views.py,sha256=2jVkpOMA2c6bNINGMM1JYWXmfyvuZqdmYHqsdBE1uzc,4221
21
21
  bananas/admin/api/schemas/__init__.py,sha256=n4vZ3Sd1H23krxIIUWvktHimmdfh1n0K-J4z2-QsZxI,336
22
22
  bananas/admin/api/schemas/base.py,sha256=XFfSQE_VeUraB_LIge1xEtVYOaTGfx6aDq6eahd6D2o,449
23
23
  bananas/admin/api/schemas/decorators.py,sha256=Zwp7AwV_6Y0iWVcsO8VjNHHttYMcdGnrfr7ElwEzJzE,492
@@ -26,22 +26,22 @@ bananas/admin/api/v1_0/__init__.py,sha256=1M909E2Ei8-GI0Xox9t5NfO3xpPG2F13znjlx_
26
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=5DPQTS_NUTHiCIwQbQV927v6UcRrWbtOr7tg4e3Sj3w,7278
29
+ bananas/drf/fencing.py,sha256=XyCNiPqx0eKMk1emjp7kvyvj7P9JvDArIWgKeQrCRD4,7264
30
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
33
  bananas/management/commands/show_urls.py,sha256=X6ipP04XN1pdrT2Ne5ozbMx068SQ8Io-HpO6Xi7v7d0,2220
34
34
  bananas/management/commands/syncpermissions.py,sha256=fmsJpikAPAwvK8Yfkj4A53fdUOz-y0rHEF7xsRiy8W0,1565
35
- bananas/static/admin/bananas/css/bananas.css,sha256=5WOuhE-vhrqKaXuwat56DTq7MPb79q7ajym0Nx-3vcU,17740
36
- bananas/static/admin/bananas/css/banansive.css,sha256=u9aYmL_ccDO9rDevOtckqhERDNs0HSazQ-4HJ9TukXI,3783
35
+ bananas/static/admin/bananas/css/bananas.css,sha256=h7IuijjLYePQ5vwMWNP_Piyxn7IDBMWBM5Of0n6knNE,17882
36
+ bananas/static/admin/bananas/css/banansive.css,sha256=t23KL0bJEO_reIjJh6TsOq_FVYBf2DAT5lP5IkLYEh8,4077
37
37
  bananas/static/admin/bananas/img/django.svg,sha256=4S1Cjntojkcoj17oTNufVtnlflhoKsRuQ9O7hTqyx6Q,5291
38
38
  bananas/static/admin/bananas/img/search.svg,sha256=59UwyGYVJ4EJ26d5AAIdjtCHR3CnO94in6eQIbkYTu8,458
39
39
  bananas/static/admin/bananas/js/bananas.js,sha256=1Xv6JEiTZYeaJSTxkKyMLwIRJ40SnRDPi2N-prpWoU0,4596
40
40
  bananas/static/admin/css/responsive.css,sha256=UAMABM7h5rRZ8FocJw4bfx36Q5XtIDm4ddXN8lMC4SQ,17894
41
- bananas/templates/admin/base_site.html,sha256=Hq4n99zOuGnzsHl1v1xmYI6uFKk1oaDVcY_bsaMFlMA,7793
41
+ bananas/templates/admin/base_site.html,sha256=RU67gxvx1ozx3-uCyXWf2jC9H6c4adm0BjwmKF8EHEo,7977
42
42
  bananas/templates/admin/view.html,sha256=WezwD9qGPch5S29intLVzbY1zVRop4zdIdm4pf7usVw,114
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,,
43
+ django_bananas-3.0.dist-info/licenses/LICENSE,sha256=ezKzVU8KUSkiuP6I5I1PEfOUn33aerXQ_fU8_4Qavos,1081
44
+ django_bananas-3.0.dist-info/METADATA,sha256=DTJIaBDEiXx8bXU14ta7Knj3juyy_TEfiMp5rizlDVo,18589
45
+ django_bananas-3.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
46
+ django_bananas-3.0.dist-info/top_level.txt,sha256=5JKVcC99qGcHeOm5WigbcrQbMcsWr6N2A0tF9ZoY6j8,8
47
+ django_bananas-3.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (76.0.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5