├── .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 | 
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 |
6 | {% for group in admin_shortcuts %}
7 | {% if group.title %}{{ group.title }}
{% endif %}
8 |
9 | {% for shortcut in group.shortcuts %}
10 | - {% include 'admin_shortcuts/shortcut.html' %}
11 | {% endfor %}
12 |
13 | {% endfor %}
14 |
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 |
--------------------------------------------------------------------------------