django-admin-mobile 0.2.4__tar.gz → 0.2.7__tar.gz

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.
Files changed (178) hide show
  1. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/CHANGELOG.md +16 -0
  2. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/PKG-INFO +77 -38
  3. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/README.md +76 -37
  4. django_admin_mobile-0.2.7/docs/hero.png +0 -0
  5. django_admin_mobile-0.2.7/docs/screenshots/app-index-375-dark.png +0 -0
  6. django_admin_mobile-0.2.7/docs/screenshots/app-index-375.png +0 -0
  7. django_admin_mobile-0.2.7/docs/screenshots/bulk-375-dark.png +0 -0
  8. django_admin_mobile-0.2.7/docs/screenshots/bulk-375.png +0 -0
  9. django_admin_mobile-0.2.7/docs/screenshots/change-form-375-dark.png +0 -0
  10. django_admin_mobile-0.2.7/docs/screenshots/change-form-375.png +0 -0
  11. django_admin_mobile-0.2.7/docs/screenshots/changelist-375-dark.png +0 -0
  12. django_admin_mobile-0.2.7/docs/screenshots/changelist-375.png +0 -0
  13. django_admin_mobile-0.2.7/docs/screenshots/changelist-editable-375-dark.png +0 -0
  14. django_admin_mobile-0.2.7/docs/screenshots/changelist-editable-375.png +0 -0
  15. django_admin_mobile-0.2.7/docs/screenshots/favorites-375-dark.png +0 -0
  16. django_admin_mobile-0.2.7/docs/screenshots/favorites-375.png +0 -0
  17. django_admin_mobile-0.2.7/docs/screenshots/filters-375-dark.png +0 -0
  18. django_admin_mobile-0.2.7/docs/screenshots/filters-375.png +0 -0
  19. django_admin_mobile-0.2.7/docs/screenshots/history-375-dark.png +0 -0
  20. django_admin_mobile-0.2.7/docs/screenshots/history-375.png +0 -0
  21. django_admin_mobile-0.2.7/docs/screenshots/infinite-scroll-375-dark.png +0 -0
  22. django_admin_mobile-0.2.7/docs/screenshots/infinite-scroll-375.png +0 -0
  23. django_admin_mobile-0.2.7/docs/screenshots/message-375-dark.png +0 -0
  24. django_admin_mobile-0.2.7/docs/screenshots/message-375.png +0 -0
  25. django_admin_mobile-0.2.7/docs/screenshots/nav-sidebar-375-dark.png +0 -0
  26. django_admin_mobile-0.2.7/docs/screenshots/nav-sidebar-375.png +0 -0
  27. django_admin_mobile-0.2.7/docs/screenshots/payments-375-dark.png +0 -0
  28. django_admin_mobile-0.2.7/docs/screenshots/payments-375.png +0 -0
  29. django_admin_mobile-0.2.7/docs/screenshots/payments-selected-375-dark.png +0 -0
  30. django_admin_mobile-0.2.7/docs/screenshots/payments-selected-375.png +0 -0
  31. django_admin_mobile-0.2.7/docs/screenshots/pull-375-dark.png +0 -0
  32. django_admin_mobile-0.2.7/docs/screenshots/pull-375.png +0 -0
  33. django_admin_mobile-0.2.7/docs/screenshots/rtl-changelist-375-dark.png +0 -0
  34. django_admin_mobile-0.2.7/docs/screenshots/rtl-changelist-375.png +0 -0
  35. django_admin_mobile-0.2.7/docs/screenshots/stock-changelist-375-dark.png +0 -0
  36. django_admin_mobile-0.2.7/docs/screenshots/stock-changelist-375.png +0 -0
  37. django_admin_mobile-0.2.7/docs/screenshots/swipe-375-dark.png +0 -0
  38. django_admin_mobile-0.2.7/docs/screenshots/swipe-375.png +0 -0
  39. django_admin_mobile-0.2.7/docs/screenshots/toast-375-dark.png +0 -0
  40. django_admin_mobile-0.2.7/docs/screenshots/toast-375.png +0 -0
  41. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/example/library/admin.py +43 -0
  42. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/example/library/demo.py +12 -0
  43. django_admin_mobile-0.2.7/example/library/migrations/0004_payment.py +96 -0
  44. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/example/library/models.py +32 -0
  45. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/src/admin_mobile/__init__.py +1 -1
  46. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/src/admin_mobile/static/admin_mobile/css/admin_mobile.css +14 -6
  47. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/src/admin_mobile/static/admin_mobile/css/admin_mobile_features.css +507 -24
  48. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/src/admin_mobile/static/admin_mobile/js/admin_mobile.js +16 -0
  49. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/src/admin_mobile/static/admin_mobile/js/admin_mobile_features.js +186 -19
  50. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/tests/test_playwright.py +223 -3
  51. django_admin_mobile-0.2.4/docs/screenshots/app-index-375-dark.png +0 -0
  52. django_admin_mobile-0.2.4/docs/screenshots/app-index-375.png +0 -0
  53. django_admin_mobile-0.2.4/docs/screenshots/bulk-375-dark.png +0 -0
  54. django_admin_mobile-0.2.4/docs/screenshots/bulk-375.png +0 -0
  55. django_admin_mobile-0.2.4/docs/screenshots/change-form-375-dark.png +0 -0
  56. django_admin_mobile-0.2.4/docs/screenshots/change-form-375.png +0 -0
  57. django_admin_mobile-0.2.4/docs/screenshots/changelist-375-dark.png +0 -0
  58. django_admin_mobile-0.2.4/docs/screenshots/changelist-375.png +0 -0
  59. django_admin_mobile-0.2.4/docs/screenshots/changelist-editable-375-dark.png +0 -0
  60. django_admin_mobile-0.2.4/docs/screenshots/changelist-editable-375.png +0 -0
  61. django_admin_mobile-0.2.4/docs/screenshots/favorites-375-dark.png +0 -0
  62. django_admin_mobile-0.2.4/docs/screenshots/favorites-375.png +0 -0
  63. django_admin_mobile-0.2.4/docs/screenshots/filters-375-dark.png +0 -0
  64. django_admin_mobile-0.2.4/docs/screenshots/filters-375.png +0 -0
  65. django_admin_mobile-0.2.4/docs/screenshots/history-375-dark.png +0 -0
  66. django_admin_mobile-0.2.4/docs/screenshots/history-375.png +0 -0
  67. django_admin_mobile-0.2.4/docs/screenshots/infinite-scroll-375-dark.png +0 -0
  68. django_admin_mobile-0.2.4/docs/screenshots/infinite-scroll-375.png +0 -0
  69. django_admin_mobile-0.2.4/docs/screenshots/message-375-dark.png +0 -0
  70. django_admin_mobile-0.2.4/docs/screenshots/message-375.png +0 -0
  71. django_admin_mobile-0.2.4/docs/screenshots/nav-sidebar-375-dark.png +0 -0
  72. django_admin_mobile-0.2.4/docs/screenshots/nav-sidebar-375.png +0 -0
  73. django_admin_mobile-0.2.4/docs/screenshots/pull-375-dark.png +0 -0
  74. django_admin_mobile-0.2.4/docs/screenshots/pull-375.png +0 -0
  75. django_admin_mobile-0.2.4/docs/screenshots/rtl-changelist-375-dark.png +0 -0
  76. django_admin_mobile-0.2.4/docs/screenshots/rtl-changelist-375.png +0 -0
  77. django_admin_mobile-0.2.4/docs/screenshots/swipe-375-dark.png +0 -0
  78. django_admin_mobile-0.2.4/docs/screenshots/swipe-375.png +0 -0
  79. django_admin_mobile-0.2.4/docs/screenshots/toast-375-dark.png +0 -0
  80. django_admin_mobile-0.2.4/docs/screenshots/toast-375.png +0 -0
  81. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/.gitignore +0 -0
  82. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/CONTRIBUTING.md +0 -0
  83. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/LICENSE +0 -0
  84. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/admindocs-375-dark.png +0 -0
  85. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/admindocs-375.png +0 -0
  86. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/admindocs-model-375-dark.png +0 -0
  87. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/admindocs-model-375.png +0 -0
  88. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/calendar-375-dark.png +0 -0
  89. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/calendar-375.png +0 -0
  90. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/camera-375-dark.png +0 -0
  91. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/camera-375.png +0 -0
  92. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/clock-375-dark.png +0 -0
  93. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/clock-375.png +0 -0
  94. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/delete-confirm-375-dark.png +0 -0
  95. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/delete-confirm-375.png +0 -0
  96. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/edition-375-dark.png +0 -0
  97. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/edition-375.png +0 -0
  98. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/index-375-dark.png +0 -0
  99. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/index-375.png +0 -0
  100. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/inline-errors-375-dark.png +0 -0
  101. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/inline-errors-375.png +0 -0
  102. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/lightbox-375-dark.png +0 -0
  103. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/lightbox-375.png +0 -0
  104. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/logged-out-375-dark.png +0 -0
  105. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/logged-out-375.png +0 -0
  106. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/login-375-dark.png +0 -0
  107. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/login-375.png +0 -0
  108. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/navigator-375-dark.png +0 -0
  109. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/navigator-375.png +0 -0
  110. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/password-change-375-dark.png +0 -0
  111. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/password-change-375.png +0 -0
  112. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/picker-375-dark.png +0 -0
  113. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/picker-375.png +0 -0
  114. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/popup-375-dark.png +0 -0
  115. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/popup-375.png +0 -0
  116. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/readonly-375-dark.png +0 -0
  117. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/readonly-375.png +0 -0
  118. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/recent-375-dark.png +0 -0
  119. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/recent-375.png +0 -0
  120. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/search-375-dark.png +0 -0
  121. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/search-375.png +0 -0
  122. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/slide-confirm-375-dark.png +0 -0
  123. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/slide-confirm-375.png +0 -0
  124. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/tabs-375-dark.png +0 -0
  125. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/tabs-375.png +0 -0
  126. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/undo-375-dark.png +0 -0
  127. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/docs/screenshots/undo-375.png +0 -0
  128. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/example/example/__init__.py +0 -0
  129. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/example/example/settings.py +0 -0
  130. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/example/example/urls.py +0 -0
  131. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/example/example/wsgi.py +0 -0
  132. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/example/library/__init__.py +0 -0
  133. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/example/library/apps.py +0 -0
  134. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/example/library/management/__init__.py +0 -0
  135. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/example/library/management/commands/__init__.py +0 -0
  136. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/example/library/management/commands/seed_demo.py +0 -0
  137. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/example/library/migrations/0001_initial.py +0 -0
  138. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/example/library/migrations/0002_admin_surfaces.py +0 -0
  139. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/example/library/migrations/0003_shelf_plural.py +0 -0
  140. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/example/library/migrations/__init__.py +0 -0
  141. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/example/manage.py +0 -0
  142. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/pyproject.toml +0 -0
  143. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/src/admin_mobile/apps.py +0 -0
  144. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/src/admin_mobile/checks.py +0 -0
  145. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/src/admin_mobile/colors.py +0 -0
  146. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/src/admin_mobile/conf.py +0 -0
  147. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/src/admin_mobile/mixins.py +0 -0
  148. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/src/admin_mobile/overrides.py +0 -0
  149. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/src/admin_mobile/py.typed +0 -0
  150. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/src/admin_mobile/readonly.py +0 -0
  151. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/src/admin_mobile/routing.py +0 -0
  152. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/src/admin_mobile/search.py +0 -0
  153. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/src/admin_mobile/static/admin_mobile/icons/icon-192.png +0 -0
  154. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/src/admin_mobile/static/admin_mobile/icons/icon-512.png +0 -0
  155. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/src/admin_mobile/templates/admin/admin_mobile/recent.html +0 -0
  156. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/src/admin_mobile/templates/admin/admin_mobile/search.html +0 -0
  157. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/src/admin_mobile/templates/admin/base.html +0 -0
  158. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/src/admin_mobile/templates/admin/change_list.html +0 -0
  159. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/src/admin_mobile/templates/admin_mobile/widgets/split_datetime.html +0 -0
  160. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/src/admin_mobile/templatetags/__init__.py +0 -0
  161. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/src/admin_mobile/templatetags/admin_mobile.py +0 -0
  162. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/src/admin_mobile/views.py +0 -0
  163. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/src/admin_mobile/widgets.py +0 -0
  164. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/tests/__init__.py +0 -0
  165. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/tests/formparse.py +0 -0
  166. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/tests/settings.py +0 -0
  167. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/tests/test_admin_save.py +0 -0
  168. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/tests/test_checks.py +0 -0
  169. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/tests/test_config.py +0 -0
  170. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/tests/test_demo.py +0 -0
  171. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/tests/test_features.py +0 -0
  172. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/tests/test_formparse.py +0 -0
  173. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/tests/test_mixin.py +0 -0
  174. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/tests/test_static_contract.py +0 -0
  175. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/tests/test_templates.py +0 -0
  176. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/tests/test_widgets.py +0 -0
  177. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/tests/urls.py +0 -0
  178. {django_admin_mobile-0.2.4 → django_admin_mobile-0.2.7}/tox.ini +0 -0
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.7
4
+
5
+ - The phone filter panel is a bottom sheet: dimmed backdrop, 16px top corners, grab handle, sticky Filters / Reset / Close header, chevrons, 44px options with an accent check and right-aligned counts, and a sticky Show results button. The scrim darkens the page in light and dark. A selected row keeps a visible accent tint and check in dark mode.
6
+ - Long filter lists get a search field. Date and range inputs stay in the sheet. With JavaScript off, the filters stay inline.
7
+
8
+ ## 0.2.6
9
+
10
+ - “Save this list” is a full-width 44px control on the same 16px edge as the rest of the changelist.
11
+ - While the sticky bulk bar is open, the top action menu and Run are hidden. Select all and the count stay. With JavaScript off, that top row stays in place.
12
+
13
+ ## 0.2.5
14
+
15
+ - A selected changelist card uses a light accent tint and outline instead of Django’s flat selected-row fill.
16
+ - The checkbox and the linked column share one left-aligned title line. Field labels stay on the rows below, about 40% wide, with values wrapping in the rest of the row.
17
+ - Rich cell HTML and list-editable inputs stay inside the value column. The sticky bulk bar is full width above the tab bar: the count, the action menu, and Run share one row.
18
+
3
19
  ## 0.2.4
