django-smart-dynamic-path 1.0.2__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 (19) hide show
  1. django_smart_dynamic_path-1.0.2/LICENSE +28 -0
  2. django_smart_dynamic_path-1.0.2/PKG-INFO +209 -0
  3. django_smart_dynamic_path-1.0.2/README.md +182 -0
  4. django_smart_dynamic_path-1.0.2/django_smart_dynamic_path/__init__.py +8 -0
  5. django_smart_dynamic_path-1.0.2/django_smart_dynamic_path/django_app.py +18 -0
  6. django_smart_dynamic_path-1.0.2/django_smart_dynamic_path/management/__init__.py +0 -0
  7. django_smart_dynamic_path-1.0.2/django_smart_dynamic_path/management/commands/__init__.py +0 -0
  8. django_smart_dynamic_path-1.0.2/django_smart_dynamic_path/management/commands/generate_secret_key.py +22 -0
  9. django_smart_dynamic_path-1.0.2/django_smart_dynamic_path/management/commands/get_admin_path.py +16 -0
  10. django_smart_dynamic_path-1.0.2/django_smart_dynamic_path/templatetags/__init__.py +0 -0
  11. django_smart_dynamic_path-1.0.2/django_smart_dynamic_path/templatetags/admin_link.py +11 -0
  12. django_smart_dynamic_path-1.0.2/django_smart_dynamic_path/urls.py +19 -0
  13. django_smart_dynamic_path-1.0.2/django_smart_dynamic_path.egg-info/PKG-INFO +209 -0
  14. django_smart_dynamic_path-1.0.2/django_smart_dynamic_path.egg-info/SOURCES.txt +17 -0
  15. django_smart_dynamic_path-1.0.2/django_smart_dynamic_path.egg-info/dependency_links.txt +1 -0
  16. django_smart_dynamic_path-1.0.2/django_smart_dynamic_path.egg-info/requires.txt +2 -0
  17. django_smart_dynamic_path-1.0.2/django_smart_dynamic_path.egg-info/top_level.txt +2 -0
  18. django_smart_dynamic_path-1.0.2/pyproject.toml +39 -0
  19. django_smart_dynamic_path-1.0.2/setup.cfg +4 -0
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, Alexander Suvorov
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,209 @@
1
+ Metadata-Version: 2.4
2
+ Name: django-smart-dynamic-path
3
+ Version: 1.0.2
4
+ Summary: Dynamic secret admin URL for Django. Time-based, deterministic, no storage. Hide your Django admin behind a URL that changes daily/monthly/hourly. Only you know the secret phrase to generate the current path.
5
+ Author-email: Alexander Suvorov <smartlegionlab@gmail.com>
6
+ License: BSD-3-Clause
7
+ Project-URL: Homepage, https://github.com/smartlegionlab/django-smart-dynamic-path
8
+ Classifier: Framework :: Django
9
+ Classifier: Framework :: Django :: 3.2
10
+ Classifier: Framework :: Django :: 4.0
11
+ Classifier: Framework :: Django :: 5.0
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.7
14
+ Classifier: Programming Language :: Python :: 3.8
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Topic :: Security
21
+ Requires-Python: >=3.7
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: smart-dynamic-path>=1.0.1
25
+ Requires-Dist: Django>=3.2
26
+ Dynamic: license-file
27
+
28
+ # Django Smart Dynamic Path <sup>v1.0.2</sup>
29
+
30
+ **Dynamic secret admin URL for Django. Time-based, deterministic, no storage.**
31
+
32
+ Hide your Django admin behind a URL that changes daily/monthly. Only you know the secret phrase to generate the current path.
33
+
34
+ ---
35
+
36
+ [![GitHub release (latest by date)](https://img.shields.io/github/v/release/smartlegionlab/django-smart-dynamic-path)](https://github.com/smartlegionlab/django-smart-dynamic-path/)
37
+ ![GitHub top language](https://img.shields.io/github/languages/top/smartlegionlab/django-smart-dynamic-path)
38
+ [![GitHub](https://img.shields.io/github/license/smartlegionlab/django-smart-dynamic-path)](https://github.com/smartlegionlab/django-smart-dynamic-path/blob/master/LICENSE)
39
+ [![GitHub stars](https://img.shields.io/github/stars/smartlegionlab/django-smart-dynamic-path?style=social)](https://github.com/smartlegionlab/django-smart-dynamic-path/stargazers)
40
+ [![GitHub forks](https://img.shields.io/github/forks/smartlegionlab/django-smart-dynamic-path?style=social)](https://github.com/smartlegionlab/django-smart-dynamic-path/network/members)
41
+
42
+ [![PyPI - Downloads](https://img.shields.io/pypi/dm/django-smart-dynamic-path?label=pypi%20downloads)](https://pypi.org/project/django-smart-dynamic-path/)
43
+ [![PyPI](https://img.shields.io/pypi/v/django-smart-dynamic-path)](https://pypi.org/project/django-smart-dynamic-path)
44
+ [![PyPI - Format](https://img.shields.io/pypi/format/django-smart-dynamic-path)](https://pypi.org/project/django-smart-dynamic-path)
45
+ [![PyPI Downloads](https://static.pepy.tech/badge/django-smart-dynamic-path)](https://pepy.tech/projects/django-smart-dynamic-path)
46
+ [![PyPI Downloads](https://static.pepy.tech/badge/django-smart-dynamic-path/month)](https://pepy.tech/projects/django-smart-dynamic-path)
47
+ [![PyPI Downloads](https://static.pepy.tech/badge/django-smart-dynamic-path/week)](https://pepy.tech/projects/django-smart-dynamic-path)
48
+
49
+ ---
50
+
51
+ ## What it does
52
+
53
+ Standard Django admin: `/admin/` (always the same)
54
+
55
+ This package: `/admin/a1b2c3d4e5f6g7h8/` (changes automatically)
56
+
57
+ ---
58
+
59
+ ## Disclaimer
60
+
61
+ **By using this software, you agree to the full disclaimer terms.**
62
+
63
+ **Summary:** Software provided "AS IS" without warranty. You assume all risks.
64
+
65
+ **Full legal disclaimer:** See [DISCLAIMER.md](https://github.com/smartlegionlab/django-smart-dynamic-path/blob/master/DISCLAIMER.md)
66
+
67
+ ---
68
+
69
+ ## Installation
70
+
71
+ ```bash
72
+ pip install django-smart-dynamic-path
73
+ ```
74
+
75
+ ## Quick start
76
+
77
+ ### 1. Add to `INSTALLED_APPS`
78
+
79
+ ```python
80
+ INSTALLED_APPS = [
81
+ 'django_smart_dynamic_path',
82
+ 'django.contrib.admin',
83
+ # ...
84
+ ]
85
+ ```
86
+
87
+ ### 2. Replace admin URLs in `urls.py`
88
+
89
+ ```python
90
+ from django.urls import path, include
91
+
92
+ urlpatterns = [
93
+ path('admin/', include('django_smart_dynamic_path.urls')),
94
+ ]
95
+ ```
96
+
97
+ ### 3. Generate SECRET_KEY from secret phrase (local)
98
+
99
+ ```bash
100
+ smart-dynamic-path --secret "your secret phrase" --key-only
101
+ ```
102
+
103
+ Copy the output and set as `SECRET_KEY` in your `.env` or `settings.py`.
104
+
105
+ ### 4. Configure period (optional, in `settings.py`)
106
+
107
+ ```python
108
+ SECRET_ADMIN_PERIOD = 'day' # day, month, static
109
+ ```
110
+
111
+ ### 5. Get current admin path (server)
112
+
113
+ ```bash
114
+ python manage.py get_admin_path --full
115
+ ```
116
+
117
+ ## ⚠️ Important Django limitation
118
+
119
+ Due to Django's URL compilation mechanism, the admin path is calculated **once when the server starts**.
120
+ To apply a new path when the period changes (e.g., next day), you need to restart the server.
121
+
122
+ ### Automatic restart with cron
123
+
124
+ ```bash
125
+ # Add to crontab (restart at midnight)
126
+ 0 0 * * * systemctl restart gunicorn
127
+ ```
128
+
129
+ This is not a bug, but a Django architectural feature.
130
+
131
+ ## Django management commands
132
+
133
+ | Command | Output |
134
+ |----------------------------------------------------------|-----------------------|
135
+ | `python manage.py generate_secret_key --secret "phrase"` | SECRET_KEY and period |
136
+ | `python manage.py get_admin_path` | `a1b2c3d4e5f6g7h8` |
137
+ | `python manage.py get_admin_path --full` | `/a1b2c3d4e5f6g7h8/` |
138
+
139
+ ## Template tag
140
+
141
+ ```django
142
+ {% load admin_link %}
143
+ <a href="{% secret_admin_link %}">Secret Admin</a>
144
+ ```
145
+
146
+ ## Python API
147
+
148
+ ```python
149
+ from django_smart_dynamic_path import get_admin_path, get_admin_url
150
+
151
+ path = get_admin_path() # 'a1b2c3d4e5f6g7h8'
152
+ url = get_admin_url() # 'admin/a1b2c3d4e5f6g7h8/'
153
+ ```
154
+
155
+ ## How it works
156
+
157
+ ```
158
+ SECRET_KEY = SHA256(secret_phrase) # 64 hex chars (256 bits)
159
+ ADMIN_PATH = SHA256(SECRET_KEY + date)[:16].hex() # 32 hex chars (128 bits)
160
+ ```
161
+
162
+ - Secret phrase → SECRET_KEY (256 bits)
163
+ - SECRET_KEY + current date → admin path (128 bits)
164
+ - Path changes automatically based on period (day/month/hour/static)
165
+ - Same secret phrase always produces same SECRET_KEY
166
+ - Same SECRET_KEY + same date always produces same path
167
+
168
+ ## Getting the path locally
169
+
170
+ ```bash
171
+ git clone https://github.com/smartlegionlab/smart-dynamic-path
172
+ cd smart-dynamic-path
173
+
174
+ python3 -m smart_dynamic_path.cli --secret "my secret phrase"
175
+ python3 -m smart_dynamic_path.cli --secret "my secret phrase" --period month --prefix admin --full
176
+ python3 -m smart_dynamic_path.cli --secret "my secret phrase" --key-only
177
+ ```
178
+
179
+ ## Implemented paradigms
180
+
181
+ ### 1. Pointer‑Based Security
182
+ The admin URL is not stored anywhere. It is regenerated on demand from a secret phrase and current time. There is no stored "pointer" — only the ability to compute it.
183
+
184
+ **DOI:** [10.5281/zenodo.17204738](https://doi.org/10.5281/zenodo.17204738)
185
+
186
+ ### 2. Local Data Regeneration
187
+ The exact admin path is computed locally on the developer's machine using only the secret phrase and date, without accessing the server. The server never knows the secret phrase.
188
+
189
+ **DOI:** [10.5281/zenodo.17264327](https://doi.org/10.5281/zenodo.17264327)
190
+
191
+ ### 3. Position‑Candidate‑Hypothesis (PCH)
192
+ Among all possible URL paths (2¹²⁸ candidates), only one specific path generated by the secret phrase is valid at any given time. The hypothesis (which path is valid) is verified through the hash function.
193
+
194
+ **DOI:** [10.5281/zenodo.17614888](https://doi.org/10.5281/zenodo.17614888)
195
+
196
+ ## Security
197
+
198
+ - No additional secrets — uses only Django's existing SECRET_KEY
199
+ - Secret phrase exists only in memory, never stored
200
+ - 32 hex chars = 2¹²⁸ possible paths (no brute force)
201
+ - Time‑based rotation limits exposure window
202
+
203
+ ## License
204
+
205
+ BSD-3-Clause
206
+
207
+ ## Author
208
+
209
+ Alexander Suvorov [@smartlegionlab](https://github.com/smartlegionlab)
@@ -0,0 +1,182 @@
1
+ # Django Smart Dynamic Path <sup>v1.0.2</sup>
2
+
3
+ **Dynamic secret admin URL for Django. Time-based, deterministic, no storage.**
4
+
5
+ Hide your Django admin behind a URL that changes daily/monthly. Only you know the secret phrase to generate the current path.
6
+
7
+ ---
8
+
9
+ [![GitHub release (latest by date)](https://img.shields.io/github/v/release/smartlegionlab/django-smart-dynamic-path)](https://github.com/smartlegionlab/django-smart-dynamic-path/)
10
+ ![GitHub top language](https://img.shields.io/github/languages/top/smartlegionlab/django-smart-dynamic-path)
11
+ [![GitHub](https://img.shields.io/github/license/smartlegionlab/django-smart-dynamic-path)](https://github.com/smartlegionlab/django-smart-dynamic-path/blob/master/LICENSE)
12
+ [![GitHub stars](https://img.shields.io/github/stars/smartlegionlab/django-smart-dynamic-path?style=social)](https://github.com/smartlegionlab/django-smart-dynamic-path/stargazers)
13
+ [![GitHub forks](https://img.shields.io/github/forks/smartlegionlab/django-smart-dynamic-path?style=social)](https://github.com/smartlegionlab/django-smart-dynamic-path/network/members)
14
+
15
+ [![PyPI - Downloads](https://img.shields.io/pypi/dm/django-smart-dynamic-path?label=pypi%20downloads)](https://pypi.org/project/django-smart-dynamic-path/)
16
+ [![PyPI](https://img.shields.io/pypi/v/django-smart-dynamic-path)](https://pypi.org/project/django-smart-dynamic-path)
17
+ [![PyPI - Format](https://img.shields.io/pypi/format/django-smart-dynamic-path)](https://pypi.org/project/django-smart-dynamic-path)
18
+ [![PyPI Downloads](https://static.pepy.tech/badge/django-smart-dynamic-path)](https://pepy.tech/projects/django-smart-dynamic-path)
19
+ [![PyPI Downloads](https://static.pepy.tech/badge/django-smart-dynamic-path/month)](https://pepy.tech/projects/django-smart-dynamic-path)
20
+ [![PyPI Downloads](https://static.pepy.tech/badge/django-smart-dynamic-path/week)](https://pepy.tech/projects/django-smart-dynamic-path)
21
+
22
+ ---
23
+
24
+ ## What it does
25
+
26
+ Standard Django admin: `/admin/` (always the same)
27
+
28
+ This package: `/admin/a1b2c3d4e5f6g7h8/` (changes automatically)
29
+
30
+ ---
31
+
32
+ ## Disclaimer
33
+
34
+ **By using this software, you agree to the full disclaimer terms.**
35
+
36
+ **Summary:** Software provided "AS IS" without warranty. You assume all risks.
37
+
38
+ **Full legal disclaimer:** See [DISCLAIMER.md](https://github.com/smartlegionlab/django-smart-dynamic-path/blob/master/DISCLAIMER.md)
39
+
40
+ ---
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ pip install django-smart-dynamic-path
46
+ ```
47
+
48
+ ## Quick start
49
+
50
+ ### 1. Add to `INSTALLED_APPS`
51
+
52
+ ```python
53
+ INSTALLED_APPS = [
54
+ 'django_smart_dynamic_path',
55
+ 'django.contrib.admin',
56
+ # ...
57
+ ]
58
+ ```
59
+
60
+ ### 2. Replace admin URLs in `urls.py`
61
+
62
+ ```python
63
+ from django.urls import path, include
64
+
65
+ urlpatterns = [
66
+ path('admin/', include('django_smart_dynamic_path.urls')),
67
+ ]
68
+ ```
69
+
70
+ ### 3. Generate SECRET_KEY from secret phrase (local)
71
+
72
+ ```bash
73
+ smart-dynamic-path --secret "your secret phrase" --key-only
74
+ ```
75
+
76
+ Copy the output and set as `SECRET_KEY` in your `.env` or `settings.py`.
77
+
78
+ ### 4. Configure period (optional, in `settings.py`)
79
+
80
+ ```python
81
+ SECRET_ADMIN_PERIOD = 'day' # day, month, static
82
+ ```
83
+
84
+ ### 5. Get current admin path (server)
85
+
86
+ ```bash
87
+ python manage.py get_admin_path --full
88
+ ```
89
+
90
+ ## ⚠️ Important Django limitation
91
+
92
+ Due to Django's URL compilation mechanism, the admin path is calculated **once when the server starts**.
93
+ To apply a new path when the period changes (e.g., next day), you need to restart the server.
94
+
95
+ ### Automatic restart with cron
96
+
97
+ ```bash
98
+ # Add to crontab (restart at midnight)
99
+ 0 0 * * * systemctl restart gunicorn
100
+ ```
101
+
102
+ This is not a bug, but a Django architectural feature.
103
+
104
+ ## Django management commands
105
+
106
+ | Command | Output |
107
+ |----------------------------------------------------------|-----------------------|
108
+ | `python manage.py generate_secret_key --secret "phrase"` | SECRET_KEY and period |
109
+ | `python manage.py get_admin_path` | `a1b2c3d4e5f6g7h8` |
110
+ | `python manage.py get_admin_path --full` | `/a1b2c3d4e5f6g7h8/` |
111
+
112
+ ## Template tag
113
+
114
+ ```django
115
+ {% load admin_link %}
116
+ <a href="{% secret_admin_link %}">Secret Admin</a>
117
+ ```
118
+
119
+ ## Python API
120
+
121
+ ```python
122
+ from django_smart_dynamic_path import get_admin_path, get_admin_url
123
+
124
+ path = get_admin_path() # 'a1b2c3d4e5f6g7h8'
125
+ url = get_admin_url() # 'admin/a1b2c3d4e5f6g7h8/'
126
+ ```
127
+
128
+ ## How it works
129
+
130
+ ```
131
+ SECRET_KEY = SHA256(secret_phrase) # 64 hex chars (256 bits)
132
+ ADMIN_PATH = SHA256(SECRET_KEY + date)[:16].hex() # 32 hex chars (128 bits)
133
+ ```
134
+
135
+ - Secret phrase → SECRET_KEY (256 bits)
136
+ - SECRET_KEY + current date → admin path (128 bits)
137
+ - Path changes automatically based on period (day/month/hour/static)
138
+ - Same secret phrase always produces same SECRET_KEY
139
+ - Same SECRET_KEY + same date always produces same path
140
+
141
+ ## Getting the path locally
142
+
143
+ ```bash
144
+ git clone https://github.com/smartlegionlab/smart-dynamic-path
145
+ cd smart-dynamic-path
146
+
147
+ python3 -m smart_dynamic_path.cli --secret "my secret phrase"
148
+ python3 -m smart_dynamic_path.cli --secret "my secret phrase" --period month --prefix admin --full
149
+ python3 -m smart_dynamic_path.cli --secret "my secret phrase" --key-only
150
+ ```
151
+
152
+ ## Implemented paradigms
153
+
154
+ ### 1. Pointer‑Based Security
155
+ The admin URL is not stored anywhere. It is regenerated on demand from a secret phrase and current time. There is no stored "pointer" — only the ability to compute it.
156
+
157
+ **DOI:** [10.5281/zenodo.17204738](https://doi.org/10.5281/zenodo.17204738)
158
+
159
+ ### 2. Local Data Regeneration
160
+ The exact admin path is computed locally on the developer's machine using only the secret phrase and date, without accessing the server. The server never knows the secret phrase.
161
+
162
+ **DOI:** [10.5281/zenodo.17264327](https://doi.org/10.5281/zenodo.17264327)
163
+
164
+ ### 3. Position‑Candidate‑Hypothesis (PCH)
165
+ Among all possible URL paths (2¹²⁸ candidates), only one specific path generated by the secret phrase is valid at any given time. The hypothesis (which path is valid) is verified through the hash function.
166
+
167
+ **DOI:** [10.5281/zenodo.17614888](https://doi.org/10.5281/zenodo.17614888)
168
+
169
+ ## Security
170
+
171
+ - No additional secrets — uses only Django's existing SECRET_KEY
172
+ - Secret phrase exists only in memory, never stored
173
+ - 32 hex chars = 2¹²⁸ possible paths (no brute force)
174
+ - Time‑based rotation limits exposure window
175
+
176
+ ## License
177
+
178
+ BSD-3-Clause
179
+
180
+ ## Author
181
+
182
+ Alexander Suvorov [@smartlegionlab](https://github.com/smartlegionlab)
@@ -0,0 +1,8 @@
1
+ # Copyright (©) 2026, Alexander Suvorov. All rights reserved.
2
+ __version__ = '1.0.2'
3
+ __all__ = [
4
+ 'get_admin_path',
5
+ 'get_admin_url',
6
+ ]
7
+
8
+ from django_smart_dynamic_path.django_app import get_admin_path, get_admin_url
@@ -0,0 +1,18 @@
1
+ # Copyright (©) 2026, Alexander Suvorov. All rights reserved.
2
+ from django.conf import settings
3
+ from smart_dynamic_path import generate_path
4
+
5
+
6
+ def get_admin_path(period: str = "day") -> str:
7
+ secret_key = getattr(settings, 'SECRET_KEY', None)
8
+ if not secret_key:
9
+ raise ValueError("SECRET_KEY not found in settings")
10
+
11
+ if period is None:
12
+ period = getattr(settings, 'SECRET_ADMIN_PERIOD', 'day')
13
+
14
+ return generate_path(secret_key, period, prefix='')
15
+
16
+
17
+ def get_admin_url(period: str = "day") -> str:
18
+ return f"/admin/{get_admin_path(period)}/"
@@ -0,0 +1,22 @@
1
+ # Copyright (©) 2026, Alexander Suvorov. All rights reserved.
2
+ from django.core.management.base import BaseCommand
3
+ from smart_dynamic_path import generate_secret_key
4
+
5
+
6
+ class Command(BaseCommand):
7
+ help = 'Generate SECRET_KEY from secret phrase'
8
+
9
+ def add_arguments(self, parser):
10
+ parser.add_argument('--secret', '-s', required=True, help='Secret phrase')
11
+ parser.add_argument('--period', '-p', default='day',
12
+ choices=['day', 'month', 'hour', 'static'],
13
+ help='Period for path generation (default: day)')
14
+
15
+ def handle(self, *args, **options):
16
+ secret = options['secret']
17
+ period = options['period']
18
+
19
+ secret_key = generate_secret_key(secret)
20
+
21
+ self.stdout.write(self.style.SUCCESS(f"SECRET_KEY={secret_key}"))
22
+ self.stdout.write(self.style.SUCCESS(f"SECRET_ADMIN_PERIOD={period}"))
@@ -0,0 +1,16 @@
1
+ # Copyright (©) 2026, Alexander Suvorov. All rights reserved.
2
+ from django.core.management.base import BaseCommand
3
+ from django_smart_dynamic_path.django_app import get_admin_path, get_admin_url
4
+
5
+
6
+ class Command(BaseCommand):
7
+ help = 'Get current secret admin path'
8
+
9
+ def add_arguments(self, parser):
10
+ parser.add_argument('--full', action='store_true', help='Show full URL')
11
+
12
+ def handle(self, *args, **options):
13
+ if options['full']:
14
+ self.stdout.write(self.style.SUCCESS(get_admin_url()))
15
+ else:
16
+ self.stdout.write(self.style.SUCCESS(f"/{get_admin_path()}/"))
@@ -0,0 +1,11 @@
1
+ # Copyright (©) 2026, Alexander Suvorov. All rights reserved.
2
+ from django import template
3
+ from django_smart_dynamic_path.django_app import get_admin_url
4
+
5
+ register = template.Library()
6
+
7
+
8
+ @register.simple_tag
9
+ def secret_admin_link():
10
+ """Template tag to get current secret admin URL"""
11
+ return get_admin_url()
@@ -0,0 +1,19 @@
1
+ # Copyright (©) 2026, Alexander Suvorov. All rights reserved.
2
+ """
3
+ IMPORTANT: Due to Django's URL compilation mechanism,
4
+ the admin path is calculated once when the server starts.
5
+ To apply a new path when the period changes, restart the server.
6
+
7
+ Add to cron for automatic restart:
8
+ 0 0 * * * systemctl restart gunicorn
9
+ """
10
+
11
+ from django.urls import path
12
+ from django.contrib import admin
13
+ from django_smart_dynamic_path.django_app import get_admin_path
14
+
15
+ CURRENT_ADMIN_PATH = get_admin_path()
16
+
17
+ urlpatterns = [
18
+ path(f'{CURRENT_ADMIN_PATH}/', admin.site.urls),
19
+ ]
@@ -0,0 +1,209 @@
1
+ Metadata-Version: 2.4
2
+ Name: django-smart-dynamic-path
3
+ Version: 1.0.2
4
+ Summary: Dynamic secret admin URL for Django. Time-based, deterministic, no storage. Hide your Django admin behind a URL that changes daily/monthly/hourly. Only you know the secret phrase to generate the current path.
5
+ Author-email: Alexander Suvorov <smartlegionlab@gmail.com>
6
+ License: BSD-3-Clause
7
+ Project-URL: Homepage, https://github.com/smartlegionlab/django-smart-dynamic-path
8
+ Classifier: Framework :: Django
9
+ Classifier: Framework :: Django :: 3.2
10
+ Classifier: Framework :: Django :: 4.0
11
+ Classifier: Framework :: Django :: 5.0
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.7
14
+ Classifier: Programming Language :: Python :: 3.8
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Topic :: Security
21
+ Requires-Python: >=3.7
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: smart-dynamic-path>=1.0.1
25
+ Requires-Dist: Django>=3.2
26
+ Dynamic: license-file
27
+
28
+ # Django Smart Dynamic Path <sup>v1.0.2</sup>
29
+
30
+ **Dynamic secret admin URL for Django. Time-based, deterministic, no storage.**
31
+
32
+ Hide your Django admin behind a URL that changes daily/monthly. Only you know the secret phrase to generate the current path.
33
+
34
+ ---
35
+
36
+ [![GitHub release (latest by date)](https://img.shields.io/github/v/release/smartlegionlab/django-smart-dynamic-path)](https://github.com/smartlegionlab/django-smart-dynamic-path/)
37
+ ![GitHub top language](https://img.shields.io/github/languages/top/smartlegionlab/django-smart-dynamic-path)
38
+ [![GitHub](https://img.shields.io/github/license/smartlegionlab/django-smart-dynamic-path)](https://github.com/smartlegionlab/django-smart-dynamic-path/blob/master/LICENSE)
39
+ [![GitHub stars](https://img.shields.io/github/stars/smartlegionlab/django-smart-dynamic-path?style=social)](https://github.com/smartlegionlab/django-smart-dynamic-path/stargazers)
40
+ [![GitHub forks](https://img.shields.io/github/forks/smartlegionlab/django-smart-dynamic-path?style=social)](https://github.com/smartlegionlab/django-smart-dynamic-path/network/members)
41
+
42
+ [![PyPI - Downloads](https://img.shields.io/pypi/dm/django-smart-dynamic-path?label=pypi%20downloads)](https://pypi.org/project/django-smart-dynamic-path/)
43
+ [![PyPI](https://img.shields.io/pypi/v/django-smart-dynamic-path)](https://pypi.org/project/django-smart-dynamic-path)
44
+ [![PyPI - Format](https://img.shields.io/pypi/format/django-smart-dynamic-path)](https://pypi.org/project/django-smart-dynamic-path)
45
+ [![PyPI Downloads](https://static.pepy.tech/badge/django-smart-dynamic-path)](https://pepy.tech/projects/django-smart-dynamic-path)
46
+ [![PyPI Downloads](https://static.pepy.tech/badge/django-smart-dynamic-path/month)](https://pepy.tech/projects/django-smart-dynamic-path)
47
+ [![PyPI Downloads](https://static.pepy.tech/badge/django-smart-dynamic-path/week)](https://pepy.tech/projects/django-smart-dynamic-path)
48
+
49
+ ---
50
+
51
+ ## What it does
52
+
53
+ Standard Django admin: `/admin/` (always the same)
54
+
55
+ This package: `/admin/a1b2c3d4e5f6g7h8/` (changes automatically)
56
+
57
+ ---
58
+
59
+ ## Disclaimer
60
+
61
+ **By using this software, you agree to the full disclaimer terms.**
62
+
63
+ **Summary:** Software provided "AS IS" without warranty. You assume all risks.
64
+
65
+ **Full legal disclaimer:** See [DISCLAIMER.md](https://github.com/smartlegionlab/django-smart-dynamic-path/blob/master/DISCLAIMER.md)
66
+
67
+ ---
68
+
69
+ ## Installation
70
+
71
+ ```bash
72
+ pip install django-smart-dynamic-path
73
+ ```
74
+
75
+ ## Quick start
76
+
77
+ ### 1. Add to `INSTALLED_APPS`
78
+
79
+ ```python
80
+ INSTALLED_APPS = [
81
+ 'django_smart_dynamic_path',
82
+ 'django.contrib.admin',
83
+ # ...
84
+ ]
85
+ ```
86
+
87
+ ### 2. Replace admin URLs in `urls.py`
88
+
89
+ ```python
90
+ from django.urls import path, include
91
+
92
+ urlpatterns = [
93
+ path('admin/', include('django_smart_dynamic_path.urls')),
94
+ ]
95
+ ```
96
+
97
+ ### 3. Generate SECRET_KEY from secret phrase (local)
98
+
99
+ ```bash
100
+ smart-dynamic-path --secret "your secret phrase" --key-only
101
+ ```
102
+
103
+ Copy the output and set as `SECRET_KEY` in your `.env` or `settings.py`.
104
+
105
+ ### 4. Configure period (optional, in `settings.py`)
106
+
107
+ ```python
108
+ SECRET_ADMIN_PERIOD = 'day' # day, month, static
109
+ ```
110
+
111
+ ### 5. Get current admin path (server)
112
+
113
+ ```bash
114
+ python manage.py get_admin_path --full
115
+ ```
116
+
117
+ ## ⚠️ Important Django limitation
118
+
119
+ Due to Django's URL compilation mechanism, the admin path is calculated **once when the server starts**.
120
+ To apply a new path when the period changes (e.g., next day), you need to restart the server.
121
+
122
+ ### Automatic restart with cron
123
+
124
+ ```bash
125
+ # Add to crontab (restart at midnight)
126
+ 0 0 * * * systemctl restart gunicorn
127
+ ```
128
+
129
+ This is not a bug, but a Django architectural feature.
130
+
131
+ ## Django management commands
132
+
133
+ | Command | Output |
134
+ |----------------------------------------------------------|-----------------------|
135
+ | `python manage.py generate_secret_key --secret "phrase"` | SECRET_KEY and period |
136
+ | `python manage.py get_admin_path` | `a1b2c3d4e5f6g7h8` |
137
+ | `python manage.py get_admin_path --full` | `/a1b2c3d4e5f6g7h8/` |
138
+
139
+ ## Template tag
140
+
141
+ ```django
142
+ {% load admin_link %}
143
+ <a href="{% secret_admin_link %}">Secret Admin</a>
144
+ ```
145
+
146
+ ## Python API
147
+
148
+ ```python
149
+ from django_smart_dynamic_path import get_admin_path, get_admin_url
150
+
151
+ path = get_admin_path() # 'a1b2c3d4e5f6g7h8'
152
+ url = get_admin_url() # 'admin/a1b2c3d4e5f6g7h8/'
153
+ ```
154
+
155
+ ## How it works
156
+
157
+ ```
158
+ SECRET_KEY = SHA256(secret_phrase) # 64 hex chars (256 bits)
159
+ ADMIN_PATH = SHA256(SECRET_KEY + date)[:16].hex() # 32 hex chars (128 bits)
160
+ ```
161
+
162
+ - Secret phrase → SECRET_KEY (256 bits)
163
+ - SECRET_KEY + current date → admin path (128 bits)
164
+ - Path changes automatically based on period (day/month/hour/static)
165
+ - Same secret phrase always produces same SECRET_KEY
166
+ - Same SECRET_KEY + same date always produces same path
167
+
168
+ ## Getting the path locally
169
+
170
+ ```bash
171
+ git clone https://github.com/smartlegionlab/smart-dynamic-path
172
+ cd smart-dynamic-path
173
+
174
+ python3 -m smart_dynamic_path.cli --secret "my secret phrase"
175
+ python3 -m smart_dynamic_path.cli --secret "my secret phrase" --period month --prefix admin --full
176
+ python3 -m smart_dynamic_path.cli --secret "my secret phrase" --key-only
177
+ ```
178
+
179
+ ## Implemented paradigms
180
+
181
+ ### 1. Pointer‑Based Security
182
+ The admin URL is not stored anywhere. It is regenerated on demand from a secret phrase and current time. There is no stored "pointer" — only the ability to compute it.
183
+
184
+ **DOI:** [10.5281/zenodo.17204738](https://doi.org/10.5281/zenodo.17204738)
185
+
186
+ ### 2. Local Data Regeneration
187
+ The exact admin path is computed locally on the developer's machine using only the secret phrase and date, without accessing the server. The server never knows the secret phrase.
188
+
189
+ **DOI:** [10.5281/zenodo.17264327](https://doi.org/10.5281/zenodo.17264327)
190
+
191
+ ### 3. Position‑Candidate‑Hypothesis (PCH)
192
+ Among all possible URL paths (2¹²⁸ candidates), only one specific path generated by the secret phrase is valid at any given time. The hypothesis (which path is valid) is verified through the hash function.
193
+
194
+ **DOI:** [10.5281/zenodo.17614888](https://doi.org/10.5281/zenodo.17614888)
195
+
196
+ ## Security
197
+
198
+ - No additional secrets — uses only Django's existing SECRET_KEY
199
+ - Secret phrase exists only in memory, never stored
200
+ - 32 hex chars = 2¹²⁸ possible paths (no brute force)
201
+ - Time‑based rotation limits exposure window
202
+
203
+ ## License
204
+
205
+ BSD-3-Clause
206
+
207
+ ## Author
208
+
209
+ Alexander Suvorov [@smartlegionlab](https://github.com/smartlegionlab)
@@ -0,0 +1,17 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ django_smart_dynamic_path/__init__.py
5
+ django_smart_dynamic_path/django_app.py
6
+ django_smart_dynamic_path/urls.py
7
+ django_smart_dynamic_path.egg-info/PKG-INFO
8
+ django_smart_dynamic_path.egg-info/SOURCES.txt
9
+ django_smart_dynamic_path.egg-info/dependency_links.txt
10
+ django_smart_dynamic_path.egg-info/requires.txt
11
+ django_smart_dynamic_path.egg-info/top_level.txt
12
+ django_smart_dynamic_path/management/__init__.py
13
+ django_smart_dynamic_path/management/commands/__init__.py
14
+ django_smart_dynamic_path/management/commands/generate_secret_key.py
15
+ django_smart_dynamic_path/management/commands/get_admin_path.py
16
+ django_smart_dynamic_path/templatetags/__init__.py
17
+ django_smart_dynamic_path/templatetags/admin_link.py
@@ -0,0 +1,2 @@
1
+ smart-dynamic-path>=1.0.1
2
+ Django>=3.2
@@ -0,0 +1,2 @@
1
+ dist
2
+ django_smart_dynamic_path
@@ -0,0 +1,39 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "django-smart-dynamic-path"
7
+ version = "1.0.2"
8
+ description = "Dynamic secret admin URL for Django. Time-based, deterministic, no storage. Hide your Django admin behind a URL that changes daily/monthly/hourly. Only you know the secret phrase to generate the current path."
9
+ authors = [
10
+ {name = "Alexander Suvorov", email = "smartlegionlab@gmail.com"}
11
+ ]
12
+ license = {text = "BSD-3-Clause"}
13
+ readme = "README.md"
14
+ requires-python = ">=3.7"
15
+ classifiers = [
16
+ "Framework :: Django",
17
+ "Framework :: Django :: 3.2",
18
+ "Framework :: Django :: 4.0",
19
+ "Framework :: Django :: 5.0",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.7",
22
+ "Programming Language :: Python :: 3.8",
23
+ "Programming Language :: Python :: 3.9",
24
+ "Programming Language :: Python :: 3.10",
25
+ "Programming Language :: Python :: 3.11",
26
+ "Programming Language :: Python :: 3.12",
27
+ "Operating System :: OS Independent",
28
+ "Topic :: Security",
29
+ ]
30
+ dependencies = [
31
+ "smart-dynamic-path>=1.0.1",
32
+ "Django>=3.2",
33
+ ]
34
+
35
+ [project.urls]
36
+ Homepage = "https://github.com/smartlegionlab/django-smart-dynamic-path"
37
+
38
+ [tool.setuptools.packages.find]
39
+ where = ["."]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+