├── .github └── workflows │ └── test-and-deploy.yaml ├── .gitignore ├── AUTHORS.txt ├── LICENSE ├── MANIFEST.in ├── README.rst ├── RELEASES.txt ├── admin_views ├── __init__.py ├── admin.py ├── compat.py ├── conf.py ├── models.py ├── static │ └── admin_views │ │ └── icons │ │ ├── link.png │ │ └── view.png ├── templates │ └── admin │ │ └── index.html ├── templatetags │ ├── __init__.py │ └── admin_views.py └── tests.py ├── pyproject.toml ├── screenshots └── admin.png └── test_project ├── Pipfile ├── Pipfile.lock ├── manage.py ├── myapp ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py ├── tests.py └── views.py └── test_project ├── __init__.py ├── asgi.py ├── settings.py ├── urls.py └── wsgi.py /.github/workflows/test-and-deploy.yaml: -------------------------------------------------------------------------------- 1 | name: Unit tests 2 | 3 | on: 4 | workflow_call: 5 | workflow_dispatch: 6 | push: 7 | branches: [master] 8 | tags: 9 | - '*' 10 | pull_request: 11 | branches: [master] 12 | 13 | jobs: 14 | test: 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | django-version: ["3.2", "4.0", "4.1"] 19 | python-version: ["3.9", "3.10"] 20 | steps: 21 | - name: Checkout code 22 | uses: actions/checkout@v3 23 | - name: Setup Python ${{ matrix.python-version }} 24 | uses: actions/setup-python@v4 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | - name: Install requirements 28 | run: | 29 | pip install django==${{ matrix.django-version }} 30 | pip install -e . 31 | - name: Run Tests 32 | run: | 33 | cd test_project 34 | python manage.py test 35 | 36 | publish: 37 | runs-on: ubuntu-latest 38 | needs: test 39 | steps: 40 | - name: Checkout code 41 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') 42 | uses: actions/checkout@v3 43 | - name: Install dependencies 44 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') 45 | run: pip install build twine 46 | - name: Build package 47 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') 48 | run: python -m build 49 | - name: Check package 50 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') 51 | run: twine check dist/* 52 | - name: Publish on pypi 53 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') 54 | uses: pypa/gh-action-pypi-publish@release/v1 55 | with: 56 | password: ${{ secrets.PYPI_API_TOKEN }} 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/python,macos,emacs,visualstudiocode 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=python,macos,emacs,visualstudiocode 3 | 4 | ### Emacs ### 5 | # -*- mode: gitignore; -*- 6 | *~ 7 | \#*\# 8 | /.emacs.desktop 9 | /.emacs.desktop.lock 10 | *.elc 11 | auto-save-list 12 | tramp 13 | .\#* 14 | 15 | # Org-mode 16 | .org-id-locations 17 | *_archive 18 | 19 | # flymake-mode 20 | *_flymake.* 21 | 22 | # eshell files 23 | /eshell/history 24 | /eshell/lastdir 25 | 26 | # elpa packages 27 | /elpa/ 28 | 29 | # reftex files 30 | *.rel 31 | 32 | # AUCTeX auto folder 33 | /auto/ 34 | 35 | # cask packages 36 | .cask/ 37 | dist/ 38 | 39 | # Flycheck 40 | flycheck_*.el 41 | 42 | # server auth directory 43 | /server/ 44 | 45 | # projectiles files 46 | .projectile 47 | 48 | # directory configuration 49 | .dir-locals.el 50 | 51 | # network security 52 | /network-security.data 53 | 54 | 55 | ### macOS ### 56 | # General 57 | .DS_Store 58 | .AppleDouble 59 | .LSOverride 60 | 61 | # Icon must end with two \r 62 | Icon 63 | 64 | 65 | # Thumbnails 66 | ._* 67 | 68 | # Files that might appear in the root of a volume 69 | .DocumentRevisions-V100 70 | .fseventsd 71 | .Spotlight-V100 72 | .TemporaryItems 73 | .Trashes 74 | .VolumeIcon.icns 75 | .com.apple.timemachine.donotpresent 76 | 77 | # Directories potentially created on remote AFP share 78 | .AppleDB 79 | .AppleDesktop 80 | Network Trash Folder 81 | Temporary Items 82 | .apdisk 83 | 84 | ### macOS Patch ### 85 | # iCloud generated files 86 | *.icloud 87 | 88 | ### Python ### 89 | # Byte-compiled / optimized / DLL files 90 | __pycache__/ 91 | *.py[cod] 92 | *$py.class 93 | 94 | # C extensions 95 | *.so 96 | 97 | # Distribution / packaging 98 | .Python 99 | build/ 100 | develop-eggs/ 101 | downloads/ 102 | eggs/ 103 | .eggs/ 104 | lib/ 105 | lib64/ 106 | parts/ 107 | sdist/ 108 | var/ 109 | wheels/ 110 | share/python-wheels/ 111 | *.egg-info/ 112 | .installed.cfg 113 | *.egg 114 | MANIFEST 115 | 116 | # PyInstaller 117 | # Usually these files are written by a python script from a template 118 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 119 | *.manifest 120 | *.spec 121 | 122 | # Installer logs 123 | pip-log.txt 124 | pip-delete-this-directory.txt 125 | 126 | # Unit test / coverage reports 127 | htmlcov/ 128 | .tox/ 129 | .nox/ 130 | .coverage 131 | .coverage.* 132 | .cache 133 | nosetests.xml 134 | coverage.xml 135 | *.cover 136 | *.py,cover 137 | .hypothesis/ 138 | .pytest_cache/ 139 | cover/ 140 | 141 | # Translations 142 | *.mo 143 | *.pot 144 | 145 | # Django stuff: 146 | *.log 147 | local_settings.py 148 | db.sqlite3 149 | db.sqlite3-journal 150 | 151 | # Flask stuff: 152 | instance/ 153 | .webassets-cache 154 | 155 | # Scrapy stuff: 156 | .scrapy 157 | 158 | # Sphinx documentation 159 | docs/_build/ 160 | 161 | # PyBuilder 162 | .pybuilder/ 163 | target/ 164 | 165 | # Jupyter Notebook 166 | .ipynb_checkpoints 167 | 168 | # IPython 169 | profile_default/ 170 | ipython_config.py 171 | 172 | # pyenv 173 | # For a library or package, you might want to ignore these files since the code is 174 | # intended to run in multiple environments; otherwise, check them in: 175 | # .python-version 176 | 177 | # pipenv 178 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 179 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 180 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 181 | # install all needed dependencies. 182 | #Pipfile.lock 183 | 184 | # poetry 185 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 186 | # This is especially recommended for binary packages to ensure reproducibility, and is more 187 | # commonly ignored for libraries. 188 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 189 | #poetry.lock 190 | 191 | # pdm 192 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 193 | #pdm.lock 194 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 195 | # in version control. 196 | # https://pdm.fming.dev/#use-with-ide 197 | .pdm.toml 198 | 199 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 200 | __pypackages__/ 201 | 202 | # Celery stuff 203 | celerybeat-schedule 204 | celerybeat.pid 205 | 206 | # SageMath parsed files 207 | *.sage.py 208 | 209 | # Environments 210 | .env 211 | .venv 212 | env/ 213 | venv/ 214 | ENV/ 215 | env.bak/ 216 | venv.bak/ 217 | 218 | # Spyder project settings 219 | .spyderproject 220 | .spyproject 221 | 222 | # Rope project settings 223 | .ropeproject 224 | 225 | # mkdocs documentation 226 | /site 227 | 228 | # mypy 229 | .mypy_cache/ 230 | .dmypy.json 231 | dmypy.json 232 | 233 | # Pyre type checker 234 | .pyre/ 235 | 236 | # pytype static type analyzer 237 | .pytype/ 238 | 239 | # Cython debug symbols 240 | cython_debug/ 241 | 242 | # PyCharm 243 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 244 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 245 | # and can be added to the global gitignore or merged into this file. For a more nuclear 246 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 247 | #.idea/ 248 | 249 | ### VisualStudioCode ### 250 | .vscode/* 251 | !.vscode/settings.json 252 | !.vscode/tasks.json 253 | !.vscode/launch.json 254 | !.vscode/extensions.json 255 | !.vscode/*.code-snippets 256 | 257 | # Local History for Visual Studio Code 258 | .history/ 259 | 260 | # Built Visual Studio Code Extensions 261 | *.vsix 262 | 263 | ### VisualStudioCode Patch ### 264 | # Ignore all local history of files 265 | .history 266 | .ionide 267 | 268 | # End of https://www.toptal.com/developers/gitignore/api/python,macos,emacs,visualstudiocode 269 | -------------------------------------------------------------------------------- /AUTHORS.txt: -------------------------------------------------------------------------------- 1 | django-admin-views was created by Frank Wiles 2 | and is now maintained by Hugo Defrance 3 | 4 | And contributions from: 5 | 6 | Takuya Wakisaka 7 | Michael Duane Mooring 8 | Kostya Esmukov 9 | Flavio Curella 10 | Ruthie BenDor 11 | Binoj David 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Hugo Defrance and individual contributors. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of django-admin-views nor the names of its contributors 15 | may be used to endorse or promote products derived from this software 16 | without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include AUTHORS.txt 3 | include LICENSE 4 | recursive-include admin_views/templates * 5 | recursive-include admin_views/static * 6 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Overview 2 | ======== 3 | 4 | While "the admin is not your app", it is often useful to be able to easily add 5 | a bit of functionality to the admin for internal staff or other internal users 6 | that are tech savvy enough to use the admin. 7 | 8 | There are several third party project such as 9 | `AdminPlus `_, but they require the 10 | user to redefine the Admin.site object. This is fine for developers who are 11 | setting up a Django project, but not ideal for developers who are writing 12 | third party tools for other developers to use in their projects. 13 | 14 | django-admin-views attempts to solve this by simply overriding the admin 15 | templates to provide two features: 16 | 17 | 1. Easily define custom admin views and link them on the admin pages 18 | 2. Easily add in external URL links 19 | 20 | Installation Steps 21 | ================== 22 | 23 | 1. ``pip install django-admin-views`` 24 | 25 | 2. Add ``admin_views`` to ``INSTALLED_APPS`` in your ``settings.py`` before admin site, i.e. ``django.contrib.admin`` 26 | 27 | If you are using a custom Admin Site, you'll need to configure the ``ADMIN_VIEWS_SITE`` setting to point to your admin site instance:: 28 | 29 | ADMIN_VIEWS_SITE = 'myproject.admin.admin_site' 30 | 31 | Usage 32 | ===== 33 | 34 | All of this magic happens in your model's admin definition. You simply subclass your 35 | admin from ``AdminViews`` instead of the standard ``admin.ModelAdmin``. 36 | In this example we have a custom view that does nothing but redirect the user to CNN 37 | and a direct URL link that goes to my company's homepage:: 38 | 39 | from django.contrib import admin 40 | from django.shortcuts import redirect 41 | 42 | from admin_views.admin import AdminViews 43 | 44 | from example_app.models import TestModel 45 | 46 | class TestAdmin(AdminViews): 47 | admin_views = ( 48 | ('Redirect to CNN', 'redirect_to_cnn'), 49 | ('Go to google.com', 'https:/google.com'), 50 | ) 51 | 52 | def redirect_to_cnn(self, *args, **kwargs): 53 | return redirect('https://www.cnn.com') 54 | 55 | admin.site.register(TestModel, TestAdmin) 56 | 57 | These will now show up in the admin below the usual Django admin model CRUD interfaces 58 | for `example_app` with a couple of different icons to distinquish between custom admin 59 | views and a direct URL link. 60 | 61 | With this third-party developers need only instruct their users to install their app 62 | and ``django-admin-views``. 63 | 64 | Hope you find it useful and as always feedback is certainly welcome. 65 | 66 | Screenshot 67 | ========== 68 | 69 | .. image:: https://raw.githubusercontent.com/koleror/django-admin-views/master/screenshots/admin.png 70 | 71 | Author 72 | ====== 73 | Frank Wiles frank@revsys.com 74 | 75 | Maintainer 76 | ========== 77 | Hugo Defrance defrance.hugo@gmail.com 78 | -------------------------------------------------------------------------------- /RELEASES.txt: -------------------------------------------------------------------------------- 1 | Version 1.0.3 2 | -------------- 3 | - Fix deployment pipeline 4 | 5 | Version 1.0.2 6 | -------------- 7 | - Fix deployment package 8 | 9 | Version 1.0.1 10 | -------------- 11 | - Fix deployment 12 | 13 | Version 1.0.0 14 | -------------- 15 | - Drop django<3 support 16 | - Adds support for django3.2 17 | - Updates project's ownership 18 | - Refacto project packaging 19 | 20 | Version 0.8.0 21 | -------------- 22 | - ? 23 | 24 | Version 0.7.0 25 | -------------- 26 | 27 | - Fix merge artifacts that were released 28 | 29 | Version 0.6.0 30 | ------------- 31 | 32 | - Better Django 1.8+ support 33 | - Removed unnecessary management command 34 | 35 | Version 0.5.0 36 | ------------- 37 | 38 | - Better Django 1.8.x support 39 | - Compatability fixes 40 | - Now supports only Django 1.6.x and newer 41 | 42 | Version 0.4.0 43 | ------------- 44 | 45 | - Django 1.8.x support 46 | 47 | Version 0.2.0 48 | ------------- 49 | 50 | - Unicode fixes 51 | - URL matching regexp fixes 52 | - Support for permission checking in views 53 | - Django 1.6 compatability 54 | 55 | Version 0.1.3 56 | ------------- 57 | 58 | - Bah forgot the icons. I'm obviously too tired to be coding today. 59 | Apologies for the release noise. 60 | 61 | Version 0.1.2 62 | ------------- 63 | 64 | - Fixed non-inclusion of admin templates. 65 | 66 | Version 0.1.1 67 | ------------- 68 | 69 | - Fixed non-inclusion of README.rst which setup.py needed. 70 | 71 | Initial release 0.1.0 72 | -------------------------------------------------------------------------------- /admin_views/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "1.0.3" 2 | -------------------------------------------------------------------------------- /admin_views/admin.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.contrib import admin 3 | from django.contrib.auth.decorators import permission_required 4 | from django.urls import re_path 5 | 6 | ADMIN_URL_PREFIX = getattr(settings, 'ADMIN_VIEWS_URL_PREFIX', '/admin') 7 | 8 | 9 | class AdminViews(admin.ModelAdmin): 10 | """ 11 | Standard admin subclass to handle easily adding views to 12 | the Django admin for an app 13 | """ 14 | 15 | def __init__(self, *args, **kwargs): 16 | super().__init__(*args, **kwargs) 17 | self.direct_links = [] 18 | self.local_view_names = [] 19 | self.output_urls = [] 20 | 21 | def get_urls(self): 22 | original_urls = super().get_urls() 23 | added_urls = [] 24 | 25 | for link in self.admin_views: 26 | if hasattr(self, link[1]): 27 | view_func = getattr(self, link[1]) 28 | if len(link) == 3: 29 | # View requires permission 30 | view_func = permission_required( 31 | link[2], raise_exception=True 32 | )(view_func) 33 | added_urls.append( 34 | re_path( 35 | r'^%s$' % link[1], 36 | name=link[1], 37 | view=self.admin_site.admin_view(view_func) 38 | ) 39 | ) 40 | self.local_view_names.append(link[0]) 41 | 42 | try: 43 | model_name = self.model._meta.model_name 44 | except AttributeError: 45 | model_name = self.model._meta.module_name # removed as of Django 1.8 46 | 47 | # Build URL from known info 48 | info = self.model._meta.app_label, model_name 49 | self.output_urls.append(( 50 | 'view', 51 | link[0], 52 | "%s/%s/%s/%s" % (ADMIN_URL_PREFIX, 53 | info[0], info[1], link[1]), 54 | link[2] if len(link) == 3 else None, 55 | )) 56 | else: 57 | self.direct_links.append(link) 58 | self.output_urls.append( 59 | ('url', link[0], link[1], link[2] if len(link) == 3 else None) 60 | ) 61 | 62 | return added_urls + original_urls 63 | -------------------------------------------------------------------------------- /admin_views/compat.py: -------------------------------------------------------------------------------- 1 | try: 2 | from django.utils.module_loading import import_string 3 | except ImportError: 4 | from django.utils.module_loading import import_by_path as import_string 5 | -------------------------------------------------------------------------------- /admin_views/conf.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | 4 | ADMIN_VIEWS_SITE = getattr(settings, 'ADMIN_VIEWS_SITE', 'django.contrib.admin.site') 5 | -------------------------------------------------------------------------------- /admin_views/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /admin_views/static/admin_views/icons/link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koleror/django-admin-views/0490e9bbc38dd9f171d56a0924102267f72eef6e/admin_views/static/admin_views/icons/link.png -------------------------------------------------------------------------------- /admin_views/static/admin_views/icons/view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koleror/django-admin-views/0490e9bbc38dd9f171d56a0924102267f72eef6e/admin_views/static/admin_views/icons/view.png -------------------------------------------------------------------------------- /admin_views/templates/admin/index.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/base_site.html" %} 2 | {% load i18n static admin_views %} 3 | 4 | {% block extrastyle %}{{ block.super }}{% endblock %} 5 | 6 | {% block coltype %}colMS{% endblock %} 7 | 8 | {% block bodyclass %}{{ block.super }} dashboard{% endblock %} 9 | 10 | {% block breadcrumbs %}{% endblock %} 11 | 12 | {% block content %} 13 |
14 | {% if app_list %} 15 | {% for app in app_list %} 16 |
17 | 18 | 21 | {% for model in app.models %} 22 | 23 | {% if model.admin_url %} 24 | 25 | {% else %} 26 | 27 | {% endif %} 28 | 29 | {% if model.add_url %} 30 | 31 | {% else %} 32 | 33 | {% endif %} 34 | 35 | {% if model.admin_url %} 36 | {% if model.view_only %} 37 | 38 | {% else %} 39 | 40 | {% endif %} 41 | {% else %} 42 | 43 | {% endif %} 44 | 45 | {% endfor %} 46 | {% get_admin_views app perms %} 47 |
19 | {{ app.name }} 20 |
{{ model.name }}{{ model.name }}{% trans 'Add' %} {% trans 'View' %}{% trans 'Change' %} 
48 |
49 | {% endfor %} 50 | {% else %} 51 |

{% trans 'You don’t have permission to view or edit anything.' %}

52 | {% endif %} 53 |
54 | {% endblock %} 55 | 56 | {% block sidebar %} 57 | 86 | {% endblock %} 87 | -------------------------------------------------------------------------------- /admin_views/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koleror/django-admin-views/0490e9bbc38dd9f171d56a0924102267f72eef6e/admin_views/templatetags/__init__.py -------------------------------------------------------------------------------- /admin_views/templatetags/admin_views.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from django import template 3 | from django.conf import settings 4 | from django.utils.safestring import mark_safe 5 | from django.utils.html import format_html_join 6 | 7 | from .. import conf 8 | from ..admin import AdminViews 9 | from ..compat import import_string 10 | 11 | site = import_string(conf.ADMIN_VIEWS_SITE) 12 | 13 | register = template.Library() 14 | 15 | 16 | if sys.version_info < (3,): 17 | import codecs 18 | 19 | def u(x): 20 | return codecs.unicode_escape_decode(x)[0] 21 | else: 22 | def u(x): 23 | return x 24 | 25 | 26 | @register.simple_tag 27 | def get_admin_views(app, perms): 28 | output = [] 29 | STATIC_URL = settings.STATIC_URL 30 | 31 | for k, v in site._registry.items(): 32 | app_name = app.get('app_label', app['name'].lower()) 33 | if app_name not in str(k._meta): 34 | continue 35 | 36 | if isinstance(v, AdminViews): 37 | for type, name, link, perm in v.output_urls: 38 | if perm and perm not in perms: 39 | continue 40 | if type == 'url': 41 | img_url = "%sadmin_views/icons/link.png" % STATIC_URL 42 | alt_text = "Link to '%s'" % name 43 | else: 44 | img_url = "%sadmin_views/icons/view.png" % STATIC_URL 45 | alt_text = "Custom admin view '%s'" % name 46 | 47 | output.append(list(map(u, (img_url, alt_text, link, name)))) 48 | 49 | output = sorted(output, key=lambda x: x[3]) 50 | 51 | return mark_safe( 52 | format_html_join( 53 | u(''), 54 | u(""" 55 | 56 | {} 57 | {} 58 |   59 |   60 | 61 | """), output) 62 | ) 63 | -------------------------------------------------------------------------------- /admin_views/tests.py: -------------------------------------------------------------------------------- 1 | import os 2 | from shutil import rmtree 3 | 4 | import sys 5 | from django.test import TestCase 6 | from django.conf import settings 7 | from django.contrib.auth.models import User 8 | from django.core.management import call_command 9 | 10 | 11 | if sys.version_info < (3,): 12 | def b(x): 13 | return x 14 | else: 15 | def b(x): 16 | return x.encode("ascii") 17 | 18 | 19 | class AdminViewsTests(TestCase): 20 | """ Test django-admin-views """ 21 | 22 | def setUp(self): 23 | # Create a superuser to login as 24 | self.superuser = User.objects.create_superuser('frank', 'frank@revsys.com', 'pass') 25 | 26 | def test_urls_showup(self): 27 | self.client.login(username='frank', password='pass') 28 | response = self.client.get('/admin/') 29 | self.assertEqual(response.status_code, 200) 30 | 31 | # Make sure our links and images show up 32 | self.assertTrue(b('/static/admin_views/icons/view.png') in response.content) 33 | self.assertTrue(b('/static/admin_views/icons/link.png') in response.content) 34 | self.assertTrue(b('/admin/example_app/testmodel/process') in response.content) 35 | self.assertTrue(b('http://www.ljworld.com') in response.content) 36 | 37 | # Test that we can go to the URLs 38 | response = self.client.get('/admin/example_app/testmodel/process') 39 | self.assertEqual(response.status_code, 302) 40 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=61.0.0", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "django-admin-views" 7 | version = "1.0.3" 8 | description = "django-admin-views is a simple way to add custom admin views and direct URLs to the Django admin" 9 | readme = "README.rst" 10 | authors = [{ name = "Hugo Defrance", email = "defrance.hugo@gmail.com" }] 11 | license = { file = "LICENSE" } 12 | classifiers = [ 13 | "Environment :: Web Environment", 14 | "Intended Audience :: Developers", 15 | "License :: OSI Approved :: BSD License", 16 | 'Operating System :: OS Independent', 17 | 'Programming Language :: Python', 18 | 'Framework :: Django', 19 | ] 20 | keywords = ["django", "admin"] 21 | dependencies = [ 22 | "django >= 3.2", 23 | ] 24 | 25 | requires-python = ">=3.9" 26 | 27 | [project.optional-dependencies] 28 | dev = ["black", "bumpver", "isort"] 29 | 30 | [project.urls] 31 | Homepage = "https://github.com/koleror/django-admin-views" 32 | 33 | [tool.setuptools] 34 | packages = ["admin_views"] 35 | 36 | [tool.bumpver] 37 | current_version = "1.0.3" 38 | version_pattern = "MAJOR.MINOR.PATCH" 39 | commit_message = "bump version {old_version} -> {new_version}" 40 | commit = true 41 | tag = true 42 | push = true 43 | 44 | [tool.bumpver.file_patterns] 45 | "pyproject.toml" = [ 46 | 'version = "{version}"', 47 | ] 48 | "admin_views/__init__.py" = ["{version}"] 49 | -------------------------------------------------------------------------------- /screenshots/admin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koleror/django-admin-views/0490e9bbc38dd9f171d56a0924102267f72eef6e/screenshots/admin.png -------------------------------------------------------------------------------- /test_project/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | django = ">=3,<4" 8 | django-admin-views = "*" 9 | 10 | [dev-packages] 11 | ipython = "*" 12 | pdbpp = "*" 13 | django-non-dark-admin = "*" 14 | 15 | [requires] 16 | python_version = "3.10" 17 | 18 | [scripts] 19 | shell = "python manage.py shell" 20 | server = "python manage.py runserver" 21 | migrate = "python manage.py migrate" 22 | -------------------------------------------------------------------------------- /test_project/Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "f183958830f6a5dc79f9234e50c83e8fcd3004ce36a859e97078e62c0273d850" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.10" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "asgiref": { 20 | "hashes": [ 21 | "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4", 22 | "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424" 23 | ], 24 | "markers": "python_version >= '3.7'", 25 | "version": "==3.5.2" 26 | }, 27 | "django": { 28 | "hashes": [ 29 | "sha256:18ba8efa36b69cfcd4b670d0fa187c6fe7506596f0ababe580e16909bcdec121", 30 | "sha256:3adc285124244724a394fa9b9839cc8cd116faf7d159554c43ecdaa8cdf0b94d" 31 | ], 32 | "index": "pypi", 33 | "version": "==3.2.16" 34 | }, 35 | "django-admin-views": { 36 | "hashes": [ 37 | "sha256:cf3f54c030cb20f1e11f42e80d68022ba020326438c44fcb15edb11487934fb8" 38 | ], 39 | "index": "pypi", 40 | "version": "==0.8.0" 41 | }, 42 | "pytz": { 43 | "hashes": [ 44 | "sha256:222439474e9c98fced559f1709d89e6c9cbf8d79c794ff3eb9f8800064291427", 45 | "sha256:e89512406b793ca39f5971bc999cc538ce125c0e51c27941bef4568b460095e2" 46 | ], 47 | "version": "==2022.6" 48 | }, 49 | "sqlparse": { 50 | "hashes": [ 51 | "sha256:0323c0ec29cd52bceabc1b4d9d579e311f3e4961b98d174201d5622a23b85e34", 52 | "sha256:69ca804846bb114d2ec380e4360a8a340db83f0ccf3afceeb1404df028f57268" 53 | ], 54 | "markers": "python_version >= '3.5'", 55 | "version": "==0.4.3" 56 | } 57 | }, 58 | "develop": { 59 | "appnope": { 60 | "hashes": [ 61 | "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24", 62 | "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e" 63 | ], 64 | "markers": "sys_platform == 'darwin'", 65 | "version": "==0.1.3" 66 | }, 67 | "asgiref": { 68 | "hashes": [ 69 | "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4", 70 | "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424" 71 | ], 72 | "markers": "python_version >= '3.7'", 73 | "version": "==3.5.2" 74 | }, 75 | "asttokens": { 76 | "hashes": [ 77 | "sha256:1b28ed85e254b724439afc783d4bee767f780b936c3fe8b3275332f42cf5f561", 78 | "sha256:4aa76401a151c8cc572d906aad7aea2a841780834a19d780f4321c0fe1b54635" 79 | ], 80 | "version": "==2.1.0" 81 | }, 82 | "backcall": { 83 | "hashes": [ 84 | "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e", 85 | "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255" 86 | ], 87 | "version": "==0.2.0" 88 | }, 89 | "decorator": { 90 | "hashes": [ 91 | "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330", 92 | "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186" 93 | ], 94 | "markers": "python_version >= '3.5'", 95 | "version": "==5.1.1" 96 | }, 97 | "django": { 98 | "hashes": [ 99 | "sha256:18ba8efa36b69cfcd4b670d0fa187c6fe7506596f0ababe580e16909bcdec121", 100 | "sha256:3adc285124244724a394fa9b9839cc8cd116faf7d159554c43ecdaa8cdf0b94d" 101 | ], 102 | "index": "pypi", 103 | "version": "==3.2.16" 104 | }, 105 | "django-non-dark-admin": { 106 | "hashes": [ 107 | "sha256:89ed8e16d2a94304c9e9fe6d7a0ba38d08f9902d0d003e4c4ec28a682fb3df58", 108 | "sha256:a0c616a5d7be761ae118c569c96e554be5c6486f0e6d31d842e16f9b51be64ad" 109 | ], 110 | "index": "pypi", 111 | "version": "==2.0.2" 112 | }, 113 | "executing": { 114 | "hashes": [ 115 | "sha256:0314a69e37426e3608aada02473b4161d4caf5a4b244d1d0c48072b8fee7bacc", 116 | "sha256:19da64c18d2d851112f09c287f8d3dbbdf725ab0e569077efb6cdcbd3497c107" 117 | ], 118 | "version": "==1.2.0" 119 | }, 120 | "fancycompleter": { 121 | "hashes": [ 122 | "sha256:09e0feb8ae242abdfd7ef2ba55069a46f011814a80fe5476be48f51b00247272", 123 | "sha256:dd076bca7d9d524cc7f25ec8f35ef95388ffef9ef46def4d3d25e9b044ad7080" 124 | ], 125 | "version": "==0.9.1" 126 | }, 127 | "ipython": { 128 | "hashes": [ 129 | "sha256:7c959e3dedbf7ed81f9b9d8833df252c430610e2a4a6464ec13cd20975ce20a5", 130 | "sha256:91ef03016bcf72dd17190f863476e7c799c6126ec7e8be97719d1bc9a78a59a4" 131 | ], 132 | "index": "pypi", 133 | "version": "==8.6.0" 134 | }, 135 | "jedi": { 136 | "hashes": [ 137 | "sha256:637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d", 138 | "sha256:74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab" 139 | ], 140 | "markers": "python_version >= '3.6'", 141 | "version": "==0.18.1" 142 | }, 143 | "matplotlib-inline": { 144 | "hashes": [ 145 | "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311", 146 | "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304" 147 | ], 148 | "markers": "python_version >= '3.5'", 149 | "version": "==0.1.6" 150 | }, 151 | "parso": { 152 | "hashes": [ 153 | "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0", 154 | "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75" 155 | ], 156 | "markers": "python_version >= '3.6'", 157 | "version": "==0.8.3" 158 | }, 159 | "pdbpp": { 160 | "hashes": [ 161 | "sha256:79580568e33eb3d6f6b462b1187f53e10cd8e4538f7d31495c9181e2cf9665d1", 162 | "sha256:d9e43f4fda388eeb365f2887f4e7b66ac09dce9b6236b76f63616530e2f669f5" 163 | ], 164 | "index": "pypi", 165 | "version": "==0.10.3" 166 | }, 167 | "pexpect": { 168 | "hashes": [ 169 | "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937", 170 | "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c" 171 | ], 172 | "markers": "sys_platform != 'win32'", 173 | "version": "==4.8.0" 174 | }, 175 | "pickleshare": { 176 | "hashes": [ 177 | "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca", 178 | "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56" 179 | ], 180 | "version": "==0.7.5" 181 | }, 182 | "prompt-toolkit": { 183 | "hashes": [ 184 | "sha256:9696f386133df0fc8ca5af4895afe5d78f5fcfe5258111c2a79a1c3e41ffa96d", 185 | "sha256:9ada952c9d1787f52ff6d5f3484d0b4df8952787c087edf6a1f7c2cb1ea88148" 186 | ], 187 | "markers": "python_full_version >= '3.6.2'", 188 | "version": "==3.0.31" 189 | }, 190 | "ptyprocess": { 191 | "hashes": [ 192 | "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", 193 | "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220" 194 | ], 195 | "version": "==0.7.0" 196 | }, 197 | "pure-eval": { 198 | "hashes": [ 199 | "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350", 200 | "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3" 201 | ], 202 | "version": "==0.2.2" 203 | }, 204 | "pygments": { 205 | "hashes": [ 206 | "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1", 207 | "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42" 208 | ], 209 | "markers": "python_version >= '3.6'", 210 | "version": "==2.13.0" 211 | }, 212 | "pyrepl": { 213 | "hashes": [ 214 | "sha256:292570f34b5502e871bbb966d639474f2b57fbfcd3373c2d6a2f3d56e681a775" 215 | ], 216 | "version": "==0.9.0" 217 | }, 218 | "six": { 219 | "hashes": [ 220 | "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", 221 | "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" 222 | ], 223 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 224 | "version": "==1.16.0" 225 | }, 226 | "sqlparse": { 227 | "hashes": [ 228 | "sha256:0323c0ec29cd52bceabc1b4d9d579e311f3e4961b98d174201d5622a23b85e34", 229 | "sha256:69ca804846bb114d2ec380e4360a8a340db83f0ccf3afceeb1404df028f57268" 230 | ], 231 | "markers": "python_version >= '3.5'", 232 | "version": "==0.4.3" 233 | }, 234 | "stack-data": { 235 | "hashes": [ 236 | "sha256:8e515439f818efaa251036af72d89e4026e2b03993f3453c000b200fb4f2d6aa", 237 | "sha256:b92d206ef355a367d14316b786ab41cb99eb453a21f2cb216a4204625ff7bc07" 238 | ], 239 | "version": "==0.6.0" 240 | }, 241 | "traitlets": { 242 | "hashes": [ 243 | "sha256:1201b2c9f76097195989cdf7f65db9897593b0dfd69e4ac96016661bb6f0d30f", 244 | "sha256:b122f9ff2f2f6c1709dab289a05555be011c87828e911c0cf4074b85cb780a79" 245 | ], 246 | "markers": "python_version >= '3.7'", 247 | "version": "==5.5.0" 248 | }, 249 | "wcwidth": { 250 | "hashes": [ 251 | "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784", 252 | "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83" 253 | ], 254 | "version": "==0.2.5" 255 | }, 256 | "wmctrl": { 257 | "hashes": [ 258 | "sha256:66cbff72b0ca06a22ec3883ac3a4d7c41078bdae4fb7310f52951769b10e14e0" 259 | ], 260 | "version": "==0.4" 261 | } 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /test_project/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'test_project.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /test_project/myapp/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koleror/django-admin-views/0490e9bbc38dd9f171d56a0924102267f72eef6e/test_project/myapp/__init__.py -------------------------------------------------------------------------------- /test_project/myapp/admin.py: -------------------------------------------------------------------------------- 1 | from admin_views.admin import AdminViews 2 | from django.contrib import admin 3 | from django.shortcuts import redirect 4 | 5 | from .models import TestModel 6 | 7 | 8 | class TestAdmin(AdminViews): 9 | admin_views = ( 10 | ('Redirect to CNN', 'redirect_to_cnn'), # Admin view 11 | ('Go to google', 'https://google.com'), # Direct url 12 | ) 13 | 14 | def redirect_to_cnn(self, *args, **kwargs): 15 | return redirect('https://www.cnn.com') 16 | 17 | 18 | admin.site.register(TestModel, TestAdmin) 19 | -------------------------------------------------------------------------------- /test_project/myapp/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class MyappConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'myapp' 7 | -------------------------------------------------------------------------------- /test_project/myapp/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.16 on 2022-11-01 11:22 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='TestModel', 16 | fields=[ 17 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ], 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /test_project/myapp/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koleror/django-admin-views/0490e9bbc38dd9f171d56a0924102267f72eef6e/test_project/myapp/migrations/__init__.py -------------------------------------------------------------------------------- /test_project/myapp/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class TestModel(models.Model): 5 | pass 6 | -------------------------------------------------------------------------------- /test_project/myapp/tests.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model 2 | from django.test import TestCase 3 | 4 | 5 | class AdminTestCase(TestCase): 6 | @classmethod 7 | def setUpTestData(cls): 8 | cls.user = get_user_model().objects.create_superuser( 9 | username='foo', 10 | password='bar' 11 | ) 12 | 13 | def test_display_admin(self): 14 | self.client.force_login(self.user) 15 | response = self.client.get('/admin/') 16 | 17 | self.assertEqual(response.status_code, 200) 18 | self.assertInHTML( 19 | """ 20 | 21 | Link to 'Go to google' 22 | Go to google 23 | """, 24 | response.rendered_content 25 | ) 26 | self.assertInHTML( 27 | """ 28 | 29 | Custom admin view 'Redirect to CNN' 31 | Redirect to CNN 32 | """, 33 | response.rendered_content 34 | ) 35 | -------------------------------------------------------------------------------- /test_project/myapp/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /test_project/test_project/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koleror/django-admin-views/0490e9bbc38dd9f171d56a0924102267f72eef6e/test_project/test_project/__init__.py -------------------------------------------------------------------------------- /test_project/test_project/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for test_project project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'test_project.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /test_project/test_project/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for test_project project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.2.16. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.2/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | 15 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 16 | BASE_DIR = Path(__file__).resolve().parent.parent 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = 'django-insecure-n_5k9!zy7#&-&q@u+5hm!iq^x%0jok&ww!#7p7sd&vk2dz(83x' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'admin_views', 35 | # 'django_non_dark_admin', 36 | 'django.contrib.admin', 37 | 'django.contrib.auth', 38 | 'django.contrib.contenttypes', 39 | 'django.contrib.sessions', 40 | 'django.contrib.messages', 41 | 'django.contrib.staticfiles', 42 | 43 | 'myapp', 44 | ] 45 | 46 | MIDDLEWARE = [ 47 | 'django.middleware.security.SecurityMiddleware', 48 | 'django.contrib.sessions.middleware.SessionMiddleware', 49 | 'django.middleware.common.CommonMiddleware', 50 | 'django.middleware.csrf.CsrfViewMiddleware', 51 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 52 | 'django.contrib.messages.middleware.MessageMiddleware', 53 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 54 | ] 55 | 56 | ROOT_URLCONF = 'test_project.urls' 57 | 58 | TEMPLATES = [ 59 | { 60 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 61 | 'DIRS': [], 62 | 'APP_DIRS': True, 63 | 'OPTIONS': { 64 | 'context_processors': [ 65 | 'django.template.context_processors.debug', 66 | 'django.template.context_processors.request', 67 | 'django.contrib.auth.context_processors.auth', 68 | 'django.contrib.messages.context_processors.messages', 69 | ], 70 | }, 71 | }, 72 | ] 73 | 74 | WSGI_APPLICATION = 'test_project.wsgi.application' 75 | 76 | 77 | # Database 78 | # https://docs.djangoproject.com/en/3.2/ref/settings/#databases 79 | 80 | DATABASES = { 81 | 'default': { 82 | 'ENGINE': 'django.db.backends.sqlite3', 83 | 'NAME': BASE_DIR / 'db.sqlite3', 84 | } 85 | } 86 | 87 | 88 | # Password validation 89 | # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators 90 | 91 | AUTH_PASSWORD_VALIDATORS = [ 92 | { 93 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 94 | }, 95 | { 96 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 97 | }, 98 | { 99 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 100 | }, 101 | { 102 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 103 | }, 104 | ] 105 | 106 | 107 | # Internationalization 108 | # https://docs.djangoproject.com/en/3.2/topics/i18n/ 109 | 110 | LANGUAGE_CODE = 'en-us' 111 | 112 | TIME_ZONE = 'UTC' 113 | 114 | USE_I18N = True 115 | 116 | USE_L10N = True 117 | 118 | USE_TZ = True 119 | 120 | 121 | # Static files (CSS, JavaScript, Images) 122 | # https://docs.djangoproject.com/en/3.2/howto/static-files/ 123 | 124 | STATIC_URL = '/static/' 125 | 126 | # Default primary key field type 127 | # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field 128 | 129 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' 130 | 131 | DISABLE_DARK_MODE = True 132 | -------------------------------------------------------------------------------- /test_project/test_project/urls.py: -------------------------------------------------------------------------------- 1 | """test_project URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/3.2/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path 18 | 19 | urlpatterns = [ 20 | path('admin/', admin.site.urls), 21 | ] 22 | -------------------------------------------------------------------------------- /test_project/test_project/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for test_project project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'test_project.settings') 15 | 16 | application = get_wsgi_application() 17 | --------------------------------------------------------------------------------