django-admin-mobile 0.1.0__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.
- django_admin_mobile-0.1.0/.gitignore +21 -0
- django_admin_mobile-0.1.0/CHANGELOG.md +10 -0
- django_admin_mobile-0.1.0/CONTRIBUTING.md +36 -0
- django_admin_mobile-0.1.0/LICENSE +21 -0
- django_admin_mobile-0.1.0/PKG-INFO +188 -0
- django_admin_mobile-0.1.0/README.md +148 -0
- django_admin_mobile-0.1.0/docs/screenshots/admindocs-375.png +0 -0
- django_admin_mobile-0.1.0/docs/screenshots/admindocs-model-375.png +0 -0
- django_admin_mobile-0.1.0/docs/screenshots/app-index-375.png +0 -0
- django_admin_mobile-0.1.0/docs/screenshots/calendar-375.png +0 -0
- django_admin_mobile-0.1.0/docs/screenshots/change-form-375.png +0 -0
- django_admin_mobile-0.1.0/docs/screenshots/changelist-375-dark.png +0 -0
- django_admin_mobile-0.1.0/docs/screenshots/changelist-375.png +0 -0
- django_admin_mobile-0.1.0/docs/screenshots/changelist-editable-375.png +0 -0
- django_admin_mobile-0.1.0/docs/screenshots/clock-375.png +0 -0
- django_admin_mobile-0.1.0/docs/screenshots/delete-confirm-375.png +0 -0
- django_admin_mobile-0.1.0/docs/screenshots/edition-375.png +0 -0
- django_admin_mobile-0.1.0/docs/screenshots/filters-375.png +0 -0
- django_admin_mobile-0.1.0/docs/screenshots/history-375.png +0 -0
- django_admin_mobile-0.1.0/docs/screenshots/index-375.png +0 -0
- django_admin_mobile-0.1.0/docs/screenshots/inline-errors-375.png +0 -0
- django_admin_mobile-0.1.0/docs/screenshots/logged-out-375.png +0 -0
- django_admin_mobile-0.1.0/docs/screenshots/login-375.png +0 -0
- django_admin_mobile-0.1.0/docs/screenshots/message-375.png +0 -0
- django_admin_mobile-0.1.0/docs/screenshots/nav-sidebar-375.png +0 -0
- django_admin_mobile-0.1.0/docs/screenshots/password-change-375.png +0 -0
- django_admin_mobile-0.1.0/docs/screenshots/popup-375.png +0 -0
- django_admin_mobile-0.1.0/docs/screenshots/rtl-changelist-375.png +0 -0
- django_admin_mobile-0.1.0/example/example/__init__.py +0 -0
- django_admin_mobile-0.1.0/example/example/settings.py +75 -0
- django_admin_mobile-0.1.0/example/example/urls.py +7 -0
- django_admin_mobile-0.1.0/example/example/wsgi.py +7 -0
- django_admin_mobile-0.1.0/example/library/__init__.py +0 -0
- django_admin_mobile-0.1.0/example/library/admin.py +137 -0
- django_admin_mobile-0.1.0/example/library/apps.py +13 -0
- django_admin_mobile-0.1.0/example/library/demo.py +143 -0
- django_admin_mobile-0.1.0/example/library/management/__init__.py +0 -0
- django_admin_mobile-0.1.0/example/library/management/commands/__init__.py +0 -0
- django_admin_mobile-0.1.0/example/library/management/commands/seed_demo.py +14 -0
- django_admin_mobile-0.1.0/example/library/migrations/0001_initial.py +111 -0
- django_admin_mobile-0.1.0/example/library/migrations/0002_admin_surfaces.py +234 -0
- django_admin_mobile-0.1.0/example/library/migrations/0003_shelf_plural.py +17 -0
- django_admin_mobile-0.1.0/example/library/migrations/__init__.py +0 -0
- django_admin_mobile-0.1.0/example/library/models.py +189 -0
- django_admin_mobile-0.1.0/example/manage.py +19 -0
- django_admin_mobile-0.1.0/pyproject.toml +91 -0
- django_admin_mobile-0.1.0/src/admin_mobile/__init__.py +3 -0
- django_admin_mobile-0.1.0/src/admin_mobile/apps.py +16 -0
- django_admin_mobile-0.1.0/src/admin_mobile/checks.py +72 -0
- django_admin_mobile-0.1.0/src/admin_mobile/conf.py +60 -0
- django_admin_mobile-0.1.0/src/admin_mobile/mixins.py +29 -0
- django_admin_mobile-0.1.0/src/admin_mobile/overrides.py +144 -0
- django_admin_mobile-0.1.0/src/admin_mobile/py.typed +0 -0
- django_admin_mobile-0.1.0/src/admin_mobile/static/admin_mobile/css/admin_mobile.css +885 -0
- django_admin_mobile-0.1.0/src/admin_mobile/static/admin_mobile/js/admin_mobile.js +263 -0
- django_admin_mobile-0.1.0/src/admin_mobile/templates/admin/base.html +13 -0
- django_admin_mobile-0.1.0/src/admin_mobile/templates/admin_mobile/widgets/split_datetime.html +13 -0
- django_admin_mobile-0.1.0/src/admin_mobile/templatetags/__init__.py +0 -0
- django_admin_mobile-0.1.0/src/admin_mobile/templatetags/admin_mobile.py +101 -0
- django_admin_mobile-0.1.0/src/admin_mobile/widgets.py +118 -0
- django_admin_mobile-0.1.0/tests/__init__.py +0 -0
- django_admin_mobile-0.1.0/tests/formparse.py +60 -0
- django_admin_mobile-0.1.0/tests/settings.py +70 -0
- django_admin_mobile-0.1.0/tests/test_admin_save.py +92 -0
- django_admin_mobile-0.1.0/tests/test_checks.py +33 -0
- django_admin_mobile-0.1.0/tests/test_config.py +43 -0
- django_admin_mobile-0.1.0/tests/test_demo.py +30 -0
- django_admin_mobile-0.1.0/tests/test_formparse.py +26 -0
- django_admin_mobile-0.1.0/tests/test_mixin.py +153 -0
- django_admin_mobile-0.1.0/tests/test_playwright.py +369 -0
- django_admin_mobile-0.1.0/tests/test_static_contract.py +78 -0
- django_admin_mobile-0.1.0/tests/test_templates.py +95 -0
- django_admin_mobile-0.1.0/tests/test_widgets.py +112 -0
- django_admin_mobile-0.1.0/tests/urls.py +9 -0
- django_admin_mobile-0.1.0/tox.ini +16 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
.venv/
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
.pytest_cache/
|
|
5
|
+
.ruff_cache/
|
|
6
|
+
.tox/
|
|
7
|
+
.mypy_cache/
|
|
8
|
+
dist/
|
|
9
|
+
build/
|
|
10
|
+
*.egg-info/
|
|
11
|
+
example/db.sqlite3
|
|
12
|
+
example/media/
|
|
13
|
+
tests/media/
|
|
14
|
+
example/*.sqlite3
|
|
15
|
+
tests/.test.sqlite3
|
|
16
|
+
tests/test_*.sqlite3
|
|
17
|
+
htmlcov/
|
|
18
|
+
.coverage
|
|
19
|
+
playwright-report/
|
|
20
|
+
test-results/
|
|
21
|
+
*.log
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
- Phone layout for the native Django admin: changelist cards, stacked tabular inlines, sticky submit row, 44px targets, 16px inputs.
|
|
6
|
+
- Collapsible `#changelist-filter` toggle that does not depend on third-party filter packages.
|
|
7
|
+
- `NativeDateInput`, `NativeTimeInput`, `NativeSplitDateTimeWidget`, and `MobileAdminMixin`.
|
|
8
|
+
- Opt-in `ADMIN_MOBILE["USE_NATIVE_DATETIME"]` for every `ModelAdmin` and inline.
|
|
9
|
+
- Light and dark surfaces use Django admin CSS variables only.
|
|
10
|
+
- Phone layout for the rest of the built-in admin: login, password change, logout, index, nav sidebar, header, breadcrumbs, date hierarchy, pagination, list editing, select-all, sort links, facets and the empty filter, related-object widgets, raw id lookup, autocomplete, filter horizontal/vertical, radios, file clear, collapsed fieldsets, readonly fields, errors, stacked inlines, history, delete confirmation, popups, messages, calendar/clock, admindocs, and RTL.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
`django-admin-mobile` keeps the stock Django admin and only adds a stylesheet, a small script, and optional native date widgets.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
uv sync --extra dev
|
|
9
|
+
uv run python example/manage.py migrate
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Checks
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
uv run ruff check .
|
|
16
|
+
uv run ruff format --check .
|
|
17
|
+
uv run pytest -m "not playwright"
|
|
18
|
+
uv run pytest -m playwright
|
|
19
|
+
uv run tox
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Playwright needs Chromium once: `uv run playwright install chromium`.
|
|
23
|
+
|
|
24
|
+
The screenshot test writes `docs/screenshots/*.png`. Update those files in the same change when the phone layout changes.
|
|
25
|
+
|
|
26
|
+
## Compatibility
|
|
27
|
+
|
|
28
|
+
The tox and GitHub Actions matrices cover Django 4.2, 5.2, and 6.0 on Python 3.10+ (Django 6 requires 3.12+). Do not add a build step or a CDN. Static files ship as plain CSS and JavaScript.
|
|
29
|
+
|
|
30
|
+
## Project layout
|
|
31
|
+
|
|
32
|
+
- `src/admin_mobile/` is the installable app.
|
|
33
|
+
- `example/` is a local admin with books, chapters, dates, and filters.
|
|
34
|
+
- `tests/` covers settings, widgets, template injection, and the phone screenshots.
|
|
35
|
+
|
|
36
|
+
Feature-Sliced Design is for product frontends. This repository stays a single Django app so `INSTALLED_APPS` can load it.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Daniil Shamraev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: django-admin-mobile
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Make the native Django admin usable on phones without replacing its look.
|
|
5
|
+
Project-URL: Homepage, https://github.com/daniilshamraev/django-admin-mobile
|
|
6
|
+
Project-URL: Repository, https://github.com/daniilshamraev/django-admin-mobile
|
|
7
|
+
Project-URL: Issues, https://github.com/daniilshamraev/django-admin-mobile/issues
|
|
8
|
+
Author: Daniil Shamraev
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: admin,django,mobile,responsive
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Web Environment
|
|
14
|
+
Classifier: Framework :: Django
|
|
15
|
+
Classifier: Framework :: Django :: 4.2
|
|
16
|
+
Classifier: Framework :: Django :: 5.2
|
|
17
|
+
Classifier: Framework :: Django :: 6.0
|
|
18
|
+
Classifier: Framework :: Django :: 6.1
|
|
19
|
+
Classifier: Intended Audience :: Developers
|
|
20
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
21
|
+
Classifier: Operating System :: OS Independent
|
|
22
|
+
Classifier: Programming Language :: JavaScript
|
|
23
|
+
Classifier: Programming Language :: Python :: 3
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
27
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
28
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
29
|
+
Requires-Python: >=3.10
|
|
30
|
+
Requires-Dist: django>=4.2
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: docutils>=0.20; extra == 'dev'
|
|
33
|
+
Requires-Dist: playwright>=1.49; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest-django>=4.9; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest-playwright>=0.6; extra == 'dev'
|
|
36
|
+
Requires-Dist: pytest>=8.3; extra == 'dev'
|
|
37
|
+
Requires-Dist: ruff>=0.8; extra == 'dev'
|
|
38
|
+
Requires-Dist: tox>=4.23; extra == 'dev'
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
|
|
41
|
+
# django-admin-mobile
|
|
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`.
|
|
44
|
+
|
|
45
|
+
Targets Django 4.2 LTS, 5.x, and 6.x on Python 3.10+.
|
|
46
|
+
|
|
47
|
+

|
|
48
|
+
|
|
49
|
+

|
|
50
|
+
|
|
51
|
+
## Install
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install django-admin-mobile
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Put the app **before** `django.contrib.admin`:
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
INSTALLED_APPS = [
|
|
61
|
+
"admin_mobile",
|
|
62
|
+
"django.contrib.admin",
|
|
63
|
+
# ...
|
|
64
|
+
]
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
That is enough. `admin_mobile` overrides `admin/base.html`, calls `{{ block.super }}`, and appends its CSS and JS inside `extrastyle` and `extrahead`. Projects that only override `admin/base_site.html` keep working, because that template still extends `admin/base.html`.
|
|
68
|
+
|
|
69
|
+
No CDN and no build step. The package ships plain files under `admin_mobile/static/`.
|
|
70
|
+
|
|
71
|
+
## If you already override `admin/base.html`
|
|
72
|
+
|
|
73
|
+
Django will use your template and skip this app's override. Load the tag yourself, and still call `{{ block.super }}`:
|
|
74
|
+
|
|
75
|
+
```django
|
|
76
|
+
{% extends "admin/base.html" %}
|
|
77
|
+
{% load admin_mobile %}
|
|
78
|
+
|
|
79
|
+
{% block extrastyle %}
|
|
80
|
+
{{ block.super }}
|
|
81
|
+
{% admin_mobile_assets "style" %}
|
|
82
|
+
{% endblock %}
|
|
83
|
+
|
|
84
|
+
{% block extrahead %}
|
|
85
|
+
{{ block.super }}
|
|
86
|
+
{% admin_mobile_assets "script" %}
|
|
87
|
+
{% endblock %}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`{% admin_mobile_assets %}` emits both. If `admin_mobile` is listed after `django.contrib.admin`, system check `admin_mobile.W001` warns you.
|
|
91
|
+
|
|
92
|
+
## Settings
|
|
93
|
+
|
|
94
|
+
All keys are optional booleans.
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
ADMIN_MOBILE = {
|
|
98
|
+
"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,
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
| Key | Default | Effect |
|
|
109
|
+
| --- | --- | --- |
|
|
110
|
+
| `ENABLED` | `True` | Turn the stylesheet and script off. |
|
|
111
|
+
| `CHANGELIST_CARDS` | `True` | At `≤600px`, changelist rows become cards. `data-label` is copied from the column header. From 601px to 1024px the table scrolls sideways instead. Checkboxes and links stay in the row. |
|
|
112
|
+
| `TABULAR_INLINE_STACK` | `True` | Tabular inlines stack into labeled fields. The empty template row stays hidden, so Django's `inlines.js` can still add and delete rows. |
|
|
113
|
+
| `STICKY_SUBMIT` | `True` | The last `.submit-row` sticks to the bottom of the viewport. The login page is left alone. |
|
|
114
|
+
| `TOUCH_TARGETS` | `True` | Targets at least 44px, inputs at 16px (stops iOS from zooming), larger checkboxes. |
|
|
115
|
+
| `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. |
|
|
116
|
+
| `USE_NATIVE_DATETIME` | `False` | Opt in to native date/time widgets on **every** `ModelAdmin` and inline. A custom `formfield_overrides` widget is kept. |
|
|
117
|
+
|
|
118
|
+
Unknown keys and non-booleans raise `ImproperlyConfigured`.
|
|
119
|
+
|
|
120
|
+
## Native date and time widgets
|
|
121
|
+
|
|
122
|
+
Per model, without the global flag:
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
from django.contrib import admin
|
|
126
|
+
from admin_mobile.mixins import MobileAdminMixin
|
|
127
|
+
from library.models import Book
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@admin.register(Book)
|
|
131
|
+
class BookAdmin(MobileAdminMixin, admin.ModelAdmin):
|
|
132
|
+
pass
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`MobileAdminMixin` must come before `admin.ModelAdmin` or `admin.TabularInline`. It sets `formfield_overrides` to:
|
|
136
|
+
|
|
137
|
+
- `admin_mobile.widgets.NativeDateInput` (`<input type="date">`)
|
|
138
|
+
- `admin_mobile.widgets.NativeTimeInput` (`<input type="time" step="1">`)
|
|
139
|
+
- `admin_mobile.widgets.NativeSplitDateTimeWidget`
|
|
140
|
+
|
|
141
|
+
Values stay on `SplitDateTimeField` / `DateField` / `TimeField`, so Django still validates them and still applies the current time zone. The widget renders the active zone (not the raw UTC instant) and prints that zone name under the inputs. ISO formats are required (`YYYY-MM-DD`, `HH:MM` or `HH:MM:SS`); a locale string such as `15.06.2024` is rejected. Microseconds are dropped, matching Django's admin split widget.
|
|
142
|
+
|
|
143
|
+
These widgets do **not** load `DateTimeShortcuts`, so you do not get a second calendar on top of the native picker.
|
|
144
|
+
|
|
145
|
+
## Example
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
uv sync --extra dev
|
|
149
|
+
uv run python example/manage.py migrate
|
|
150
|
+
uv run python example/manage.py runserver 127.0.0.1:8741
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
With `DEBUG` and `ADMIN_MOBILE_DEMO` (both set in `example/example/settings.py`) migrate creates a local superuser `admin` / `admin` and a few books. That password exists only for this demo. Do not set `ADMIN_MOBILE_DEMO` on a shared host; the seed refuses to run when `DEBUG` is off.
|
|
154
|
+
|
|
155
|
+
Open http://127.0.0.1:8741/admin/ and narrow the window to 375px, or use device mode.
|
|
156
|
+
|
|
157
|
+

|
|
158
|
+
|
|
159
|
+

|
|
160
|
+
|
|
161
|
+
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).
|
|
162
|
+
|
|
163
|
+
Further 375px captures live in `docs/screenshots/` (`login`, `index`, `nav-sidebar`, `edition`, `calendar`, `clock`, `history`, `delete-confirm`, `popup`, `admindocs`, `rtl-changelist`, and others).
|
|
164
|
+
|
|
165
|
+
`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.
|
|
166
|
+
|
|
167
|
+
## Tests
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
uv run ruff check .
|
|
171
|
+
uv run pytest -m "not playwright"
|
|
172
|
+
uv run playwright install chromium
|
|
173
|
+
uv run pytest -m playwright
|
|
174
|
+
uv run tox
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
`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.
|
|
178
|
+
|
|
179
|
+
## Publishing
|
|
180
|
+
|
|
181
|
+
The package is not on PyPI yet.
|
|
182
|
+
|
|
183
|
+
1. Create a GitHub repository and push this project.
|
|
184
|
+
2. Add `[project.urls]` in `pyproject.toml` (`Homepage`, `Repository`, `Changelog`).
|
|
185
|
+
3. Build: `uv build` (hatchling, src layout, static files and templates included).
|
|
186
|
+
4. Publish: `uv publish` with a PyPI API token. The name `django-admin-mobile` has to be free on PyPI; the import name is `admin_mobile`.
|
|
187
|
+
|
|
188
|
+
Author: Daniil Shamraev. License: MIT.
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# django-admin-mobile
|
|
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`.
|
|
4
|
+
|
|
5
|
+
Targets Django 4.2 LTS, 5.x, and 6.x on Python 3.10+.
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install django-admin-mobile
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Put the app **before** `django.contrib.admin`:
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
INSTALLED_APPS = [
|
|
21
|
+
"admin_mobile",
|
|
22
|
+
"django.contrib.admin",
|
|
23
|
+
# ...
|
|
24
|
+
]
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
That is enough. `admin_mobile` overrides `admin/base.html`, calls `{{ block.super }}`, and appends its CSS and JS inside `extrastyle` and `extrahead`. Projects that only override `admin/base_site.html` keep working, because that template still extends `admin/base.html`.
|
|
28
|
+
|
|
29
|
+
No CDN and no build step. The package ships plain files under `admin_mobile/static/`.
|
|
30
|
+
|
|
31
|
+
## If you already override `admin/base.html`
|
|
32
|
+
|
|
33
|
+
Django will use your template and skip this app's override. Load the tag yourself, and still call `{{ block.super }}`:
|
|
34
|
+
|
|
35
|
+
```django
|
|
36
|
+
{% extends "admin/base.html" %}
|
|
37
|
+
{% load admin_mobile %}
|
|
38
|
+
|
|
39
|
+
{% block extrastyle %}
|
|
40
|
+
{{ block.super }}
|
|
41
|
+
{% admin_mobile_assets "style" %}
|
|
42
|
+
{% endblock %}
|
|
43
|
+
|
|
44
|
+
{% block extrahead %}
|
|
45
|
+
{{ block.super }}
|
|
46
|
+
{% admin_mobile_assets "script" %}
|
|
47
|
+
{% endblock %}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`{% admin_mobile_assets %}` emits both. If `admin_mobile` is listed after `django.contrib.admin`, system check `admin_mobile.W001` warns you.
|
|
51
|
+
|
|
52
|
+
## Settings
|
|
53
|
+
|
|
54
|
+
All keys are optional booleans.
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
ADMIN_MOBILE = {
|
|
58
|
+
"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,
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
| Key | Default | Effect |
|
|
69
|
+
| --- | --- | --- |
|
|
70
|
+
| `ENABLED` | `True` | Turn the stylesheet and script off. |
|
|
71
|
+
| `CHANGELIST_CARDS` | `True` | At `≤600px`, changelist rows become cards. `data-label` is copied from the column header. From 601px to 1024px the table scrolls sideways instead. Checkboxes and links stay in the row. |
|
|
72
|
+
| `TABULAR_INLINE_STACK` | `True` | Tabular inlines stack into labeled fields. The empty template row stays hidden, so Django's `inlines.js` can still add and delete rows. |
|
|
73
|
+
| `STICKY_SUBMIT` | `True` | The last `.submit-row` sticks to the bottom of the viewport. The login page is left alone. |
|
|
74
|
+
| `TOUCH_TARGETS` | `True` | Targets at least 44px, inputs at 16px (stops iOS from zooming), larger checkboxes. |
|
|
75
|
+
| `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. |
|
|
76
|
+
| `USE_NATIVE_DATETIME` | `False` | Opt in to native date/time widgets on **every** `ModelAdmin` and inline. A custom `formfield_overrides` widget is kept. |
|
|
77
|
+
|
|
78
|
+
Unknown keys and non-booleans raise `ImproperlyConfigured`.
|
|
79
|
+
|
|
80
|
+
## Native date and time widgets
|
|
81
|
+
|
|
82
|
+
Per model, without the global flag:
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from django.contrib import admin
|
|
86
|
+
from admin_mobile.mixins import MobileAdminMixin
|
|
87
|
+
from library.models import Book
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@admin.register(Book)
|
|
91
|
+
class BookAdmin(MobileAdminMixin, admin.ModelAdmin):
|
|
92
|
+
pass
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`MobileAdminMixin` must come before `admin.ModelAdmin` or `admin.TabularInline`. It sets `formfield_overrides` to:
|
|
96
|
+
|
|
97
|
+
- `admin_mobile.widgets.NativeDateInput` (`<input type="date">`)
|
|
98
|
+
- `admin_mobile.widgets.NativeTimeInput` (`<input type="time" step="1">`)
|
|
99
|
+
- `admin_mobile.widgets.NativeSplitDateTimeWidget`
|
|
100
|
+
|
|
101
|
+
Values stay on `SplitDateTimeField` / `DateField` / `TimeField`, so Django still validates them and still applies the current time zone. The widget renders the active zone (not the raw UTC instant) and prints that zone name under the inputs. ISO formats are required (`YYYY-MM-DD`, `HH:MM` or `HH:MM:SS`); a locale string such as `15.06.2024` is rejected. Microseconds are dropped, matching Django's admin split widget.
|
|
102
|
+
|
|
103
|
+
These widgets do **not** load `DateTimeShortcuts`, so you do not get a second calendar on top of the native picker.
|
|
104
|
+
|
|
105
|
+
## Example
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
uv sync --extra dev
|
|
109
|
+
uv run python example/manage.py migrate
|
|
110
|
+
uv run python example/manage.py runserver 127.0.0.1:8741
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
With `DEBUG` and `ADMIN_MOBILE_DEMO` (both set in `example/example/settings.py`) migrate creates a local superuser `admin` / `admin` and a few books. That password exists only for this demo. Do not set `ADMIN_MOBILE_DEMO` on a shared host; the seed refuses to run when `DEBUG` is off.
|
|
114
|
+
|
|
115
|
+
Open http://127.0.0.1:8741/admin/ and narrow the window to 375px, or use device mode.
|
|
116
|
+
|
|
117
|
+

|
|
118
|
+
|
|
119
|
+

|
|
120
|
+
|
|
121
|
+
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).
|
|
122
|
+
|
|
123
|
+
Further 375px captures live in `docs/screenshots/` (`login`, `index`, `nav-sidebar`, `edition`, `calendar`, `clock`, `history`, `delete-confirm`, `popup`, `admindocs`, `rtl-changelist`, and others).
|
|
124
|
+
|
|
125
|
+
`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.
|
|
126
|
+
|
|
127
|
+
## Tests
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
uv run ruff check .
|
|
131
|
+
uv run pytest -m "not playwright"
|
|
132
|
+
uv run playwright install chromium
|
|
133
|
+
uv run pytest -m playwright
|
|
134
|
+
uv run tox
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`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.
|
|
138
|
+
|
|
139
|
+
## Publishing
|
|
140
|
+
|
|
141
|
+
The package is not on PyPI yet.
|
|
142
|
+
|
|
143
|
+
1. Create a GitHub repository and push this project.
|
|
144
|
+
2. Add `[project.urls]` in `pyproject.toml` (`Homepage`, `Repository`, `Changelog`).
|
|
145
|
+
3. Build: `uv build` (hatchling, src layout, static files and templates included).
|
|
146
|
+
4. Publish: `uv publish` with a PyPI API token. The name `django-admin-mobile` has to be free on PyPI; the import name is `admin_mobile`.
|
|
147
|
+
|
|
148
|
+
Author: Daniil Shamraev. License: MIT.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Local demo settings. Do not deploy this module."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
6
|
+
|
|
7
|
+
SECRET_KEY = "example-only-not-a-secret"
|
|
8
|
+
DEBUG = True
|
|
9
|
+
ALLOWED_HOSTS = ["127.0.0.1", "localhost"]
|
|
10
|
+
ADMIN_MOBILE_DEMO = True
|
|
11
|
+
|
|
12
|
+
INSTALLED_APPS = [
|
|
13
|
+
"admin_mobile",
|
|
14
|
+
"django.contrib.admin",
|
|
15
|
+
"django.contrib.auth",
|
|
16
|
+
"django.contrib.contenttypes",
|
|
17
|
+
"django.contrib.sessions",
|
|
18
|
+
"django.contrib.messages",
|
|
19
|
+
"django.contrib.staticfiles",
|
|
20
|
+
"django.contrib.admindocs",
|
|
21
|
+
"library",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
MIDDLEWARE = [
|
|
25
|
+
"django.middleware.security.SecurityMiddleware",
|
|
26
|
+
"django.contrib.sessions.middleware.SessionMiddleware",
|
|
27
|
+
"django.middleware.common.CommonMiddleware",
|
|
28
|
+
"django.middleware.csrf.CsrfViewMiddleware",
|
|
29
|
+
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
|
30
|
+
"django.contrib.messages.middleware.MessageMiddleware",
|
|
31
|
+
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
ROOT_URLCONF = "example.urls"
|
|
35
|
+
WSGI_APPLICATION = "example.wsgi.application"
|
|
36
|
+
|
|
37
|
+
TEMPLATES = [
|
|
38
|
+
{
|
|
39
|
+
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
|
40
|
+
"DIRS": [],
|
|
41
|
+
"APP_DIRS": True,
|
|
42
|
+
"OPTIONS": {
|
|
43
|
+
"context_processors": [
|
|
44
|
+
"django.template.context_processors.request",
|
|
45
|
+
"django.contrib.auth.context_processors.auth",
|
|
46
|
+
"django.contrib.messages.context_processors.messages",
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
DATABASES = {
|
|
53
|
+
"default": {
|
|
54
|
+
"ENGINE": "django.db.backends.sqlite3",
|
|
55
|
+
"NAME": BASE_DIR / "db.sqlite3",
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
AUTH_PASSWORD_VALIDATORS = []
|
|
60
|
+
|
|
61
|
+
LANGUAGE_CODE = "en-us"
|
|
62
|
+
TIME_ZONE = "Europe/Moscow"
|
|
63
|
+
USE_I18N = True
|
|
64
|
+
USE_TZ = True
|
|
65
|
+
|
|
66
|
+
STATIC_URL = "static/"
|
|
67
|
+
MEDIA_URL = "media/"
|
|
68
|
+
MEDIA_ROOT = BASE_DIR / "media"
|
|
69
|
+
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
|
70
|
+
|
|
71
|
+
# Native widgets are opt-in per model via MobileAdminMixin.
|
|
72
|
+
# AppointmentAdmin keeps Django's calendar and clock.
|
|
73
|
+
ADMIN_MOBILE = {
|
|
74
|
+
"USE_NATIVE_DATETIME": False,
|
|
75
|
+
}
|
|
File without changes
|