4
20
 
5
21
  - The action menu and Run share one 44px row and the same corner radius. Run stays only as wide as its label.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: django-admin-mobile
3
- Version: 0.2.4
3
+ Version: 0.2.7
4
4
  Summary: Make the native Django admin usable on phones without replacing its look.
5
5
  Project-URL: Homepage, https://github.com/daniilshamraev/django-admin-mobile
6
6
  Project-URL: Repository, https://github.com/daniilshamraev/django-admin-mobile
@@ -40,15 +40,57 @@ Description-Content-Type: text/markdown
40
40
 
41
41
  # django-admin-mobile
42
42
 
43
- Phone layout for the **native** Django admin. It is not a theme: colors, typography, and chrome stay on Django's own CSS variables, including `html[data-theme]` and `prefers-color-scheme`.
43
+ Phone ergonomics for the native Django admin. Same colors, same templates, a layout that fits a 375px screen.
44
44
 
45
- Targets Django 4.2 LTS, 5.x, and 6.x on Python 3.10+.
45
+ [![PyPI](https://img.shields.io/pypi/v/django-admin-mobile)](https://pypi.org/project/django-admin-mobile/)
46
+ [![Python versions](https://img.shields.io/pypi/pyversions/django-admin-mobile)](https://pypi.org/project/django-admin-mobile/)
47
+ [![Django versions](https://img.shields.io/badge/Django-4.2%20%7C%205.2%20%7C%206.0-092e20)](https://docs.djangoproject.com/)
48
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
49
+ [![CI](https://github.com/daniilshamraev/django-admin-mobile/actions/workflows/ci.yml/badge.svg)](https://github.com/daniilshamraev/django-admin-mobile/actions/workflows/ci.yml)
46
50
 
47
- ![Changelist at 375px](docs/screenshots/changelist-375.png)
51
+ ![Index, a selected changelist, the filter sheet, and a change form](docs/hero.png)
48
52
 
49
- ![Change form at 375px](docs/screenshots/change-form-375.png)
53
+ ## Before / after
50
54
 
51
- ## Install
55
+ Stock Django admin at 375px, then the same changelist with this package.
56
+
57
+ | Before | After |
58
+ | --- | --- |
59
+ | <picture><source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/stock-changelist-375-dark.png"><img alt="Stock Django admin changelist at 375px" src="docs/screenshots/stock-changelist-375.png" width="250"></picture> | <picture><source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/changelist-375-dark.png"><img alt="Changelist with django-admin-mobile at 375px" src="docs/screenshots/changelist-375.png" width="250"></picture> |
60
+
61
+ ## Changelist
62
+
63
+ Cards, a full-width action row, and a sticky bulk bar. Selecting a row hides the top action menu so Run appears once.
64
+
65
+ | Cards | One row selected | Bulk bar |
66
+ | --- | --- | --- |
67
+ | <picture><source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/changelist-375-dark.png"><img alt="Book changelist cards" src="docs/screenshots/changelist-375.png" width="250"></picture> | <picture><source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/payments-375-dark.png"><img alt="Payments changelist with nothing selected" src="docs/screenshots/payments-375.png" width="250"></picture> | <picture><source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/payments-selected-375-dark.png"><img alt="Payments changelist with the sticky bulk bar" src="docs/screenshots/payments-selected-375.png" width="250"></picture> |
68
+
69
+ ## Filters
70
+
71
+ `FILTER_SHEET` opens filters in a bottom sheet: dimmed backdrop, grab handle, Reset, a close control, 44px rows, and Show results. Long lists get a search field. Date and range widgets stay inside the sheet. With JavaScript off, `#changelist-filter` stays inline.
72
+
73
+ | Light | Dark |
74
+ | --- | --- |
75
+ | <img alt="Filter bottom sheet, light" src="docs/screenshots/filters-375.png" width="250"> | <img alt="Filter bottom sheet, dark" src="docs/screenshots/filters-375-dark.png" width="250"> |
76
+
77
+ ## Change form
78
+
79
+ Native date and time inputs, stacked inlines, and a submit bar that sits above the tab bar.
80
+
81
+ | Change form | Edition | History |
82
+ | --- | --- | --- |
83
+ | <picture><source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/change-form-375-dark.png"><img alt="Book change form" src="docs/screenshots/change-form-375.png" width="250"></picture> | <picture><source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/edition-375-dark.png"><img alt="Edition change form" src="docs/screenshots/edition-375.png" width="250"></picture> | <picture><source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/history-375-dark.png"><img alt="Object history" src="docs/screenshots/history-375.png" width="250"></picture> |
84
+
85
+ ## Index and navigation
86
+
87
+ | Index | App index | Search |
88
+ | --- | --- | --- |
89
+ | <picture><source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/index-375-dark.png"><img alt="Admin index" src="docs/screenshots/index-375.png" width="250"></picture> | <picture><source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/app-index-375-dark.png"><img alt="App index" src="docs/screenshots/app-index-375.png" width="250"></picture> | <picture><source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/search-375-dark.png"><img alt="Global search" src="docs/screenshots/search-375.png" width="250"></picture> |
90
+
91
+ Screenshots are the 375px viewport, light and dark. More captures live in [`docs/screenshots/`](docs/screenshots/).
92
+
93
+ ## Quick start
52
94
 
53
95
  ```bash
54
96
  pip install django-admin-mobile
@@ -68,7 +110,7 @@ That is enough. `admin_mobile` overrides `admin/base.html`, calls `{{ block.supe
68
110
 
69
111
  No CDN and no build step. The package ships plain files under `admin_mobile/static/`.
70
112
 
71
- ## If you already override `admin/base.html`
113
+ ### If you already override `admin/base.html`
72
114
 
73
115
  Django will use your template and skip this app's override. Load the tag yourself, and still call `{{ block.super }}`:
74
116
 
@@ -89,19 +131,14 @@ Django will use your template and skip this app's override. Load the tag yoursel
89
131
 
90
132
  `{% admin_mobile_assets %}` emits both. If `admin_mobile` is listed after `django.contrib.admin`, system check `admin_mobile.W001` warns you.
91
133
 
92
- ## Settings
134
+ ## Configuration
93
135
 
94
- All keys are optional booleans.
136
+ Every key is optional. Unknown keys are rejected. Boolean flags must be real bools.
95
137
 
96
138
  ```python
97
139
  ADMIN_MOBILE = {
98
140
  "ENABLED": True,
99
- "CHANGELIST_CARDS": True,
100
- "TABULAR_INLINE_STACK": True,
101
- "STICKY_SUBMIT": True,
102
- "TOUCH_TARGETS": True,
103
- "COLLAPSIBLE_FILTERS": True,
104
- "USE_NATIVE_DATETIME": False,
141
+ "FILTER_SHEET": True,
105
142
  }
106
143
  ```
107
144
 
@@ -113,14 +150,14 @@ ADMIN_MOBILE = {
113
150
  | `STICKY_SUBMIT` | `True` | The last `.submit-row` sticks to the bottom of the viewport. The login page is left alone. |
114
151
  | `TOUCH_TARGETS` | `True` | Targets at least 44px, inputs at 16px (stops iOS from zooming), larger checkboxes. |
115
152
  | `COLLAPSIBLE_FILTERS` | `True` | `#changelist-filter` collapses behind a button. Django's filters, django-admin-rangefilter, and django-more-admin-filters render inside that node; this package does not import them. |
153
+ | `FILTER_SHEET` | `True` | On a phone, filters open as a bottom sheet with Reset, a close control, an active-count badge, search on long lists, and Show results. With JavaScript off, the filter block stays inline. |
116
154
  | `USE_NATIVE_DATETIME` | `False` | Opt in to native date/time widgets on **every** `ModelAdmin` and inline. A custom `formfield_overrides` widget is kept. |
117
155
  | `BOTTOM_TABS` | `True` | Home, search, recent, and saved-model tabs. Favorites stay in `localStorage`. |
118
156
  | `GLOBAL_SEARCH` | `True` | Staff search across admins that define `search_fields`. Permissions are checked per model. |
119
157
  | `SWIPE_ACTIONS` | `True` | Swipe a card right to open it, left for Open/Delete. Delete still posts `delete_selected` after confirm. |
120
158
  | `PULL_TO_REFRESH` | `True` | Pull down on a changelist to reload. |
121
159
  | `INFINITE_SCROLL` | `False` | Fetch the next changelist page instead of using the pager. |
122
- | `FILTER_SHEET` | `True` | Filters open in a bottom sheet with an active-count badge and Reset. |
123
- | `STICKY_ACTIONS` | `True` | `Selected N`, the action menu, and Run stick above the tab bar. |
160
+ | `STICKY_ACTIONS` | `True` | `Selected N`, the action menu, and Run stick above the tab bar. While that bar is open, the top action menu is hidden. |
124
161
  | `UNSAVED_GUARD` | `True` | `beforeunload` when a change form is dirty. |
125
162
  | `FORM_DRAFTS` | `False` | Autosave add-form text fields in `localStorage` and offer restore. Passwords and files are skipped. |
126
163
  | `CAMERA_CAPTURE` | `True` | Camera button on file inputs, with a preview. |
@@ -137,18 +174,16 @@ ADMIN_MOBILE = {
137
174
  | `ACCENT` | unset | Hex color (`#rgb` or `#rrggbb`) mapped to `--button-bg`, `--default-button-bg`, and `--object-tools-bg` for both `data-theme` values and `prefers-color-scheme`. It does not set `--accent`, because that variable is also the header title color. |
138
175
  | `PALETTE` | unset | Optional map of allow-listed admin variable names (`primary`, `header-bg`, `link-fg`, …) to hex colors. |
139
176
  | `READONLY_MODELS` | `[]` | `app.model` labels that lose add/change/delete when the `admin_mobile_viewport=mobile` cookie is set. |
140
-
141
- `MobileAdminMixin` also accepts `mobile_card_title`, `mobile_card_subtitle`, `mobile_card_image`, `mobile_list_display`, and `mobile_readonly = True`.
177
+ | `UNDO_SECONDS` | `8` | Seconds to wait when `UNDO_DELETE` is on. An int from 5 to 10. |
178
+ | `SWIPE_CONFIRM_ACTIONS` | `["delete_selected"]` | Action names that use the slide-to-confirm control. |
142
179
 
143
180
  `mobile_readonly` and `READONLY_MODELS` only look at a cookie the phone script sets. That is a UX guard. It does not replace permissions: a desktop client, or a client that drops the cookie, keeps the real Django permissions.
144
181
 
145
182
  Surfaces use Django admin CSS variables, including `html[data-theme]` and `prefers-color-scheme`. The service worker does not cache HTML or authenticated responses.
146
183
 
147
- Unknown keys are rejected. Boolean flags must be real bools. `ACCENT` and `PALETTE` must be hex.
148
-
149
- ## Native date and time widgets
184
+ ## MobileAdminMixin
150
185
 
151
- Per model, without the global flag:
186
+ Per model, without the global date flag:
152
187
 
153
188
  ```python
154
189
  from django.contrib import admin
@@ -158,7 +193,11 @@ from library.models import Book
158
193
 
159
194
  @admin.register(Book)
160
195
  class BookAdmin(MobileAdminMixin, admin.ModelAdmin):
161
- pass
196
+ mobile_card_title = "title"
197
+ mobile_card_subtitle = "author"
198
+ mobile_list_display = ("status", "published_on")
199
+ # mobile_card_image = "cover"
200
+ # mobile_readonly = True
162
201
  ```
163
202
 
164
203
  `MobileAdminMixin` must come before `admin.ModelAdmin` or `admin.TabularInline`. It sets `formfield_overrides` to:
@@ -171,6 +210,12 @@ Values stay on `SplitDateTimeField` / `DateField` / `TimeField`, so Django still
171
210
 
172
211
  These widgets do **not** load `DateTimeShortcuts`, so you do not get a second calendar on top of the native picker.
173
212
 
213
+ ## Compatibility
214
+
215
+ Python 3.10+ and Django 4.2 LTS, 5.2, and 6.0+. Django 6 needs Python 3.12+. `tox.ini` and GitHub Actions run that matrix.
216
+
217
+ The stylesheet also covers the rest of the built-in admin at `≤600px`: login, password change, and logout; the index and app list; the nav sidebar and its filter; header, user tools, and breadcrumbs; changelist search, actions, select-all, sortable headers, date hierarchy, pagination, result counts, and `list_editable`; every built-in filter (choices, related, date, empty, and facets on Django 5+); change-form fieldsets (including collapse), help, errors, readonly fields, related-object icons, raw id lookup, autocomplete, filter horizontal/vertical, radios, and file Currently/Change/Clear; stacked and tabular inlines; object history; delete confirmation; related-object popups; messages; the stock calendar and clock when native widgets are off; admindocs; and RTL (`dir="rtl"`).
218
+
174
219
  ## Example
175
220
 
176
221
  ```bash
@@ -183,17 +228,11 @@ With `DEBUG` and `ADMIN_MOBILE_DEMO` (both set in `example/example/settings.py`)
183
228
 
184
229
  Open http://127.0.0.1:8741/admin/ and narrow the window to 375px, or use device mode.
185
230
 
186
- ![Filters open](docs/screenshots/filters-375.png)
187
-
188
- ![Changelist, dark](docs/screenshots/changelist-375-dark.png)
189
-
190
- The same stylesheet also covers the rest of the built-in admin at `≤600px`: login, password change, and logout; the index and app list; the nav sidebar and its filter; header, user tools, and breadcrumbs; changelist search, actions, select-all, sortable headers, date hierarchy, pagination, result counts, and `list_editable`; every built-in filter (choices, related, date, empty, and facets on Django 5+); change-form fieldsets (including collapse), help, errors, readonly fields, related-object icons, raw id lookup, autocomplete, filter horizontal/vertical, radios, and file Currently/Change/Clear; stacked and tabular inlines; object history; delete confirmation; related-object popups; messages; the stock calendar and clock when native widgets are off; admindocs; and RTL (`dir="rtl"`, logical alignment).
191
-
192
- Further 375px captures live in `docs/screenshots/` (`login`, `index`, `nav-sidebar`, `edition`, `calendar`, `clock`, `history`, `delete-confirm`, `popup`, `admindocs`, `rtl-changelist`, and others).
193
-
194
231
  `Appointment` in the example keeps Django's calendar and clock. `Book` and `Edition` use `MobileAdminMixin`. `USE_NATIVE_DATETIME` stays off in the example so both modes are visible.
195
232
 
196
- ## Tests
233
+ ## Contributing
234
+
235
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
197
236
 
198
237
  ```bash
199
238
  uv run ruff check .
@@ -203,14 +242,14 @@ uv run pytest -m playwright
203
242
  uv run tox
204
243
  ```
205
244
 
206
- `tox.ini` runs Django 4.2, 5.2, and 6.0. GitHub Actions adds Python 3.10–3.13 where each Django release allows it.
245
+ ## License
246
+
247
+ MIT. Copyright (c) 2026 Daniil Shamraev. See [LICENSE](LICENSE).
207
248
 
208
249
  ## Publishing
209
250
 
210
- 0.1.0 is already on PyPI. This tree is 0.2.4 and is not published from here.
251
+ 0.1.0 is already on PyPI. This tree is 0.2.7 and is not published from here.
211
252
 
212
- 1. Tag `0.2.4` when you want a release.
253
+ 1. Tag `0.2.7` when you want a release.
213
254
  2. Build: `uv build`.
214
255
  3. Publish: `uv publish` with a PyPI token.
215
-
216
- Author: Daniil Shamraev. License: MIT.
@@ -1,14 +1,56 @@
1
1
  # django-admin-mobile
2
2
 
3
- Phone layout for the **native** Django admin. It is not a theme: colors, typography, and chrome stay on Django's own CSS variables, including `html[data-theme]` and `prefers-color-scheme`.
3
+ Phone ergonomics for the native Django admin. Same colors, same templates, a layout that fits a 375px screen.
4
4
 
5
- Targets Django 4.2 LTS, 5.x, and 6.x on Python 3.10+.
5
+ [![PyPI](https://img.shields.io/pypi/v/django-admin-mobile)](https://pypi.org/project/django-admin-mobile/)
6
+ [![Python versions](https://img.shields.io/pypi/pyversions/django-admin-mobile)](https://pypi.org/project/django-admin-mobile/)
7
+ [![Django versions](https://img.shields.io/badge/Django-4.2%20%7C%205.2%20%7C%206.0-092e20)](https://docs.djangoproject.com/)
8
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
9
+ [![CI](https://github.com/daniilshamraev/django-admin-mobile/actions/workflows/ci.yml/badge.svg)](https://github.com/daniilshamraev/django-admin-mobile/actions/workflows/ci.yml)
6
10
 
7
- ![Changelist at 375px](docs/screenshots/changelist-375.png)
11
+ ![Index, a selected changelist, the filter sheet, and a change form](docs/hero.png)
8
12
 
9
- ![Change form at 375px](docs/screenshots/change-form-375.png)
13
+ ## Before / after
10
14
 
11
- ## Install
15
+ Stock Django admin at 375px, then the same changelist with this package.
16
+
17
+ | Before | After |
18
+ | --- | --- |
19
+ | <picture><source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/stock-changelist-375-dark.png"><img alt="Stock Django admin changelist at 375px" src="docs/screenshots/stock-changelist-375.png" width="250"></picture> | <picture><source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/changelist-375-dark.png"><img alt="Changelist with django-admin-mobile at 375px" src="docs/screenshots/changelist-375.png" width="250"></picture> |
20
+
21
+ ## Changelist
22
+
23
+ Cards, a full-width action row, and a sticky bulk bar. Selecting a row hides the top action menu so Run appears once.
24
+
25
+ | Cards | One row selected | Bulk bar |
26
+ | --- | --- | --- |
27
+ | <picture><source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/changelist-375-dark.png"><img alt="Book changelist cards" src="docs/screenshots/changelist-375.png" width="250"></picture> | <picture><source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/payments-375-dark.png"><img alt="Payments changelist with nothing selected" src="docs/screenshots/payments-375.png" width="250"></picture> | <picture><source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/payments-selected-375-dark.png"><img alt="Payments changelist with the sticky bulk bar" src="docs/screenshots/payments-selected-375.png" width="250"></picture> |
28
+
29
+ ## Filters
30
+
31
+ `FILTER_SHEET` opens filters in a bottom sheet: dimmed backdrop, grab handle, Reset, a close control, 44px rows, and Show results. Long lists get a search field. Date and range widgets stay inside the sheet. With JavaScript off, `#changelist-filter` stays inline.
32
+
33
+ | Light | Dark |
34
+ | --- | --- |
35
+ | <img alt="Filter bottom sheet, light" src="docs/screenshots/filters-375.png" width="250"> | <img alt="Filter bottom sheet, dark" src="docs/screenshots/filters-375-dark.png" width="250"> |
36
+
37
+ ## Change form
38
+
39
+ Native date and time inputs, stacked inlines, and a submit bar that sits above the tab bar.
40
+
41
+ | Change form | Edition | History |
42
+ | --- | --- | --- |
43
+ | <picture><source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/change-form-375-dark.png"><img alt="Book change form" src="docs/screenshots/change-form-375.png" width="250"></picture> | <picture><source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/edition-375-dark.png"><img alt="Edition change form" src="docs/screenshots/edition-375.png" width="250"></picture> | <picture><source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/history-375-dark.png"><img alt="Object history" src="docs/screenshots/history-375.png" width="250"></picture> |
44
+
45
+ ## Index and navigation
46
+
47
+ | Index | App index | Search |
48
+ | --- | --- | --- |
49
+ | <picture><source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/index-375-dark.png"><img alt="Admin index" src="docs/screenshots/index-375.png" width="250"></picture> | <picture><source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/app-index-375-dark.png"><img alt="App index" src="docs/screenshots/app-index-375.png" width="250"></picture> | <picture><source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/search-375-dark.png"><img alt="Global search" src="docs/screenshots/search-375.png" width="250"></picture> |
50
+
51
+ Screenshots are the 375px viewport, light and dark. More captures live in [`docs/screenshots/`](docs/screenshots/).
52
+
53
+ ## Quick start
12
54
 
13
55
  ```bash
14
56
  pip install django-admin-mobile
@@ -28,7 +70,7 @@ That is enough. `admin_mobile` overrides `admin/base.html`, calls `{{ block.supe
28
70
 
29
71
  No CDN and no build step. The package ships plain files under `admin_mobile/static/`.
30
72
 
31
- ## If you already override `admin/base.html`
73
+ ### If you already override `admin/base.html`
32
74
 
33
75
  Django will use your template and skip this app's override. Load the tag yourself, and still call `{{ block.super }}`:
34
76
 
@@ -49,19 +91,14 @@ Django will use your template and skip this app's override. Load the tag yoursel
49
91
 
50
92
  `{% admin_mobile_assets %}` emits both. If `admin_mobile` is listed after `django.contrib.admin`, system check `admin_mobile.W001` warns you.
51
93
 
52
- ## Settings
94
+ ## Configuration
53
95
 
54
- All keys are optional booleans.
96
+ Every key is optional. Unknown keys are rejected. Boolean flags must be real bools.
55
97
 
56
98
  ```python
57
99
  ADMIN_MOBILE = {
58
100
  "ENABLED": True,
59
- "CHANGELIST_CARDS": True,
60
- "TABULAR_INLINE_STACK": True,
61
- "STICKY_SUBMIT": True,
62
- "TOUCH_TARGETS": True,
63
- "COLLAPSIBLE_FILTERS": True,
64
- "USE_NATIVE_DATETIME": False,
101
+ "FILTER_SHEET": True,
65
102
  }
66
103
  ```
67
104
 
@@ -73,14 +110,14 @@ ADMIN_MOBILE = {
73
110
  | `STICKY_SUBMIT` | `True` | The last `.submit-row` sticks to the bottom of the viewport. The login page is left alone. |
74
111
  | `TOUCH_TARGETS` | `True` | Targets at least 44px, inputs at 16px (stops iOS from zooming), larger checkboxes. |
75
112
  | `COLLAPSIBLE_FILTERS` | `True` | `#changelist-filter` collapses behind a button. Django's filters, django-admin-rangefilter, and django-more-admin-filters render inside that node; this package does not import them. |
113
+ | `FILTER_SHEET` | `True` | On a phone, filters open as a bottom sheet with Reset, a close control, an active-count badge, search on long lists, and Show results. With JavaScript off, the filter block stays inline. |
76
114
  | `USE_NATIVE_DATETIME` | `False` | Opt in to native date/time widgets on **every** `ModelAdmin` and inline. A custom `formfield_overrides` widget is kept. |
77
115
  | `BOTTOM_TABS` | `True` | Home, search, recent, and saved-model tabs. Favorites stay in `localStorage`. |
78
116
  | `GLOBAL_SEARCH` | `True` | Staff search across admins that define `search_fields`. Permissions are checked per model. |
79
117
  | `SWIPE_ACTIONS` | `True` | Swipe a card right to open it, left for Open/Delete. Delete still posts `delete_selected` after confirm. |
80
118
  | `PULL_TO_REFRESH` | `True` | Pull down on a changelist to reload. |
81
119
  | `INFINITE_SCROLL` | `False` | Fetch the next changelist page instead of using the pager. |
82
- | `FILTER_SHEET` | `True` | Filters open in a bottom sheet with an active-count badge and Reset. |
83
- | `STICKY_ACTIONS` | `True` | `Selected N`, the action menu, and Run stick above the tab bar. |
120
+ | `STICKY_ACTIONS` | `True` | `Selected N`, the action menu, and Run stick above the tab bar. While that bar is open, the top action menu is hidden. |
84
121
  | `UNSAVED_GUARD` | `True` | `beforeunload` when a change form is dirty. |
85
122
  | `FORM_DRAFTS` | `False` | Autosave add-form text fields in `localStorage` and offer restore. Passwords and files are skipped. |
86
123
  | `CAMERA_CAPTURE` | `True` | Camera button on file inputs, with a preview. |
@@ -97,18 +134,16 @@ ADMIN_MOBILE = {
97
134
  | `ACCENT` | unset | Hex color (`#rgb` or `#rrggbb`) mapped to `--button-bg`, `--default-button-bg`, and `--object-tools-bg` for both `data-theme` values and `prefers-color-scheme`. It does not set `--accent`, because that variable is also the header title color. |
98
135
  | `PALETTE` | unset | Optional map of allow-listed admin variable names (`primary`, `header-bg`, `link-fg`, …) to hex colors. |
99
136
  | `READONLY_MODELS` | `[]` | `app.model` labels that lose add/change/delete when the `admin_mobile_viewport=mobile` cookie is set. |
100
-
101
- `MobileAdminMixin` also accepts `mobile_card_title`, `mobile_card_subtitle`, `mobile_card_image`, `mobile_list_display`, and `mobile_readonly = True`.
137
+ | `UNDO_SECONDS` | `8` | Seconds to wait when `UNDO_DELETE` is on. An int from 5 to 10. |
138
+ | `SWIPE_CONFIRM_ACTIONS` | `["delete_selected"]` | Action names that use the slide-to-confirm control. |
102
139
 
103
140
  `mobile_readonly` and `READONLY_MODELS` only look at a cookie the phone script sets. That is a UX guard. It does not replace permissions: a desktop client, or a client that drops the cookie, keeps the real Django permissions.
104
141
 
105
142
  Surfaces use Django admin CSS variables, including `html[data-theme]` and `prefers-color-scheme`. The service worker does not cache HTML or authenticated responses.
106
143
 
107
- Unknown keys are rejected. Boolean flags must be real bools. `ACCENT` and `PALETTE` must be hex.
108
-
109
- ## Native date and time widgets
144
+ ## MobileAdminMixin
110
145
 
111
- Per model, without the global flag:
146
+ Per model, without the global date flag:
112
147
 
113
148
  ```python
114
149
  from django.contrib import admin
@@ -118,7 +153,11 @@ from library.models import Book
118
153
 
119
154
  @admin.register(Book)
120
155
  class BookAdmin(MobileAdminMixin, admin.ModelAdmin):
121
- pass
156
+ mobile_card_title = "title"
157
+ mobile_card_subtitle = "author"
158
+ mobile_list_display = ("status", "published_on")
159
+ # mobile_card_image = "cover"
160
+ # mobile_readonly = True
122
161
  ```
123
162
 
124
163
  `MobileAdminMixin` must come before `admin.ModelAdmin` or `admin.TabularInline`. It sets `formfield_overrides` to:
@@ -131,6 +170,12 @@ Values stay on `SplitDateTimeField` / `DateField` / `TimeField`, so Django still
131
170
 
132
171
  These widgets do **not** load `DateTimeShortcuts`, so you do not get a second calendar on top of the native picker.
133
172
 
173
+ ## Compatibility
174
+
175
+ Python 3.10+ and Django 4.2 LTS, 5.2, and 6.0+. Django 6 needs Python 3.12+. `tox.ini` and GitHub Actions run that matrix.
176
+
177
+ The stylesheet also covers the rest of the built-in admin at `≤600px`: login, password change, and logout; the index and app list; the nav sidebar and its filter; header, user tools, and breadcrumbs; changelist search, actions, select-all, sortable headers, date hierarchy, pagination, result counts, and `list_editable`; every built-in filter (choices, related, date, empty, and facets on Django 5+); change-form fieldsets (including collapse), help, errors, readonly fields, related-object icons, raw id lookup, autocomplete, filter horizontal/vertical, radios, and file Currently/Change/Clear; stacked and tabular inlines; object history; delete confirmation; related-object popups; messages; the stock calendar and clock when native widgets are off; admindocs; and RTL (`dir="rtl"`).
178
+
134
179
  ## Example
135
180
 
136
181
  ```bash
@@ -143,17 +188,11 @@ With `DEBUG` and `ADMIN_MOBILE_DEMO` (both set in `example/example/settings.py`)
143
188
 
144
189
  Open http://127.0.0.1:8741/admin/ and narrow the window to 375px, or use device mode.
145
190
 
146
- ![Filters open](docs/screenshots/filters-375.png)
147
-
148
- ![Changelist, dark](docs/screenshots/changelist-375-dark.png)
149
-
150
- The same stylesheet also covers the rest of the built-in admin at `≤600px`: login, password change, and logout; the index and app list; the nav sidebar and its filter; header, user tools, and breadcrumbs; changelist search, actions, select-all, sortable headers, date hierarchy, pagination, result counts, and `list_editable`; every built-in filter (choices, related, date, empty, and facets on Django 5+); change-form fieldsets (including collapse), help, errors, readonly fields, related-object icons, raw id lookup, autocomplete, filter horizontal/vertical, radios, and file Currently/Change/Clear; stacked and tabular inlines; object history; delete confirmation; related-object popups; messages; the stock calendar and clock when native widgets are off; admindocs; and RTL (`dir="rtl"`, logical alignment).
151
-
152
- Further 375px captures live in `docs/screenshots/` (`login`, `index`, `nav-sidebar`, `edition`, `calendar`, `clock`, `history`, `delete-confirm`, `popup`, `admindocs`, `rtl-changelist`, and others).
153
-
154
191
  `Appointment` in the example keeps Django's calendar and clock. `Book` and `Edition` use `MobileAdminMixin`. `USE_NATIVE_DATETIME` stays off in the example so both modes are visible.
155
192
 
156
- ## Tests
193
+ ## Contributing
194
+
195
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
157
196
 
158
197
  ```bash
159
198
  uv run ruff check .
@@ -163,14 +202,14 @@ uv run pytest -m playwright
163
202
  uv run tox
164
203
  ```
165
204
 
166
- `tox.ini` runs Django 4.2, 5.2, and 6.0. GitHub Actions adds Python 3.10–3.13 where each Django release allows it.
205
+ ## License
206
+
207
+ MIT. Copyright (c) 2026 Daniil Shamraev. See [LICENSE](LICENSE).
167
208
 
168
209
  ## Publishing
169
210
 
170
- 0.1.0 is already on PyPI. This tree is 0.2.4 and is not published from here.
211
+ 0.1.0 is already on PyPI. This tree is 0.2.7 and is not published from here.
171
212
 
172
- 1. Tag `0.2.4` when you want a release.
213
+ 1. Tag `0.2.7` when you want a release.
173
214
  2. Build: `uv build`.
174
215
  3. Publish: `uv publish` with a PyPI token.
175
-
176
- Author: Daniil Shamraev. License: MIT.
Binary file
@@ -1,6 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from django.contrib import admin
4
+ from django.templatetags.static import static
5
+ from django.utils.html import format_html
4
6
 
5
7
  from admin_mobile.mixins import MobileAdminMixin
6
8
  from library.models import (
@@ -10,6 +12,7 @@ from library.models import (
10
12
  Chapter,
11
13
  Citation,
12
14
  Edition,
15
+ Payment,
13
16
  Publisher,
14
17
  Revision,
15
18
  Series,
@@ -142,6 +145,46 @@ class RevisionAdmin(admin.ModelAdmin):
142
145
  search_fields = ("label",)
143
146
 
144
147
 
148
+ @admin.action(description="Отметить проверенным")
149
+ def mark_reviewed(
150
+ modeladmin: admin.ModelAdmin, request: object, queryset: object
151
+ ) -> None:
152
+ del modeladmin, request
153
+ queryset.update(reviewed=True)
154
+
155
+
156
+ @admin.register(Payment)
157
+ class PaymentAdmin(MobileAdminMixin, admin.ModelAdmin):
158
+ """No card-title hint: the link column itself has to become the title."""
159
+
160
+ list_display = (
161
+ "id",
162
+ "payer",
163
+ "amount",
164
+ "system",
165
+ "status",
166
+ "paid_on",
167
+ "external_id",
168
+ "comment",
169
+ "currency",
170
+ )
171
+ list_display_links = ("id",)
172
+ list_editable = ("amount",)
173
+ list_per_page = 10
174
+ actions = (mark_reviewed,)
175
+
176
+ @admin.display(description="Пользователь")
177
+ def payer(self, obj: Payment) -> str:
178
+ return format_html(
179
+ '<img alt="" src="{}" width="16" height="16"> <strong>{}</strong> '
180
+ '(<a href="https://vk.com/id{}">vk.com/id{}</a>)',
181
+ static("admin/img/icon-viewlink.svg"),
182
+ obj.payer_name,
183
+ obj.vk_id,
184
+ obj.vk_id,
185
+ )
186
+
187
+
145
188
  @admin.register(Appointment)
146
189
  class AppointmentAdmin(admin.ModelAdmin):
147
190
  """Stock widgets on purpose, so the admin calendar and clock remain."""
@@ -26,6 +26,7 @@ def seed_demo(*args: object, **kwargs: object) -> bool:
26
26
  Chapter,
27
27
  Citation,
28
28
  Edition,
29
+ Payment,
29
30
  Publisher,
30
31
  Revision,
31
32
  Series,
@@ -140,4 +141,15 @@ def seed_demo(*args: object, **kwargs: object) -> bool:
140
141
  at_time=time(18, 30),
141
142
  when=timezone.make_aware(datetime(2026, 4, 2, 18, 30)),
142
143
  )
144
+ Payment.objects.create(
145
+ payer_name="Мария Александровна Волконская",
146
+ vk_id="123456789012345678",
147
+ amount="1490.50",
148
+ system=Payment.System.WALLET,
149
+ status="Оплачен",
150
+ paid_on=date(2026, 3, 2),
151
+ external_id="pay-1843-notes",
152
+ comment="Перевод за второе издание",
153
+ currency="RUB",
154
+ )
143
155
  return True