├── .gitignore ├── AUTHORS.rst ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.md ├── admin_shortcuts ├── __init__.py ├── templates │ ├── admin │ │ └── index.html │ └── admin_shortcuts │ │ ├── base.css │ │ ├── base.html │ │ ├── shortcut.html │ │ └── style.css └── templatetags │ ├── __init__.py │ └── admin_shortcuts_tags.py ├── example ├── README.md ├── db.sqlite3 ├── django-admin-shortcuts-screenshot.png ├── example │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ ├── utils.py │ └── wsgi.py ├── manage.py └── requirements.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | dist 3 | *egg-info* 4 | /tmp 5 | .venv -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | Authors 2 | ======= 3 | 4 | * Ales Kocjancic 5 | * Benjamin Wohlwend 6 | * Venelin Stoykov 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Ales Kocjancic 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of django-admin-shortcuts nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL ALES KOCJANCIC BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.md 3 | recursive-include admin_shortcuts/templates * -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROJECT = "admin_shortcuts" 2 | 3 | current-version: 4 | @echo "Current version is `cat ${PROJECT}/__init__.py | awk -F '("|")' '{ print($$2)}'`" 5 | 6 | build: 7 | git stash 8 | python setup.py sdist 9 | - git stash pop 10 | 11 | upload: 12 | git stash 13 | python setup.py sdist 14 | twine upload dist/django-admin-shortcuts-`cat ${PROJECT}/__init__.py | awk -F '("|")' '{ print($$2)}'`.tar.gz 15 | - git stash pop 16 | 17 | git-release: 18 | git add ${PROJECT}/__init__.py 19 | git commit -m "Bumped version to `cat ${PROJECT}/__init__.py | awk -F '("|")' '{ print($$2)}'`" 20 | git tag `cat ${PROJECT}/__init__.py | awk -F '("|")' '{ print($$2)}'` 21 | git push 22 | git push --tags 23 | 24 | _release-patch: 25 | @echo "version = \"`cat ${PROJECT}/__init__.py | awk -F '("|")' '{ print($$2)}' | awk -F. '{$$NF = $$NF + 1;} 1' | sed 's/ /./g'`\"" > ${PROJECT}/__init__.py 26 | release-patch: _release-patch git-release build upload current-version 27 | 28 | _release-minor: 29 | @echo "version = \"`cat ${PROJECT}/__init__.py | awk -F '("|")' '{ print($$2)}' | awk -F. '{$$(NF-1) = $$(NF-1) + 1;} 1' | sed 's/ /./g' | awk -F. '{$$(NF) = 0;} 1' | sed 's/ /./g' `\"" > ${PROJECT}/__init__.py 30 | release-minor: _release-minor git-release build upload current-version 31 | 32 | _release-major: 33 | @echo "version = \"`cat ${PROJECT}/__init__.py | awk -F '("|")' '{ print($$2)}' | awk -F. '{$$(NF-2) = $$(NF-2) + 1;} 1' | sed 's/ /./g' | awk -F. '{$$(NF-1) = 0;} 1' | sed 's/ /./g' | awk -F. '{$$(NF) = 0;} 1' | sed 's/ /./g' `\"" > ${PROJECT}/__init__.py 34 | release-major: _release-major git-release build upload current-version 35 | 36 | release: release-patch 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Django Admin Shortcuts 2 | 3 | ![image](https://raw.githubusercontent.com/alesdotio/django-admin-shortcuts/3.0.0/example/django-admin-shortcuts-screenshot.png) 4 | 5 | 6 | ## What is this? 7 | 8 | It's a simple dashboard app that adds shortcuts to your Django admin homepage. The keyword here is SIMPLE! 9 | 10 | 11 | ## Why does it exist? 12 | 13 | Because some people noted that it's sometimes hard to find the app you are looking for on the admin homepage. 14 | 15 | > "So why don't we customize the admin site a bit?" 16 | 17 | > "Nah, I don't want to go through all the hassle of editing templates or setting up a complex dashboard app ..." 18 | 19 | Well, good thing django-admin-shortcuts is here, because it only takes five minutes of your time to go from the old 20 | dreadfully boring admin to the marvelous engineering excellence that is this app. 21 | 22 | 23 | ## How do I use it? 24 | 25 | 1) `pip install django-admin-shortcuts` 26 | 27 | 2) add `'admin_shortcuts'` to your `INSTALLED_APPS`, just before `'django.contrib.admin'` **<-- IMPORTANT** 28 | 29 | 3) add `ADMIN_SHORTCUTS` to your settings 30 | 31 | For example: 32 | 33 | ``` 34 | ADMIN_SHORTCUTS = [ 35 | { 36 | 'title': 'Authentication', 37 | 'shortcuts': [ 38 | { 39 | 'title': 'Groups', 40 | 'url_name': 'admin:auth_group_changelist', 41 | 'has_perms': ['example.change_group', 'example.delete_group'], 42 | }, 43 | { 44 | 'title': 'Add user', 45 | 'url_name': 'admin:auth_user_add', 46 | 'test_func': 'example.utils.has_perms_to_users', 47 | }, 48 | ] 49 | }, 50 | ] 51 | ``` 52 | 53 | Where ... 54 | 55 | * required `url_name` is a name that will be resolved using Django's reverse url method (see Django docs https://docs.djangoproject.com/en/5.0/ref/contrib/admin/#admin-reverse-urls) 56 | * optional `app_name` is the name of the admin app that will be used for URL reversal. You can safely ignore this if you have only one admin site in your ``urls.py`` 57 | * optional `url` is a direct link that will override `url_name` 58 | * optional `url_extra` is extra stuff to be attached at the end of the url (like GET data for pre-filtering admin views) 59 | * optional `title` is the title of the shortcut 60 | * optional `count` and `count_new` are paths to a function inside your project that returns something interesting (like a count of all products or a count of all pending orders). 61 | The function can optionally take one argument, `request`, which is the current Django `HttpRequest` object. 62 | * optional `test_func` is a path to a function inside your project that returns a boolean. If True the shortcut is displayed. 63 | Like above, this function can optionally take one argument `request` as well. 64 | * optional `has_perms` is a list of strings representing the built-in admin permissions required to display the shortcut. 65 | * optional `open_new_window` sets whether the link should open in a new window (default is False) 66 | * optional `icon` is an emoji (if you don't specify one, magical ponies will do it for you) 67 | 68 | 4) profit!! 69 | 70 | 5) optionally, also add ``ADMIN_SHORTCUTS_SETTINGS`` to your settings 71 | 72 | ``` 73 | ADMIN_SHORTCUTS_SETTINGS = { 74 | 'open_new_window': False, 75 | } 76 | ``` 77 | 78 | 79 | Where ... 80 | 81 | * optional `open_new_window` makes all shortcuts open in a new window 82 | 83 | 84 | ## What are the settings used in the pretty image above? 85 | 86 | ``` 87 | ADMIN_SHORTCUTS = [ 88 | { 89 | 'shortcuts': [ 90 | { 91 | 'url': '/', 92 | 'open_new_window': True, 93 | }, 94 | { 95 | 'url_name': 'admin:logout', 96 | }, 97 | { 98 | 'title': 'Users', 99 | 'url_name': 'admin:auth_user_changelist', 100 | 'count': 'example.utils.count_users', 101 | }, 102 | { 103 | 'title': 'Groups', 104 | 'url_name': 'admin:auth_group_changelist', 105 | 'count': 'example.utils.count_groups', 106 | 'has_perms': ['example.change_group', 'example.delete_group'], 107 | }, 108 | { 109 | 'title': 'Add user', 110 | 'url_name': 'admin:auth_user_add', 111 | 'test_func': 'example.utils.has_perms_to_users', 112 | 'has_perms': 'example.utils.has_perms_to_users', 113 | }, 114 | ] 115 | }, 116 | { 117 | 'title': 'CMS', 118 | 'shortcuts': [ 119 | { 120 | 'title': 'Pages', 121 | 'url_name': 'admin:index', 122 | }, 123 | { 124 | 'title': 'Files', 125 | 'url_name': 'admin:index', 126 | 'icon': '❤️' 127 | }, 128 | { 129 | 'title': 'Contact forms', 130 | 'url_name': 'admin:index', 131 | 'count_new': '3', 132 | }, 133 | { 134 | 'title': 'Products', 135 | 'url_name': 'admin:index', 136 | }, 137 | { 138 | 'title': 'Orders', 139 | 'url_name': 'admin:index', 140 | 'count_new': '12', 141 | }, 142 | ] 143 | }, 144 | ] 145 | ADMIN_SHORTCUTS_SETTINGS = { 146 | 'open_new_window': False, 147 | } 148 | ``` 149 | 150 | 151 | ## I want to change how stuff looks 152 | 153 | * to change the CSS overwrite the ``templates/admin_shortcuts/base.css`` template 154 | * to change the which icons are magically selected specify the mappings in ``ADMIN_SHORTCUTS_ICON_MAPPINGS`` 155 | 156 | -------------------------------------------------------------------------------- /admin_shortcuts/__init__.py: -------------------------------------------------------------------------------- 1 | version = "3.0.1" 2 | -------------------------------------------------------------------------------- /admin_shortcuts/templates/admin/index.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/index.html" %} 2 | {% load admin_shortcuts_tags %} 3 | 4 | {% block nav-breadcrumbs %} 5 |
6 | {% admin_shortcuts %} 7 |
8 | {{ block.super }} 9 | {% endblock %} 10 | 11 | 12 | {% block extrastyle %} 13 | {{ block.super }} 14 | 15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /admin_shortcuts/templates/admin_shortcuts/base.css: -------------------------------------------------------------------------------- 1 | {% block main %}{% endblock %} 2 | {% block count %}{% endblock %} 3 | {% block icons %}{% endblock %} 4 | {% block dark_mode %}{% endblock %} 5 | {% block extra %}{% endblock %} -------------------------------------------------------------------------------- /admin_shortcuts/templates/admin_shortcuts/base.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | 3 | {% if enable_admin_shortcuts %} 4 | {% if admin_shortcuts %} 5 | 15 | {% endif %} 16 | {% endif %} -------------------------------------------------------------------------------- /admin_shortcuts/templates/admin_shortcuts/shortcut.html: -------------------------------------------------------------------------------- 1 | 2 | {% if shortcut.icon %}{{ shortcut.icon }}{% endif %} 3 | {% if shortcut.title %}{{ shortcut.title }}{% endif %} 4 | {% if shortcut.count != None %}{{ shortcut.count }}{% endif %} 5 | {% if shortcut.count_new %}{{ shortcut.count_new }}{% endif %} 6 | -------------------------------------------------------------------------------- /admin_shortcuts/templates/admin_shortcuts/style.css: -------------------------------------------------------------------------------- 1 | {% extends 'admin_shortcuts/base.css' %} 2 | {% load static admin_shortcuts_tags %} 3 | 4 | {% block main %} 5 | /* shortcuts */ 6 | .admin_shortcuts .shortcuts { 7 | padding: 10px 40px 10px 40px; 8 | margin: 0; 9 | list-style: none; 10 | } 11 | 12 | @media (max-width: 1024px) { 13 | .admin_shortcuts .shortcuts { 14 | padding-left: 20px; 15 | padding-right: 20px; 16 | } 17 | } 18 | 19 | .admin_shortcuts .shortcuts ul:after { 20 | content: "."; 21 | display: block; 22 | clear: both; 23 | visibility: hidden; 24 | line-height: 0; 25 | height: 0; 26 | } 27 | 28 | .admin_shortcuts .shortcuts ul { 29 | padding: 0; 30 | margin: 5px 0 0 0; 31 | list-style: none; 32 | } 33 | 34 | .admin_shortcuts .shortcuts h2 { 35 | font-weight: normal; 36 | color: #666; 37 | line-height: 24px; 38 | margin: 10px 0 5px; 39 | } 40 | 41 | .admin_shortcuts .shortcuts li { 42 | padding: 0; 43 | margin: 0; 44 | float: left; 45 | list-style: none; 46 | } 47 | 48 | .admin_shortcuts .shortcuts li a { 49 | padding: 20px 0px 20px 55px; 50 | margin: 5px 10px 5px 0; 51 | display: block; 52 | float: left; 53 | color: #666; 54 | border: 1px solid #ddd; 55 | background: #fff; 56 | font-size: 14px; 57 | line-height: 14px; 58 | border-radius: 4px; 59 | position: relative; 60 | min-height: 14px; 61 | transition: none; 62 | } 63 | 64 | .admin_shortcuts .shortcuts li a .title { 65 | padding-right: 25px; 66 | } 67 | 68 | .admin_shortcuts .shortcuts li a:hover, .admin_shortcuts .shortcuts li a:focus, .admin_shortcuts .shortcuts li a:active { 69 | border-color: #888; 70 | color: #333; 71 | text-decoration: none; 72 | } 73 | {% endblock %} 74 | 75 | 76 | {% block count %} 77 | /* count */ 78 | .admin_shortcuts .shortcuts li a .count { 79 | position: absolute; 80 | right: 3px; 81 | bottom: 2px; 82 | color: #aaa; 83 | font-size: 10px; 84 | } 85 | 86 | .admin_shortcuts .shortcuts li a .count_new { 87 | position: absolute; 88 | right: -7px; 89 | top: -7px; 90 | color: #fff; 91 | font-size: 11px; 92 | line-height: 14px; 93 | border-radius: 30px; 94 | display: block; 95 | overflow: hidden; 96 | max-width: 60px; 97 | min-width: 15px; 98 | height: 20px; 99 | padding: 5px 5px 0 5px; 100 | background: #940f3a; 101 | text-align: center; 102 | font-weight: bold; 103 | } 104 | {% endblock %} 105 | 106 | 107 | {% block icons %} 108 | /* icons */ 109 | .admin_shortcuts .shortcuts li a .icon { 110 | width: 34px; 111 | height: 32px; 112 | text-align: center; 113 | font-size: 24px; 114 | line-height: 32px; 115 | position: absolute; 116 | left: 10px; 117 | top: 10px; 118 | } 119 | {% endblock %} 120 | 121 | 122 | {% block dark_mode %} 123 | @media (prefers-color-scheme: dark) { 124 | .admin_shortcuts .shortcuts li a { 125 | background: #333; 126 | color: #ccc; 127 | border-color: #444; 128 | } 129 | .admin_shortcuts .shortcuts li a:hover, .admin_shortcuts .shortcuts li a:focus, .admin_shortcuts .shortcuts li a:active { 130 | border-color: #888; 131 | color: #fff; 132 | } 133 | } 134 | @media (prefers-color-scheme: light) { 135 | .admin_shortcuts .shortcuts li a { 136 | background: #fff; 137 | color: #666; 138 | border-color: #ddd; 139 | } 140 | .admin_shortcuts .shortcuts li a:hover, .admin_shortcuts .shortcuts li a:focus, .admin_shortcuts .shortcuts li a:active { 141 | border-color: #888; 142 | color: #333; 143 | } 144 | } 145 | html[data-theme="dark"] { 146 | .admin_shortcuts .shortcuts li a { 147 | background: #333; 148 | color: #ccc; 149 | border-color: #444; 150 | } 151 | .admin_shortcuts .shortcuts li a:hover, .admin_shortcuts .shortcuts li a:focus, .admin_shortcuts .shortcuts li a:active { 152 | border-color: #888; 153 | color: #fff; 154 | } 155 | } 156 | html[data-theme="light"] { 157 | .admin_shortcuts .shortcuts li a { 158 | background: #fff; 159 | color: #666; 160 | border-color: #ddd; 161 | } 162 | .admin_shortcuts .shortcuts li a:hover, .admin_shortcuts .shortcuts li a:focus, .admin_shortcuts .shortcuts li a:active { 163 | border-color: #888; 164 | color: #333; 165 | } 166 | } 167 | {% endblock %} 168 | -------------------------------------------------------------------------------- /admin_shortcuts/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesdotio/django-admin-shortcuts/78610b1a760368271d4a05c7398cc710d66c8ed4/admin_shortcuts/templatetags/__init__.py -------------------------------------------------------------------------------- /admin_shortcuts/templatetags/admin_shortcuts_tags.py: -------------------------------------------------------------------------------- 1 | import copy 2 | import inspect 3 | import logging 4 | 5 | from django import __version__ as django_version 6 | from django import template 7 | from django.conf import settings 8 | from django.core.exceptions import ImproperlyConfigured 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | if float(django_version[0:3]) >= 4.0: 13 | from django.utils.encoding import force_str 14 | else: 15 | from django.utils.encoding import force_text as force_str 16 | 17 | try: 18 | from django.core.urlresolvers import reverse 19 | except ImportError: 20 | from django.urls import reverse 21 | 22 | if float(django_version[0:3]) >= 4.0: 23 | from django.utils.translation import gettext 24 | from django.utils.translation import gettext_lazy as _ 25 | else: 26 | from django.utils.translation import ugettext as gettext 27 | from django.utils.translation import ugettext_lazy as _ 28 | 29 | 30 | try: 31 | from django.utils.module_loading import import_module 32 | except ImportError: 33 | from django.utils.importlib import import_module 34 | 35 | register = template.Library() 36 | 37 | 38 | @register.inclusion_tag('admin_shortcuts/base.html', takes_context=True) 39 | def admin_shortcuts(context): 40 | if 'ADMIN_SHORTCUTS' in context: 41 | admin_shortcuts = copy.deepcopy(context['ADMIN_SHORTCUTS']) 42 | else: 43 | admin_shortcuts = copy.deepcopy(getattr(settings, 'ADMIN_SHORTCUTS', {})) 44 | admin_shortcuts_settings = copy.deepcopy(getattr(settings, 'ADMIN_SHORTCUTS_SETTINGS', {})) 45 | request = context.get('request', None) 46 | if not admin_shortcuts: 47 | return {} 48 | 49 | for group in admin_shortcuts: 50 | if not group.get('shortcuts'): 51 | raise ImproperlyConfigured('settings.ADMIN_SHORTCUTS is improperly configured.') 52 | 53 | if group.get('title'): 54 | group['title'] = gettext(group['title']) 55 | 56 | enabled_shortcuts = [] 57 | for shortcut in group.get('shortcuts'): 58 | if shortcut.get('has_perms'): 59 | required_perms = shortcut['has_perms'] 60 | if isinstance(required_perms, str): 61 | # backward compatibility 62 | logger.warning(('Field `has_perms` has been modified and using a function here is deprecated. ' 63 | '`has_perms` should now be a list of string permissions, consider also the `test_func` field.')) 64 | if not eval_func(required_perms, request): 65 | continue 66 | elif not request.user.has_perms(required_perms): 67 | continue 68 | 69 | if shortcut.get('test_func'): 70 | test_func = shortcut['test_func'] 71 | authorized = eval_func(test_func, request) 72 | if isinstance(authorized, str): 73 | logger.warning(f'The test_func `{test_func}` was not found') 74 | continue 75 | elif not authorized: 76 | continue 77 | 78 | if not shortcut.get('url'): 79 | try: 80 | url_name = shortcut['url_name'] 81 | except KeyError: 82 | raise ImproperlyConfigured(_('settings.ADMIN_SHORTCUTS is improperly configured. ' 83 | 'Please supply either a "url" or a "url_name" for each shortcut.')) 84 | current_app = shortcut.get('app_name') 85 | if isinstance(url_name, list): 86 | shortcut['url'] = reverse(url_name[0], args=url_name[1:], current_app=current_app) 87 | else: 88 | shortcut['url'] = reverse(url_name, current_app=current_app) 89 | 90 | shortcut['url'] += shortcut.get('url_extra', '') 91 | 92 | if not shortcut.get('icon'): 93 | class_text = force_str(shortcut.get('url_name', shortcut.get('url', ''))) 94 | class_text += force_str(shortcut.get('title', '')) 95 | shortcut['icon'] = get_shortcut_class(class_text) 96 | 97 | if shortcut.get('count'): 98 | shortcut['count'] = eval_func(shortcut['count'], request) 99 | 100 | if shortcut.get('count_new'): 101 | shortcut['count_new'] = eval_func(shortcut['count_new'], request) 102 | 103 | if shortcut.get('title'): 104 | shortcut['title'] = gettext(shortcut['title']) 105 | 106 | enabled_shortcuts.append(shortcut) 107 | 108 | group['shortcuts'] = enabled_shortcuts 109 | 110 | is_front_page = False 111 | if request: 112 | is_front_page = reverse('admin:index') == request.path 113 | 114 | return { 115 | 'enable_admin_shortcuts': is_front_page, 116 | 'admin_shortcuts': admin_shortcuts, 117 | } 118 | 119 | 120 | @register.inclusion_tag('admin_shortcuts/style.css') 121 | def admin_shortcuts_css(): 122 | return {} 123 | 124 | 125 | def eval_func(func_path, request): 126 | try: 127 | module_str = '.'.join(func_path.split('.')[:-1]) 128 | func_str = func_path.split('.')[-1:][0] 129 | module = import_module(module_str) 130 | result = getattr(module, func_str) 131 | if callable(result): 132 | try: 133 | args = inspect.signature(result).parameters 134 | except AttributeError: # Python version < 3.3 135 | args = inspect.getargspec(result)[0] 136 | if 'request' in args: 137 | result = result(request) 138 | else: 139 | result = result() 140 | return result 141 | except: 142 | return func_path 143 | 144 | 145 | @register.simple_tag 146 | def admin_static_url(): 147 | """ 148 | If set, returns the string contained in the setting ADMIN_MEDIA_PREFIX, otherwise returns STATIC_URL + 'admin/'. 149 | """ 150 | return getattr(settings, 'ADMIN_MEDIA_PREFIX', None) or ''.join([settings.STATIC_URL, 'admin/']) 151 | 152 | 153 | DEFAULT_ICON = getattr(settings, 'ADMIN_SHORTCUTS_DEFAULT_ICON', '➡️') 154 | 155 | 156 | def get_shortcut_class(text=''): 157 | text = text.lower() 158 | icon_weights = {} 159 | max_weight = 0 160 | for icon, keywords in ICON_MAPPINGS.items(): 161 | weight = sum([1 if k in text else 0 for k in keywords]) 162 | icon_weights[icon] = weight 163 | if weight > max_weight: 164 | max_weight = weight 165 | best_icon_matches = [] 166 | for icon, weight in icon_weights.items(): 167 | if weight == max_weight: 168 | best_icon_matches.append(icon) 169 | if len(best_icon_matches): 170 | return best_icon_matches[0] 171 | return DEFAULT_ICON 172 | 173 | 174 | ICON_MAPPINGS = getattr(settings, 'ADMIN_SHORTCUTS_ICON_MAPPINGS', { 175 | '🏠': ['home', 'main', 'dashboard', 'start'], 176 | '➕': ['add', 'plus', 'create', 'new'], 177 | '🔒': ['logout', 'login', 'lock', 'secure', 'authentication'], 178 | '📄': ['file', 'document', 'paper', 'doc'], 179 | '📃': ['page', 'text', 'sheet'], 180 | '🖼️': ['image', 'picture', 'photo', 'gallery', 'media'], 181 | '🛒': ['product', 'store', 'cart', 'shopping'], 182 | '💵': ['order', 'pay', 'sale', 'income', 'revenue', 'money', 'finance'], 183 | '📦': ['category', 'box', 'package'], 184 | '👤': ['user', 'account', 'profile', 'person'], 185 | '👥': ['group', 'team', 'users', 'community'], 186 | '📒': ['address', 'contacts', 'book', 'directory'], 187 | '✉️': ['message', 'contact', 'mail', 'email'], 188 | '📁': ['folder', 'directory', 'path', 'files'], 189 | '📚': ['blog', 'book', 'library', 'reading'], 190 | '📅': ['event', 'calendar', 'schedule', 'date'], 191 | '🚚': ['delivery', 'shipping', 'truck', 'transport'], 192 | '✏️': ['change', 'edit', 'modify', 'write', 'pencil'], 193 | '🔍': ['search', 'find', 'look', 'magnify'], 194 | '📊': ['report', 'chart', 'statistics', 'analytics', 'data', 'graph'], 195 | '💼': ['business', 'portfolio', 'briefcase', 'work'], 196 | '📈': ['growth', 'increase', 'analytics', 'rise', 'trend'], 197 | '⚙️': ['settings', 'preferences', 'gear', 'tools'], 198 | '📉': ['decrease', 'decline', 'drop', 'reduce'], 199 | '🔗': ['link', 'connection', 'url', 'chain'], 200 | '📷': ['camera', 'photo', 'picture', 'snap'], 201 | '🔔': ['notification', 'alert', 'bell', 'reminder'], 202 | '🏷️': ['tag', 'label', 'price', 'sticker'], 203 | '💬': ['chat', 'conversation', 'message', 'discussion', 'comment', 'feedback', 'reply'], 204 | '🔄': ['sync', 'refresh', 'reload', 'update'], 205 | '💡': ['idea', 'tip', 'insight', 'lightbulb'], 206 | '🔓': ['unlock', 'access', 'open', 'secure'], 207 | '📌': ['pin', 'bookmark', 'save', 'mark'], 208 | '🔧': ['tool', 'fix', 'maintenance', 'repair', 'wrench'], 209 | '🖊️': ['sign', 'signature', 'write', 'pen'], 210 | '📤': ['send', 'outbox', 'upload', 'export'], 211 | '📥': ['receive', 'inbox', 'download', 'import'], 212 | '🗑️': ['delete', 'remove', 'trash', 'discard'], 213 | '📋': ['clipboard', 'copy', 'list', 'paste'], 214 | '🔨': ['build', 'construct', 'hammer'], 215 | '💳': ['payment', 'credit card', 'card', 'finance'], 216 | '🔑': ['key', 'password', 'authentication', 'security'], 217 | '📝': ['note', 'document', 'write', 'memo', 'to-do', 'task', 'list'], 218 | '🗂️': ['archive', 'file', 'folder', 'organize'], 219 | '💻': ['computer', 'laptop', 'device', 'tech'], 220 | '📲': ['mobile', 'phone', 'device', 'smartphone'], 221 | '🌐': ['web', 'internet', 'global', 'world'], 222 | '🕒': ['time', 'clock', 'hour', 'schedule'], 223 | '🔋': ['battery', 'power', 'charge'], 224 | '🛠️': ['tools', 'repair', 'settings', 'maintenance'], 225 | '📶': ['network', 'signal', 'wifi', 'connection'], 226 | '🎨': ['design', 'art', 'creativity', 'paint'], 227 | '📛': ['badge', 'identification', 'ID', 'tag'], 228 | '🎫': ['ticket', 'pass', 'entry'], 229 | '🌟': ['favorite', 'highlight', 'star', 'feature'], 230 | '🗳️': ['vote', 'ballot', 'election', 'choice'], 231 | '📎': ['attachment', 'paperclip', 'clip'], 232 | '📧': ['email', 'message', 'mail', 'send'], 233 | '📬': ['mailbox', 'receive', 'post', 'inbox'], 234 | '📯': ['announcement', 'notification', 'alert'], 235 | '📱': ['mobile', 'phone', 'smartphone', 'device'], 236 | '🖥️': ['desktop', 'computer', 'monitor', 'screen'], 237 | '🖨️': ['print', 'printer', 'document'], 238 | '🖱️': ['click', 'mouse', 'pointer'], 239 | '🎙️': ['record', 'microphone', 'audio'], 240 | '🎥': ['video', 'camera', 'record', 'film'], 241 | '🎞️': ['film', 'movie', 'record'], 242 | '🎬': ['action', 'movie', 'clapperboard'], 243 | '📹': ['video', 'camera', 'record'], 244 | '🎧': ['audio', 'headphones', 'music'], 245 | '🎤': ['microphone', 'audio', 'record'], 246 | '📡': ['satellite', 'antenna', 'signal'], 247 | '🛰️': ['satellite', 'space', 'signal'], 248 | '📺': ['tv', 'television', 'screen'], 249 | '📻': ['radio', 'audio', 'broadcast'], 250 | '📽️': ['projector', 'film', 'movie'], 251 | '🔦': ['flashlight', 'light', 'torch'], 252 | '📖': ['book', 'read', 'pages'], 253 | '📰': ['news', 'newspaper', 'article'], 254 | }) 255 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Easy and ready example 2 | 3 | The database is already set up with a staff user. You can login with the following credentials: 4 | username: root 5 | password: root 6 | 7 | In a environment with django and django-admin-shortcuts installed, you can run `python manage.py runserver` to start the server. -------------------------------------------------------------------------------- /example/db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesdotio/django-admin-shortcuts/78610b1a760368271d4a05c7398cc710d66c8ed4/example/db.sqlite3 -------------------------------------------------------------------------------- /example/django-admin-shortcuts-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesdotio/django-admin-shortcuts/78610b1a760368271d4a05c7398cc710d66c8ed4/example/django-admin-shortcuts-screenshot.png -------------------------------------------------------------------------------- /example/example/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alesdotio/django-admin-shortcuts/78610b1a760368271d4a05c7398cc710d66c8ed4/example/example/__init__.py -------------------------------------------------------------------------------- /example/example/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for example project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.0.3. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/2.0/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | from django import __version__ as django_version 17 | 18 | if float(django_version[0:3]) >= 4.0: 19 | from django.utils.translation import gettext_lazy as _ 20 | else: 21 | from django.utils.translation import ugettext_lazy as _ 22 | 23 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 24 | 25 | 26 | # Quick-start development settings - unsuitable for production 27 | # See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/ 28 | 29 | # SECURITY WARNING: keep the secret key used in production secret! 30 | SECRET_KEY = '6c$f^^c^44lxq5_vn3s%)uy#%ovzc!k))(a(h1_b2%$(ohit+f' 31 | 32 | # SECURITY WARNING: don't run with debug turned on in production! 33 | DEBUG = True 34 | 35 | ALLOWED_HOSTS = [] 36 | 37 | 38 | # Application definition 39 | 40 | INSTALLED_APPS = [ 41 | 'admin_shortcuts', 42 | 'django.contrib.admin', 43 | 'django.contrib.auth', 44 | 'django.contrib.contenttypes', 45 | 'django.contrib.sessions', 46 | 'django.contrib.messages', 47 | 'django.contrib.staticfiles', 48 | 'example' 49 | ] 50 | 51 | MIDDLEWARE = [ 52 | 'django.middleware.security.SecurityMiddleware', 53 | 'django.contrib.sessions.middleware.SessionMiddleware', 54 | 'django.middleware.common.CommonMiddleware', 55 | 'django.middleware.csrf.CsrfViewMiddleware', 56 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 57 | 'django.contrib.messages.middleware.MessageMiddleware', 58 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 59 | ] 60 | 61 | ROOT_URLCONF = 'example.urls' 62 | 63 | TEMPLATES = [ 64 | { 65 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 66 | 'DIRS': [BASE_DIR + "/templates"], 67 | 'APP_DIRS': True, 68 | 'OPTIONS': { 69 | 'context_processors': [ 70 | 'django.template.context_processors.debug', 71 | 'django.template.context_processors.request', 72 | 'django.contrib.auth.context_processors.auth', 73 | 'django.contrib.messages.context_processors.messages', 74 | ], 75 | }, 76 | }, 77 | ] 78 | 79 | WSGI_APPLICATION = 'example.wsgi.application' 80 | 81 | 82 | # Database 83 | # https://docs.djangoproject.com/en/2.0/ref/settings/#databases 84 | 85 | DATABASES = { 86 | 'default': { 87 | 'ENGINE': 'django.db.backends.sqlite3', 88 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 89 | } 90 | } 91 | 92 | 93 | # Password validation 94 | # https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators 95 | 96 | AUTH_PASSWORD_VALIDATORS = [ 97 | { 98 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 99 | }, 100 | { 101 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 102 | }, 103 | { 104 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 105 | }, 106 | { 107 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 108 | }, 109 | ] 110 | 111 | 112 | # Internationalization 113 | # https://docs.djangoproject.com/en/2.0/topics/i18n/ 114 | 115 | LANGUAGE_CODE = 'en-us' 116 | 117 | TIME_ZONE = 'UTC' 118 | 119 | USE_I18N = True 120 | 121 | USE_L10N = True 122 | 123 | USE_TZ = True 124 | 125 | 126 | # Static files (CSS, JavaScript, Images) 127 | # https://docs.djangoproject.com/en/2.0/howto/static-files/ 128 | 129 | STATIC_URL = '/static/' 130 | 131 | 132 | # Admin Shortcuts 133 | 134 | 135 | ADMIN_SHORTCUTS = [ 136 | { 137 | 'shortcuts': [ 138 | { 139 | 'url': '/', 140 | 'open_new_window': True, 141 | }, 142 | { 143 | 'url_name': 'admin:logout', 144 | }, 145 | { 146 | 'title': 'Users', 147 | 'url_name': 'admin:auth_user_changelist', 148 | 'count': 'example.utils.count_users', 149 | }, 150 | { 151 | 'title': 'Groups', 152 | 'url_name': 'admin:auth_group_changelist', 153 | 'count': 'example.utils.count_groups', 154 | 'has_perms': ['example.change_group', 'example.delete_group'], 155 | }, 156 | { 157 | 'title': 'Add user', 158 | 'url_name': 'admin:auth_user_add', 159 | 'test_func': 'example.utils.has_perms_to_users', 160 | 'has_perms': 'example.utils.has_perms_to_users', 161 | }, 162 | ] 163 | }, 164 | { 165 | 'title': 'CMS', 166 | 'shortcuts': [ 167 | { 168 | 'title': 'Pages', 169 | 'url_name': 'admin:index', 170 | }, 171 | { 172 | 'title': 'Files', 173 | 'url_name': 'admin:index', 174 | 'icon': '❤️' 175 | }, 176 | { 177 | 'title': 'Contact forms', 178 | 'url_name': 'admin:index', 179 | 'count_new': '3', 180 | }, 181 | { 182 | 'title': 'Products', 183 | 'url_name': 'admin:index', 184 | }, 185 | { 186 | 'title': 'Orders', 187 | 'url_name': 'admin:index', 188 | 'count_new': '12', 189 | }, 190 | ] 191 | }, 192 | ] 193 | ADMIN_SHORTCUTS_SETTINGS = { 194 | 'open_new_window': False, 195 | } 196 | -------------------------------------------------------------------------------- /example/example/urls.py: -------------------------------------------------------------------------------- 1 | """example URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.0/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 | -------------------------------------------------------------------------------- /example/example/utils.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User, Group 2 | 3 | 4 | def count_users(): 5 | return User.objects.count() 6 | 7 | 8 | def count_groups(): 9 | return Group.objects.count() 10 | 11 | 12 | def has_perms_to_users(request): 13 | return request.user.is_authenticated and request.user.is_staff 14 | -------------------------------------------------------------------------------- /example/example/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for example 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/2.0/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", "example.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /example/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError as exc: 10 | raise ImportError( 11 | "Couldn't import Django. Are you sure it's installed and " 12 | "available on your PYTHONPATH environment variable? Did you " 13 | "forget to activate a virtual environment?" 14 | ) from exc 15 | execute_from_command_line(sys.argv) 16 | -------------------------------------------------------------------------------- /example/requirements.txt: -------------------------------------------------------------------------------- 1 | Django 2 | -e ../ 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | from codecs import open 3 | import os 4 | 5 | from admin_shortcuts import version 6 | 7 | 8 | setup( 9 | name='django-admin-shortcuts', 10 | author='Ales Kocjancic', 11 | author_email='alesdotio@gmail.com', 12 | version=version, 13 | description='Add simple and pretty shortcuts to the django admin homepage.', 14 | long_description=open(os.path.join(os.path.dirname(__file__), 'README.md')).read(), 15 | long_description_content_type='text/markdown', 16 | url='https://github.com/alesdotio/django-admin-shortcuts', 17 | packages=find_packages(exclude=['docs', 'tests*']), 18 | include_package_data=True, 19 | install_requires=[ 20 | 'Django>=4.0', 21 | ], 22 | download_url='https://github.com/alesdotio/django-admin-shortcuts/tarball/' + version, 23 | license='BSD', 24 | classifiers=[ 25 | 'Development Status :: 3 - Alpha', 26 | 'Intended Audience :: Developers', 27 | 'Programming Language :: Python :: 3', 28 | ], 29 | keywords='', 30 | ) 31 | --------------------------------------------------------------------------------