├── djadmin2 ├── tests │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ └── 0001_initial.py │ ├── urls.py │ ├── test_views.py │ ├── test_actions.py │ ├── models.py │ ├── templates │ │ └── djadmin2theme_bootstrap3 │ │ │ └── custom_login_template.html │ ├── test_auth_admin.py │ ├── test_core.py │ ├── test_admin2tags.py │ ├── test_types.py │ └── test_renderers.py ├── themes │ ├── __init__.py │ └── djadmin2theme_bootstrap3 │ │ ├── __init__.py │ │ ├── static │ │ └── djadmin2theme_bootstrap3 │ │ │ ├── less │ │ │ ├── sb-admin2 │ │ │ │ ├── mixins.less │ │ │ │ └── variables.less │ │ │ └── base.less │ │ │ ├── scss │ │ │ ├── sb-admin2 │ │ │ │ ├── mixins │ │ │ │ │ ├── btn-outline.scss │ │ │ │ │ └── panel.scss │ │ │ │ └── _variables.scss │ │ │ ├── _variables.scss │ │ │ └── base.scss │ │ │ ├── libs │ │ │ ├── font-awesome │ │ │ │ └── fonts │ │ │ │ │ ├── FontAwesome.otf │ │ │ │ │ ├── fontawesome-webfont.eot │ │ │ │ │ ├── fontawesome-webfont.ttf │ │ │ │ │ ├── fontawesome-webfont.woff │ │ │ │ │ └── fontawesome-webfont.woff2 │ │ │ ├── bootstrap │ │ │ │ └── fonts │ │ │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ │ │ ├── glyphicons-halflings-regular.woff │ │ │ │ │ └── glyphicons-halflings-regular.woff2 │ │ │ └── html5shiv.js │ │ │ └── js │ │ │ ├── base.js │ │ │ ├── actions.js │ │ │ └── sb-admin-2.js │ │ └── templates │ │ └── djadmin2theme_bootstrap3 │ │ ├── renderers │ │ └── boolean.html │ │ ├── edit_inlines │ │ ├── stacked.html │ │ └── tabular.html │ │ ├── auth │ │ ├── logout.html │ │ ├── password_change_done.html │ │ ├── password_change_form.html │ │ └── login.html │ │ ├── includes │ │ ├── history.html │ │ ├── save_buttons.html │ │ ├── pagination.html │ │ ├── list_actions.html │ │ └── app_model_list.html │ │ ├── model_detail.html │ │ ├── app_index.html │ │ ├── index.html │ │ ├── actions │ │ └── delete_selected_confirmation.html │ │ ├── model_confirm_delete.html │ │ ├── model_history.html │ │ └── model_update_form.html ├── migrations │ ├── __init__.py │ └── 0001_initial.py ├── templatetags │ └── __init__.py ├── site.py ├── locale │ ├── bs │ │ └── LC_MESSAGES │ │ │ └── django.mo │ ├── ca │ │ └── LC_MESSAGES │ │ │ └── django.mo │ ├── de │ │ └── LC_MESSAGES │ │ │ └── django.mo │ ├── en │ │ └── LC_MESSAGES │ │ │ └── django.mo │ ├── es │ │ └── LC_MESSAGES │ │ │ └── django.mo │ ├── fr │ │ └── LC_MESSAGES │ │ │ └── django.mo │ ├── it │ │ └── LC_MESSAGES │ │ │ └── django.mo │ ├── nl │ │ └── LC_MESSAGES │ │ │ └── django.mo │ ├── sk │ │ └── LC_MESSAGES │ │ │ └── django.mo │ ├── zh │ │ └── LC_MESSAGES │ │ │ └── django.mo │ ├── pl_PL │ │ └── LC_MESSAGES │ │ │ └── django.mo │ └── pt_BR │ │ └── LC_MESSAGES │ │ └── django.mo ├── __init__.py ├── apps.py ├── settings.py ├── admin2.py ├── renderers.py ├── forms.py └── models.py ├── example ├── blog │ ├── __init__.py │ ├── tests │ │ ├── __init__.py │ │ ├── test_filters.py │ │ ├── test_builtin_api_resources.py │ │ └── test_nestedobjects.py │ ├── migrations │ │ ├── __init__.py │ │ └── 0001_initial.py │ ├── locale │ │ ├── de │ │ │ └── LC_MESSAGES │ │ │ │ └── django.mo │ │ ├── en │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ ├── fr │ │ │ └── LC_MESSAGES │ │ │ │ └── django.mo │ │ ├── it │ │ │ └── LC_MESSAGES │ │ │ │ └── django.mo │ │ ├── nl │ │ │ └── LC_MESSAGES │ │ │ │ └── django.mo │ │ ├── sk │ │ │ └── LC_MESSAGES │ │ │ │ └── django.mo │ │ ├── zh │ │ │ └── LC_MESSAGES │ │ │ │ └── django.mo │ │ ├── pl_PL │ │ │ └── LC_MESSAGES │ │ │ │ └── django.mo │ │ └── pt_BR │ │ │ └── LC_MESSAGES │ │ │ └── django.mo │ ├── views.py │ ├── admin.py │ ├── templates │ │ ├── blog │ │ │ ├── blog_detail.html │ │ │ ├── blog_list.html │ │ │ └── home.html │ │ ├── djadmin2 │ │ │ └── bootstrap │ │ │ │ └── actions │ │ │ │ └── publish_selected_items.html │ │ └── base.html │ ├── admin2.py │ ├── models.py │ └── actions.py ├── example │ ├── __init__.py │ ├── wsgi.py │ └── urls.py ├── files │ ├── __init__.py │ ├── tests │ │ ├── __init__.py │ │ ├── fixtures │ │ │ └── pubtest.txt │ │ └── test_models.py │ ├── migrations │ │ ├── __init__.py │ │ └── 0001_initial.py │ ├── views.py │ ├── locale │ │ ├── de │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ ├── en │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ ├── fr │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ ├── it │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ ├── nl │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ ├── sk │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ ├── zh │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ ├── pl_PL │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ ├── pt_BR │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ └── tl_PH │ │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── admin.py │ ├── admin2.py │ ├── templates │ │ └── home.html │ └── models.py ├── polls │ ├── __init__.py │ ├── tests │ │ ├── __init__.py │ │ └── test_models.py │ ├── migrations │ │ ├── __init__.py │ │ └── 0001_initial.py │ ├── views.py │ ├── locale │ │ ├── de │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ ├── en │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ ├── fr │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ ├── it │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ ├── nl │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ ├── sk │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ ├── zh │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ ├── pl_PL │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ ├── pt_BR │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ └── tl_PH │ │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── templates │ │ └── home.html │ ├── admin.py │ ├── admin2.py │ └── models.py ├── static │ └── img │ │ ├── admin.png │ │ └── admin2.png └── manage.py ├── docs ├── _static │ ├── README │ ├── join_team.png │ ├── translate_now.png │ └── request_language.png ├── tutorial.rst ├── ref │ ├── api.rst │ ├── built-in-views.rst │ ├── modeladmin.rst │ ├── renderers.rst │ ├── views.rst │ ├── forms.rst │ └── actions.rst ├── README ├── faq.rst ├── index.rst ├── _ext │ └── djangodocs.py ├── installation.rst └── design.rst ├── requirements_test.txt ├── screenshots ├── Change_user.png ├── Select_user.png └── Site_administration.png ├── requirements.txt ├── MANIFEST.in ├── .coveragerc ├── CONTRIBUTING.rst ├── tox.ini ├── .gitignore ├── fabfile.py ├── .github └── workflows │ ├── test.yml │ └── release.yml ├── LICENSE ├── CODE_OF_CONDUCT.md └── AUTHORS.rst /djadmin2/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /djadmin2/themes/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/blog/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/example/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/files/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/polls/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /djadmin2/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /djadmin2/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/blog/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/files/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/polls/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /djadmin2/tests/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/blog/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/files/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/polls/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/files/views.py: -------------------------------------------------------------------------------- 1 | # Create your views here. 2 | -------------------------------------------------------------------------------- /example/polls/views.py: -------------------------------------------------------------------------------- 1 | # Create your views here. 2 | -------------------------------------------------------------------------------- /example/files/tests/fixtures/pubtest.txt: -------------------------------------------------------------------------------- 1 | Hello World 2 | -------------------------------------------------------------------------------- /docs/_static/README: -------------------------------------------------------------------------------- 1 | Put static files for Sphinx in here. 2 | -------------------------------------------------------------------------------- /djadmin2/site.py: -------------------------------------------------------------------------------- 1 | from . import core 2 | 3 | djadmin2_site = core.Admin2() 4 | -------------------------------------------------------------------------------- /requirements_test.txt: -------------------------------------------------------------------------------- 1 | -rrequirements.txt 2 | flake8>=2.5.4 3 | pytest 4 | pytest-django 5 | pytest-cov 6 | -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/static/djadmin2theme_bootstrap3/less/sb-admin2/mixins.less: -------------------------------------------------------------------------------- 1 | // Mixins 2 | -------------------------------------------------------------------------------- /docs/_static/join_team.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/docs/_static/join_team.png -------------------------------------------------------------------------------- /screenshots/Change_user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/screenshots/Change_user.png -------------------------------------------------------------------------------- /screenshots/Select_user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/screenshots/Select_user.png -------------------------------------------------------------------------------- /docs/_static/translate_now.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/docs/_static/translate_now.png -------------------------------------------------------------------------------- /example/static/img/admin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/example/static/img/admin.png -------------------------------------------------------------------------------- /example/static/img/admin2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/example/static/img/admin2.png -------------------------------------------------------------------------------- /docs/_static/request_language.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/docs/_static/request_language.png -------------------------------------------------------------------------------- /screenshots/Site_administration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/screenshots/Site_administration.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | django-extra-views 2 | django-braces 3 | djangorestframework 4 | django-filter 5 | django-debug-toolbar 6 | pytz 7 | -------------------------------------------------------------------------------- /djadmin2/locale/bs/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/djadmin2/locale/bs/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /djadmin2/locale/ca/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/djadmin2/locale/ca/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /djadmin2/locale/de/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/djadmin2/locale/de/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /djadmin2/locale/en/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/djadmin2/locale/en/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /djadmin2/locale/es/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/djadmin2/locale/es/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /djadmin2/locale/fr/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/djadmin2/locale/fr/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /djadmin2/locale/it/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/djadmin2/locale/it/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /djadmin2/locale/nl/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/djadmin2/locale/nl/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /djadmin2/locale/sk/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/djadmin2/locale/sk/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /djadmin2/locale/zh/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/djadmin2/locale/zh/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /djadmin2/locale/pl_PL/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/djadmin2/locale/pl_PL/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /djadmin2/locale/pt_BR/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/djadmin2/locale/pt_BR/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /example/blog/locale/de/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/example/blog/locale/de/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /example/blog/locale/en/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/example/blog/locale/en/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /example/blog/locale/fr/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/example/blog/locale/fr/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /example/blog/locale/it/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/example/blog/locale/it/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /example/blog/locale/nl/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/example/blog/locale/nl/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /example/blog/locale/sk/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/example/blog/locale/sk/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /example/blog/locale/zh/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/example/blog/locale/zh/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /example/files/locale/de/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/example/files/locale/de/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /example/files/locale/en/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/example/files/locale/en/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /example/files/locale/fr/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/example/files/locale/fr/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /example/files/locale/it/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/example/files/locale/it/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /example/files/locale/nl/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/example/files/locale/nl/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /example/files/locale/sk/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/example/files/locale/sk/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /example/files/locale/zh/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/example/files/locale/zh/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /example/polls/locale/de/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/example/polls/locale/de/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /example/polls/locale/en/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/example/polls/locale/en/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /example/polls/locale/fr/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/example/polls/locale/fr/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /example/polls/locale/it/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/example/polls/locale/it/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /example/polls/locale/nl/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/example/polls/locale/nl/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /example/polls/locale/sk/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/example/polls/locale/sk/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /example/polls/locale/zh/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/example/polls/locale/zh/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /example/blog/locale/pl_PL/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/example/blog/locale/pl_PL/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /example/blog/locale/pt_BR/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/example/blog/locale/pt_BR/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /example/files/locale/pl_PL/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/example/files/locale/pl_PL/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /example/files/locale/pt_BR/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/example/files/locale/pt_BR/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /example/files/locale/tl_PH/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/example/files/locale/tl_PH/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /example/polls/locale/pl_PL/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/example/polls/locale/pl_PL/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /example/polls/locale/pt_BR/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/example/polls/locale/pt_BR/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /example/polls/locale/tl_PH/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/example/polls/locale/tl_PH/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include LICENSE 3 | include AUTHORS.rst 4 | include HISTORY.rst 5 | include MANIFEST.in 6 | recursive-include djadmin2 *.html *.css *.js *.png 7 | -------------------------------------------------------------------------------- /docs/tutorial.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | Tutorial 3 | =============== 4 | 5 | This is where the django-admin2 tutorial is in the process of being written. It will be analogous with Page 2 of the Django tutorial. 6 | -------------------------------------------------------------------------------- /example/files/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import CaptionedFile, UncaptionedFile 4 | 5 | 6 | admin.site.register(CaptionedFile) 7 | admin.site.register(UncaptionedFile) 8 | -------------------------------------------------------------------------------- /example/files/admin2.py: -------------------------------------------------------------------------------- 1 | from djadmin2.site import djadmin2_site 2 | from .models import CaptionedFile, UncaptionedFile 3 | 4 | 5 | djadmin2_site.register(CaptionedFile) 6 | djadmin2_site.register(UncaptionedFile) 7 | -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/static/djadmin2theme_bootstrap3/scss/sb-admin2/mixins/btn-outline.scss: -------------------------------------------------------------------------------- 1 | @mixin btn-outline($color, $hover-color: #FFF) { 2 | color: $color; 3 | &:hover { 4 | color: $hover-color; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/templates/djadmin2theme_bootstrap3/renderers/boolean.html: -------------------------------------------------------------------------------- 1 | {% if value %} 2 | 3 | {% else %} 4 | 5 | {% endif %} 6 | -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/static/djadmin2theme_bootstrap3/libs/font-awesome/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/djadmin2/themes/djadmin2theme_bootstrap3/static/djadmin2theme_bootstrap3/libs/font-awesome/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /djadmin2/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.7.1' 2 | 3 | __author__ = 'Daniel Greenfeld & Contributors' 4 | 5 | VERSION = __version__ # synonym 6 | 7 | # Default datetime input and output formats 8 | ISO_8601 = 'iso-8601' 9 | 10 | default_app_config = "djadmin2.apps.Djadmin2Config" 11 | -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/static/djadmin2theme_bootstrap3/libs/font-awesome/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/djadmin2/themes/djadmin2theme_bootstrap3/static/djadmin2theme_bootstrap3/libs/font-awesome/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/static/djadmin2theme_bootstrap3/libs/font-awesome/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/djadmin2/themes/djadmin2theme_bootstrap3/static/djadmin2theme_bootstrap3/libs/font-awesome/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/static/djadmin2theme_bootstrap3/libs/font-awesome/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/djadmin2/themes/djadmin2theme_bootstrap3/static/djadmin2theme_bootstrap3/libs/font-awesome/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/static/djadmin2theme_bootstrap3/libs/font-awesome/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/djadmin2/themes/djadmin2theme_bootstrap3/static/djadmin2theme_bootstrap3/libs/font-awesome/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/static/djadmin2theme_bootstrap3/libs/bootstrap/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/djadmin2/themes/djadmin2theme_bootstrap3/static/djadmin2theme_bootstrap3/libs/bootstrap/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/static/djadmin2theme_bootstrap3/libs/bootstrap/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/djadmin2/themes/djadmin2theme_bootstrap3/static/djadmin2theme_bootstrap3/libs/bootstrap/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/static/djadmin2theme_bootstrap3/libs/bootstrap/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/djadmin2/themes/djadmin2theme_bootstrap3/static/djadmin2theme_bootstrap3/libs/bootstrap/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/static/djadmin2theme_bootstrap3/libs/bootstrap/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzband/django-admin2/main/djadmin2/themes/djadmin2theme_bootstrap3/static/djadmin2theme_bootstrap3/libs/bootstrap/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /example/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /example/blog/views.py: -------------------------------------------------------------------------------- 1 | from django.views.generic import ListView, DetailView 2 | 3 | from .models import Post 4 | 5 | 6 | class BlogListView(ListView): 7 | model = Post 8 | template_name = 'blog_list.html' 9 | paginate_by = 10 10 | 11 | 12 | class BlogDetailView(DetailView): 13 | model = Post 14 | template_name = 'blog_detail.html' 15 | -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/static/djadmin2theme_bootstrap3/scss/sb-admin2/mixins/panel.scss: -------------------------------------------------------------------------------- 1 | @mixin panel-color($color) { 2 | border-color: $color; 3 | .panel-heading { 4 | border-color: $color; 5 | color: white; 6 | background-color: $color; 7 | } 8 | a { 9 | color: $color; 10 | &:hover { 11 | color: darken($color, 15%); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /example/files/templates/home.html: -------------------------------------------------------------------------------- 1 | {% extends "djadmin2theme_bootstrap3/base.html" %} 2 | {% load i18n %} 3 | 4 | {% block content %} 5 |

{% trans "Example Home" %}

6 | 7 | 11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /example/polls/templates/home.html: -------------------------------------------------------------------------------- 1 | {% extends "djadmin2theme_bootstrap3/base.html" %} 2 | {% load i18n %} 3 | 4 | {% block content %} 5 |

{% trans "Example Home" %}

6 | 7 | 11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/static/djadmin2theme_bootstrap3/js/base.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | /** 3 | * Dynamicaly adds button to each input element 4 | * Required by filter section to allow form to be submitted 5 | */ 6 | $("#filter_form").find('input').each(function(){ 7 | var input_field = $(this); 8 | 9 | var btn = input_field.after(''); 10 | }); 11 | 12 | }); 13 | -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/static/djadmin2theme_bootstrap3/scss/_variables.scss: -------------------------------------------------------------------------------- 1 | $gray-darker: lighten(#000, 13.5%); 2 | $gray-dark: lighten(#000, 20%); 3 | $gray: lighten(#000, 33.5%); 4 | $gray-light: lighten(#000, 60%); 5 | $gray-lighter: lighten(#000, 93.5%); 6 | $gray-lightest: lighten(#000, 97.25%); 7 | $brand-primary: #428bca; 8 | $brand-success: #5cb85c; 9 | $brand-info: #5bc0de; 10 | $brand-warning: #f0ad4e; 11 | $brand-danger: #d9534f; 12 | 13 | -------------------------------------------------------------------------------- /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/1.9/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 | -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/static/djadmin2theme_bootstrap3/less/sb-admin2/variables.less: -------------------------------------------------------------------------------- 1 | // Variables 2 | 3 | @gray-darker: lighten(#000, 13.5%); 4 | @gray-dark: lighten(#000, 20%); 5 | @gray: lighten(#000, 33.5%); 6 | @gray-light: lighten(#000, 60%); 7 | @gray-lighter: lighten(#000, 93.5%); 8 | @gray-lightest: lighten(#000, 97.25%); 9 | @brand-primary: #428bca; 10 | @brand-success: #5cb85c; 11 | @brand-info: #5bc0de; 12 | @brand-warning: #f0ad4e; 13 | @brand-danger: #d9534f; 14 | 15 | -------------------------------------------------------------------------------- /example/blog/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Post, Comment 4 | 5 | 6 | class CommentInline(admin.TabularInline): 7 | model = Comment 8 | 9 | 10 | class PostAdmin(admin.ModelAdmin): 11 | inlines = [CommentInline, ] 12 | search_fields = ('title', 'body', "published_date") 13 | list_filter = ['published', 'title'] 14 | date_hierarchy = "published_date" 15 | 16 | 17 | admin.site.register(Post, PostAdmin) 18 | admin.site.register(Comment) 19 | -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/templates/djadmin2theme_bootstrap3/edit_inlines/stacked.html: -------------------------------------------------------------------------------- 1 | {% load i18n admin2_tags %} 2 | 3 | {% for inline_form in formset %} 4 |
5 | {{ inline_form }} 6 | {% if not inline_form.visible_fields %} 7 |

8 | {% trans "This form doesn't have visible fields. This doesn't mean there are no hidden fields." %} 9 |

10 | {% endif %} 11 |
12 | {% endfor %} 13 | -------------------------------------------------------------------------------- /djadmin2/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | from django.db.models.signals import post_migrate 3 | from django.utils.translation import gettext_lazy as _ 4 | 5 | from djadmin2.permissions import create_view_permissions 6 | 7 | 8 | class Djadmin2Config(AppConfig): 9 | name = 'djadmin2' 10 | verbose_name = _("Django Admin2") 11 | 12 | def ready(self): 13 | post_migrate.connect( 14 | create_view_permissions, 15 | dispatch_uid="django-admin2.djadmin2.permissions.create_view_permissions" 16 | ) 17 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | include = 3 | djadmin2* 4 | example* 5 | omit = 6 | */tests* 7 | 8 | [report] 9 | # Regexes for lines to exclude from consideration 10 | exclude_lines = 11 | # Don't complain about missing debug-only code: 12 | def __repr__ 13 | if self\.debug 14 | 15 | # Don't complain if tests don't hit defensive assertion code: 16 | raise AssertionError 17 | raise NotImplementedError 18 | 19 | # Don't complain if non-runnable code isn't run: 20 | if 0: 21 | if False: 22 | if __name__ == .__main__.: 23 | -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/static/djadmin2theme_bootstrap3/scss/sb-admin2/_variables.scss: -------------------------------------------------------------------------------- 1 | $gray-darker: lighten(#000, 13.5%) !default; 2 | $gray-dark: lighten(#000, 20%) !default; 3 | $gray: lighten(#000, 33.5%) !default; 4 | $gray-light: lighten(#000, 60%) !default; 5 | $gray-lighter: lighten(#000, 93.5%) !default; 6 | $gray-lightest: lighten(#000, 97.25%) !default; 7 | $brand-primary: #428bca !default; 8 | $brand-success: #5cb85c !default; 9 | $brand-info: #5bc0de !default; 10 | $brand-warning: #f0ad4e !default; 11 | $brand-danger: #d9534f !default; -------------------------------------------------------------------------------- /djadmin2/tests/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.conf.urls.static import static 3 | from django.urls import re_path 4 | 5 | from djadmin2.site import djadmin2_site 6 | 7 | from djadmin2.views import LoginView 8 | 9 | 10 | class CustomLoginView(LoginView): 11 | default_template_name = "custom_login_template.html" 12 | 13 | 14 | djadmin2_site.login_view = CustomLoginView 15 | djadmin2_site.autodiscover() 16 | 17 | urlpatterns = [ 18 | re_path(r'^admin2/', djadmin2_site.urls), 19 | ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 20 | -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/templates/djadmin2theme_bootstrap3/auth/logout.html: -------------------------------------------------------------------------------- 1 | {% extends "djadmin2theme_bootstrap3/base.html" %} 2 | {% load i18n admin2_tags %} 3 | 4 | {% block breadcrumbs %} 5 |
  • 6 | {% trans "Home" %} 7 |
  • 8 |
  • {% trans "Log out" %}
  • 9 | {% endblock breadcrumbs %} 10 | 11 | {% block content %} 12 |

    {% trans "Thanks for spending some quality time with the Web site today." %}

    13 |

    {% trans 'Log in again' %}

    14 | {% endblock content %} 15 | -------------------------------------------------------------------------------- /example/polls/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Poll, Choice 4 | 5 | 6 | class ChoiceInline(admin.TabularInline): 7 | model = Choice 8 | extra = 3 9 | 10 | 11 | class PollAdmin(admin.ModelAdmin): 12 | fieldsets = [ 13 | (None, {'fields': ['question']}), 14 | ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}), 15 | ] 16 | inlines = [ChoiceInline] 17 | list_display = ('question', 'pub_date', 'was_published_recently') 18 | list_filter = ['pub_date'] 19 | search_fields = ['question'] 20 | date_hierarchy = 'pub_date' 21 | 22 | 23 | admin.site.register(Poll, PollAdmin) 24 | -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/templates/djadmin2theme_bootstrap3/includes/history.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | 3 | {% if actions %} 4 |
      5 | {% for action in actions %} 6 |
    1. 7 | {% if action.is_addition %} 8 | {% elif action.is_change %} 9 | {% else %} 10 | {% endif %} {{ action }} 11 | {{ action.content_type.model }} 12 |
    2. 13 | {% endfor %} 14 |
    15 | {% else %} 16 |

    {% trans "None available" %}

    17 | {% endif %} 18 | 19 | -------------------------------------------------------------------------------- /docs/ref/api.rst: -------------------------------------------------------------------------------- 1 | RESTful API 2 | ============= 3 | 4 | **django-admin2** comes with a builtin REST-API for accessing all the 5 | resources you can get from the frontend via JSON. 6 | 7 | The API can be found at the URL you choose for the admin2 and then append 8 | ``api/v0/``. 9 | 10 | If the API has changed in a backwards-incompatible way we will increase the 11 | API version to the next number. So you can be sure that you're frontend code 12 | should keep working even between updates to more recent django-admin2 13 | versions. 14 | 15 | However currently we are still in heavy development, so we are using ``v0`` 16 | for the API, which means is subject to change and being broken at any time. 17 | -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/templates/djadmin2theme_bootstrap3/auth/password_change_done.html: -------------------------------------------------------------------------------- 1 | {% extends "djadmin2theme_bootstrap3/base.html" %} 2 | {% load i18n admin2_tags %} 3 | 4 | {% block title %}{% trans 'Password change successful' %}{% endblock title %} 5 | {% block page_title %}{% trans 'Password change successful' %}{% endblock page_title %} 6 | 7 | {% block breadcrumbs %} 8 |
  • 9 | {% trans "Home" %} 10 |
  • 11 |
  • {% trans "Password change successful" %}
  • 12 | {% endblock breadcrumbs %} 13 | 14 | {% block content %} 15 |

    {% trans 'Your password was changed.' %}

    16 | {% endblock content %} 17 | -------------------------------------------------------------------------------- /example/polls/admin2.py: -------------------------------------------------------------------------------- 1 | from djadmin2.site import djadmin2_site 2 | from djadmin2.types import Admin2TabularInline, ModelAdmin2 3 | from .models import Poll, Choice 4 | 5 | 6 | class ChoiceInline(Admin2TabularInline): 7 | model = Choice 8 | fields = '__all__' 9 | 10 | 11 | class PollAdmin(ModelAdmin2): 12 | fieldsets = [ 13 | (None, {'fields': ['question']}), 14 | ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}), 15 | ] 16 | inlines = [ChoiceInline] 17 | list_display = ('question', 'pub_date', 'was_published_recently') 18 | list_filter = ['pub_date'] 19 | search_fields = ['question'] 20 | date_hierarchy = 'pub_date' 21 | 22 | 23 | djadmin2_site.register(Poll, PollAdmin) 24 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | .. image:: https://jazzband.co/static/img/jazzband.svg 5 | :target: https://jazzband.co/ 6 | :alt: Jazzband 7 | 8 | This is a `Jazzband `_ project. By contributing you agree to abide by the `Contributor Code of Conduct `_ and follow the `guidelines `_. 9 | 10 | Also, please read the following: 11 | 12 | * Our design_ document, which lists the constraints and goals of the project. 13 | * Our contributing_ document, which describes our procedures and methods. 14 | 15 | .. _design: https://django-admin2.readthedocs.io/en/latest/design.html 16 | .. _contributing: https://django-admin2.readthedocs.io/en/latest/contributing.html 17 | -------------------------------------------------------------------------------- /djadmin2/settings.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | 4 | # Restricts the attributes that are passed from ModelAdmin2 classes to their 5 | # views. This is a security feature. 6 | # See the docstring on djadmin2.types.ModelAdmin2 for more detail. 7 | MODEL_ADMIN_ATTRS = ( 8 | 'actions_selection_counter', "date_hierarchy", 'list_display', 9 | 'list_display_links', 'list_filter', 'admin', 'search_fields', 10 | 'field_renderers', 'index_view', 'detail_view', 'create_view', 11 | 'update_view', 'delete_view', 'get_default_view_kwargs', 12 | 'get_list_actions', 'get_ordering', 'actions_on_bottom', 'actions_on_top', 13 | 'ordering', 'save_on_top', 'save_on_bottom', 'readonly_fields', ) 14 | 15 | ADMIN2_THEME_DIRECTORY = getattr(settings, "ADMIN2_THEME_DIRECTORY", "djadmin2theme_bootstrap3") 16 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = E265,E501 3 | max-line-length = 100 4 | max-complexity = 10 5 | exclude = migrations/*,docs/* 6 | 7 | [tox] 8 | envlist = 9 | py35-{2.2}, 10 | py36-{2.2,3.1}, 11 | py37-{2.2,3.1,3.2}, 12 | py38-{2.2,3.1,3.2,main}, 13 | 14 | [gh-actions] 15 | python = 16 | 3.5: py35 17 | 3.6: py36 18 | 3.7: py37 19 | 3.8: py38 20 | 21 | [testenv] 22 | commands = 23 | pytest --cov-append --cov djadmin2 --cov-report=xml [] 24 | deps = 25 | -rrequirements_test.txt 26 | 3.1: Django>=3.1,<3.2 27 | 3.2: Django>=3.2,<4.0 28 | 2.2: Django>=2.2,<2.3 29 | main: https://github.com/django/django/tarball/main 30 | setenv= 31 | DJANGO_SETTINGS_MODULE = example.settings 32 | PYTHONPATH = {toxinidir}/example:{toxinidir} 33 | PYTHONDONTWRITEBYTECODE=1 34 | PYTHONWARNINGS=once 35 | -------------------------------------------------------------------------------- /docs/README: -------------------------------------------------------------------------------- 1 | The documentation in this tree is in plain text files and can be viewed using 2 | any text file viewer. 3 | 4 | It uses ReST (reStructuredText) [1], and the Sphinx documentation system [2]. 5 | This allows it to be built into other forms for easier viewing and browsing. 6 | 7 | To create an HTML version of the docs: 8 | 9 | * Create a virtualenv and activate it. 10 | 11 | * Install all dependencies from requirements.txt inot the virtualenv using 12 | ``pip install -r requirements.txt`` (this will also install Sphinx). 13 | 14 | * In this docs/ directory, type ``make html`` (or ``make.bat html`` on 15 | Windows) at a shell prompt while the virtualenv is active. 16 | 17 | The documentation in _build/html/index.html can then be viewed in a web browser. 18 | 19 | [1] http://docutils.sourceforge.net/rst.html 20 | [2] http://sphinx.pocoo.org/ 21 | -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/templates/djadmin2theme_bootstrap3/model_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "djadmin2theme_bootstrap3/base.html" %} 2 | {% load i18n admin2_tags %} 3 | 4 | {% block title %}{{ object }}{% endblock title %} 5 | 6 | {% block page_title %}{{ object }}{% endblock page_title %} 7 | 8 | {% block breadcrumbs %} 9 |
  • 10 | {% trans "Home" %} 11 |
  • 12 |
  • 13 | {% firstof app_verbose_name app_label|title %} 14 |
  • 15 |
  • 16 | {{ model_name_pluralized|title }} 17 |
  • 18 |
  • {{ object }}
  • 19 | {% endblock breadcrumbs %} 20 | 21 | {% block content %} 22 | {{ object }} 23 | {% endblock content %} 24 | -------------------------------------------------------------------------------- /example/files/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.utils.translation import gettext_lazy as _ 3 | 4 | 5 | class CaptionedFile(models.Model): 6 | caption = models.CharField(max_length=200, verbose_name=_('caption')) 7 | publication = models.FileField(upload_to='captioned-files', verbose_name=_('Uploaded File')) 8 | 9 | def __str__(self): 10 | return self.caption 11 | 12 | class Meta: 13 | verbose_name = _('Captioned File') 14 | verbose_name_plural = _('Captioned Files') 15 | 16 | 17 | class UncaptionedFile(models.Model): 18 | publication = models.FileField(upload_to='uncaptioned-files', verbose_name=_('Uploaded File')) 19 | 20 | def __str__(self): 21 | return self.publication.name 22 | 23 | class Meta: 24 | verbose_name = _('Uncaptioned File') 25 | verbose_name_plural = _('Uncaptioned Files') 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | 3 | # Packages 4 | .eggs/ 5 | *.egg 6 | *.egg-info 7 | dist 8 | build 9 | eggs 10 | parts 11 | bin 12 | var 13 | sdist 14 | develop-eggs 15 | .installed.cfg 16 | 17 | # Installer logs 18 | pip-log.txt 19 | 20 | # Unit test / coverage reports 21 | .coverage 22 | .tox 23 | 24 | # Mr Developer 25 | .mr.developer.cfg 26 | 27 | # Django 28 | dev.db* 29 | dev 30 | *.log 31 | local_settings.py 32 | collected_static/ 33 | 34 | # Local file cruft/auto-backups 35 | .DS_Store 36 | *~ 37 | 38 | # Coverage 39 | coverage 40 | 41 | # Sphinx 42 | docs/_build 43 | 44 | # Launchpad 45 | lp-cache 46 | _data 47 | 48 | # PostgreSQL 49 | logfile 50 | 51 | # SQLite 52 | *.db 53 | 54 | # vim swap files 55 | *.sw[o-z] 56 | 57 | # Sublime Text 58 | *.sublime-workspace 59 | *.sublime-project 60 | 61 | # test media upload 62 | media 63 | 64 | # PyCharm 65 | .idea/ 66 | 67 | .cache 68 | .pytest_cache -------------------------------------------------------------------------------- /example/example/urls.py: -------------------------------------------------------------------------------- 1 | from blog.views import BlogListView, BlogDetailView 2 | from django.conf import settings 3 | from django.conf.urls.static import static 4 | from django.contrib import admin 5 | from django.urls import re_path 6 | 7 | from djadmin2.site import djadmin2_site 8 | 9 | 10 | djadmin2_site.autodiscover() 11 | 12 | urlpatterns = [ 13 | re_path(r"^admin2/", djadmin2_site.urls), 14 | re_path(r"^admin/", admin.site.urls), 15 | re_path( 16 | r"^blog/", 17 | BlogListView.as_view(template_name="blog/blog_list.html"), 18 | name="blog_list", 19 | ), 20 | re_path( 21 | r"^blog/detail(?P\d+)/$", 22 | BlogDetailView.as_view(template_name="blog/blog_detail.html"), 23 | name="blog_detail", 24 | ), 25 | re_path(r"^$", BlogListView.as_view(template_name="blog/home.html"), name="home"), 26 | ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 27 | -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/templates/djadmin2theme_bootstrap3/app_index.html: -------------------------------------------------------------------------------- 1 | {% extends "djadmin2theme_bootstrap3/base.html" %} 2 | {% load admin2_tags i18n %} 3 | 4 | {% block breadcrumbs %} 5 |
  • 6 | {% trans "Home" %} 7 |
  • 8 |
  • 9 | {% with app_verbose_names|verbose_name_for:app_label as verbose_name %} 10 | {% firstof verbose_name app_label|title %} 11 | {% endwith %} 12 |
  • 13 | {% endblock breadcrumbs %} 14 | 15 | {% block page_title %}{% blocktrans with app_label=app_label|title %}{{ app_label }} administration 16 | {% endblocktrans %}{% endblock page_title %} 17 | 18 | {% block content %} 19 |
    20 |
    21 | {% include 'djadmin2theme_bootstrap3/includes/app_model_list.html' %} 22 |
    23 |
    24 | {% endblock content %} 25 | -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/templates/djadmin2theme_bootstrap3/index.html: -------------------------------------------------------------------------------- 1 | {% extends "djadmin2theme_bootstrap3/base.html" %} 2 | {% load admin2_tags i18n %} 3 | 4 | {% block breacrumbs %}{% endblock breacrumbs %} 5 | 6 | {% block content %} 7 |
    8 |
    9 | {% for app_label, registry in apps.items %} 10 | {% include 'djadmin2theme_bootstrap3/includes/app_model_list.html' %} 11 | {% endfor %} 12 |
    13 |
    14 |
    15 |
    16 | {% trans "Recent Actions" %} 17 |
    18 |
    19 |

    {% trans "My Actions" %}

    20 | {% action_history %} 21 |
    22 |
    23 |
    24 |
    25 | {% endblock content %} 26 | -------------------------------------------------------------------------------- /djadmin2/tests/test_views.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase, override_settings 2 | from django.urls import reverse 3 | 4 | from django.utils.encoding import force_str 5 | 6 | 7 | from .. import views 8 | 9 | 10 | class AdminViewTest(TestCase): 11 | 12 | def setUp(self): 13 | self.admin_view = views.AdminView(r'^$', views.ModelListView, name='admin-view') 14 | 15 | def test_url(self): 16 | self.assertEqual(self.admin_view.url, r'^$') 17 | 18 | def test_view(self): 19 | self.assertEqual(self.admin_view.view, views.ModelListView) 20 | 21 | def test_name(self): 22 | self.assertEqual(self.admin_view.name, 'admin-view') 23 | 24 | 25 | @override_settings(ROOT_URLCONF='djadmin2.tests.urls') 26 | class CustomLoginViewTest(TestCase): 27 | 28 | def test_view_ok(self): 29 | response = self.client.get(reverse("admin2:dashboard")) 30 | self.assertInHTML('

    Custom login view

    ', force_str(response.content)) 31 | -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/static/djadmin2theme_bootstrap3/less/base.less: -------------------------------------------------------------------------------- 1 | @import "sb-admin2/sb-admin-2"; 2 | 3 | #wrapper { 4 | &.no-sidebar { 5 | #page-wrapper { 6 | margin: 0; 7 | } 8 | } 9 | } 10 | 11 | .model-search { 12 | margin-bottom: 15px; 13 | } 14 | 15 | .sort_link { 16 | display: block; 17 | cursor: pointer; 18 | color: black; 19 | } 20 | 21 | .sort_link:hover { 22 | color: black; 23 | } 24 | 25 | .previous-link a { 26 | color: gray; 27 | } 28 | 29 | .date-drilldown { 30 | padding-bottom: 0; 31 | padding-top: 0; 32 | } 33 | 34 | #page-wrapper { 35 | ol.breadcrumb { 36 | border-radius: 0; 37 | margin-bottom: 0; 38 | } 39 | h1.page-header { 40 | margin-top: 30px; 41 | font-size: 24px; 42 | } 43 | } 44 | @media (min-width: 768px) { 45 | #page-wrapper { 46 | ol.breadcrumb { 47 | margin-left: -15px; 48 | margin-right: -15px; 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/templates/djadmin2theme_bootstrap3/includes/save_buttons.html: -------------------------------------------------------------------------------- 1 | {% load i18n admin2_tags %} 2 | 3 |
    4 |
    5 | {% if object %} 6 | 7 | {% trans "Delete" %} 8 | 9 | {% endif %} 10 |
    11 | 14 | 17 | 20 |
    21 |
    22 |
    23 | -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/static/djadmin2theme_bootstrap3/scss/base.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | @import "sb-admin2/sb-admin2"; 3 | 4 | .model-search { 5 | margin-bottom: 15px; 6 | } 7 | 8 | .sort_link { 9 | display: block; 10 | cursor: pointer; 11 | color: black; 12 | } 13 | 14 | .sort_link:hover { 15 | color: black; 16 | } 17 | 18 | .previous-link a { 19 | color: gray; 20 | } 21 | 22 | .date-drilldown { 23 | padding-bottom: 0; 24 | padding-top: 0; 25 | } 26 | 27 | #wrapper { 28 | &.no-sidebar { 29 | #page-wrapper { 30 | margin: 0; 31 | } 32 | } 33 | } 34 | 35 | #page-wrapper { 36 | ol.breadcrumb { 37 | border-radius: 0; 38 | margin-bottom: 0; 39 | } 40 | h1.page-header { 41 | margin-top: 30px; 42 | font-size: 24px; 43 | } 44 | } 45 | @media (min-width: 768px) { 46 | #page-wrapper { 47 | ol.breadcrumb { 48 | margin-left: -15px; 49 | margin-right: -15px; 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /example/blog/templates/blog/blog_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load static %} 3 | 4 | {% block content %} 5 |
    6 |
    7 |

    8 | {% trans "Back to the Blog" %} 9 |

    10 |

    11 | {{ post.title }} 12 |

    13 | 14 | {% if post.published %} 15 |

    16 | {{ post.body }} 17 |

    18 |
      19 | {% for comment in post.comments.all %} 20 |
    • {{ comment.body }}
    • 21 | {% empty %} 22 |
    • No comments yet
    • 23 | {% endfor %} 24 |
    25 | {% else %} 26 |

    27 | {% url 'home' as home_url %} 28 | {% blocktrans %} Unpublished - Choose an admin tool and publish it.{% endblocktrans %} 29 |

    30 | {% endif %} 31 |
    32 |
    33 | {% endblock %} -------------------------------------------------------------------------------- /example/files/locale/en/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the django-admin2 package. 2 | # 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: django-admin2\n" 6 | "Report-Msgid-Bugs-To: \n" 7 | "POT-Creation-Date: 2013-09-11 06:09-0500\n" 8 | "PO-Revision-Date: 2013-07-09 11:57+0200\n" 9 | "Last-Translator: FULL NAME \n" 10 | "Language-Team: LANGUAGE \n" 11 | "Language: en\n" 12 | "MIME-Version: 1.0\n" 13 | "Content-Type: text/plain; charset=UTF-8\n" 14 | "Content-Transfer-Encoding: 8bit\n" 15 | 16 | #: models.py:9 17 | msgid "caption" 18 | msgstr "" 19 | 20 | #: models.py:10 models.py:21 21 | msgid "Uploaded File" 22 | msgstr "" 23 | 24 | #: models.py:16 25 | msgid "Captioned File" 26 | msgstr "" 27 | 28 | #: models.py:17 29 | msgid "Captioned Files" 30 | msgstr "" 31 | 32 | #: models.py:27 33 | msgid "Uncaptioned File" 34 | msgstr "" 35 | 36 | #: models.py:28 37 | msgid "Uncaptioned Files" 38 | msgstr "" 39 | 40 | #: templates/home.html:4 41 | msgid "Example Home" 42 | msgstr "" 43 | 44 | #: templates/home.html:8 45 | msgid "(for reference)" 46 | msgstr "" 47 | -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/templates/djadmin2theme_bootstrap3/includes/pagination.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | 3 | 32 | -------------------------------------------------------------------------------- /example/blog/templates/blog/blog_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load i18n static %} 3 | 4 | {% block content %} 5 |
    6 |
    7 |

    {% trans "The blog of Example.com" %}

    8 | 9 | {% for post in post_list %} 10 |

    11 | 12 | {{ post.title }} 13 | 14 |

    15 | {% if post.published %} 16 |

    17 | {{ post.body }} 18 |

    19 | {% else %} 20 | {% url 'home' as home_url %} 21 |

    22 | {% blocktrans %}Unpublished - Choose an admin tool and publish it.{% endblocktrans %} 23 |

    24 | {% endif %} 25 | {% empty %} 26 |

    {% trans "No Content Yet!" %}

    27 |

    28 | {% url 'home' as home_url %} 29 | {% blocktrans %}Choose an admin tool and add content.{% endblocktrans %} 30 |

    31 | {% endfor %} 32 |
    33 |
    34 | {% endblock %} -------------------------------------------------------------------------------- /fabfile.py: -------------------------------------------------------------------------------- 1 | from fabric.api import local, lcd 2 | from fabric.contrib.console import confirm 3 | 4 | 5 | DIRS = ['djadmin2', 'example/blog', 'example2/polls'] 6 | 7 | 8 | def _run(command, directory): 9 | with lcd(directory): 10 | print('\n### Processing %s...' % directory) 11 | local(command) 12 | 13 | 14 | def makemessages(): 15 | command = 'django-admin.py makemessages -a' 16 | for d in DIRS: 17 | _run(command, d) 18 | 19 | 20 | def compilemessages(): 21 | command = 'django-admin.py compilemessages' 22 | for d in DIRS: 23 | _run(command, d) 24 | 25 | 26 | def checkmessages(): 27 | command = 'ls -1 locale/*/LC_MESSAGES/django.po | xargs -I {} msgfmt -c {}' 28 | for d in DIRS: 29 | _run(command, d) 30 | 31 | 32 | def pulltx(): 33 | print('\n### Pulling new translations from Transifex...') 34 | local('tx pull -a') 35 | 36 | 37 | def pushtx(): 38 | print('\n### Pushing translations and sources to Transifex...') 39 | print('Warning: This might destroy existing translations. Probably you should pull first.') 40 | if confirm('Continue anyways?', default=False): 41 | local('tx push -s -t') 42 | else: 43 | print('Aborting.') 44 | -------------------------------------------------------------------------------- /example/polls/locale/en/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the django-admin2 package. 2 | # 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: django-admin2\n" 6 | "Report-Msgid-Bugs-To: \n" 7 | "POT-Creation-Date: 2013-07-09 11:57+0200\n" 8 | "PO-Revision-Date: 2013-07-09 11:57+0200\n" 9 | "Last-Translator: FULL NAME \n" 10 | "Language-Team: LANGUAGE \n" 11 | "Language: en\n" 12 | "MIME-Version: 1.0\n" 13 | "Content-Type: text/plain; charset=UTF-8\n" 14 | "Content-Transfer-Encoding: 8bit\n" 15 | 16 | #: models.py:12 17 | msgid "question" 18 | msgstr "" 19 | 20 | #: models.py:13 21 | msgid "date published" 22 | msgstr "" 23 | 24 | #: models.py:22 25 | msgid "Published recently?" 26 | msgstr "" 27 | 28 | #: models.py:25 models.py:30 29 | msgid "poll" 30 | msgstr "" 31 | 32 | #: models.py:26 33 | msgid "polls" 34 | msgstr "" 35 | 36 | #: models.py:31 37 | msgid "choice text" 38 | msgstr "" 39 | 40 | #: models.py:32 41 | msgid "votes" 42 | msgstr "" 43 | 44 | #: models.py:38 45 | msgid "choice" 46 | msgstr "" 47 | 48 | #: models.py:39 49 | msgid "choices" 50 | msgstr "" 51 | 52 | #: templates/home.html:4 53 | msgid "Example Home" 54 | msgstr "" 55 | 56 | #: templates/home.html:8 57 | msgid "(for reference)" 58 | msgstr "" 59 | -------------------------------------------------------------------------------- /example/files/locale/zh/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the django-admin2 package. 2 | # 3 | # Translators: 4 | # EricHo , 2013 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: django-admin2\n" 8 | "Report-Msgid-Bugs-To: \n" 9 | "POT-Creation-Date: 2013-09-11 06:09-0500\n" 10 | "PO-Revision-Date: 2013-07-07 11:16+0000\n" 11 | "Last-Translator: EricHo \n" 12 | "Language-Team: Chinese (http://www.transifex.com/projects/p/django-admin2/" 13 | "language/zh/)\n" 14 | "Language: zh\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=1; plural=0;\n" 19 | 20 | #: models.py:9 21 | msgid "caption" 22 | msgstr "" 23 | 24 | #: models.py:10 models.py:21 25 | msgid "Uploaded File" 26 | msgstr "" 27 | 28 | #: models.py:16 29 | msgid "Captioned File" 30 | msgstr "" 31 | 32 | #: models.py:17 33 | msgid "Captioned Files" 34 | msgstr "" 35 | 36 | #: models.py:27 37 | msgid "Uncaptioned File" 38 | msgstr "" 39 | 40 | #: models.py:28 41 | msgid "Uncaptioned Files" 42 | msgstr "" 43 | 44 | #: templates/home.html:4 45 | msgid "Example Home" 46 | msgstr "首頁範本" 47 | 48 | #: templates/home.html:8 49 | msgid "(for reference)" 50 | msgstr "(參考)" 51 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | max-parallel: 5 10 | matrix: 11 | python-version: [3.5, 3.6, 3.7, 3.8] 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Set up Python ${{ matrix.python-version }} 17 | uses: actions/setup-python@v2 18 | with: 19 | python-version: ${{ matrix.python-version }} 20 | 21 | - name: Get pip cache dir 22 | id: pip-cache 23 | run: | 24 | echo "::set-output name=dir::$(pip cache dir)" 25 | 26 | - name: Cache 27 | uses: actions/cache@v2 28 | with: 29 | path: ${{ steps.pip-cache.outputs.dir }} 30 | key: 31 | -${{ matrix.python-version }}-v1-${{ hashFiles('**/setup.py') }} 32 | restore-keys: | 33 | -${{ matrix.python-version }}-v1- 34 | 35 | - name: Install dependencies 36 | run: | 37 | python -m pip install --upgrade pip 38 | python -m pip install --upgrade tox tox-gh-actions 39 | 40 | - name: Tox tests 41 | run: | 42 | tox -v 43 | 44 | - name: Upload coverage 45 | uses: codecov/codecov-action@v1 46 | with: 47 | name: Python ${{ matrix.python-version }} 48 | -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/static/djadmin2theme_bootstrap3/js/actions.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | var element = $("#model-list"); 3 | var selectAllCheckbox = element.find('.model-select-all'); 4 | var selectCheckbox = element.find('.model-select'); 5 | var selectedCount = element.find('.selected-count'); 6 | 7 | var updateSelectedCount = function() { 8 | if (selectedCount.length) { 9 | var count = 0; 10 | for (var ix = 0; ix < selectCheckbox.length; ix++) { 11 | if ($(selectCheckbox[ix]).prop('checked')) { 12 | count++; 13 | } 14 | } 15 | selectAllCheckbox.prop('checked', count == selectCheckbox.length); 16 | selectedCount.text(count); 17 | } 18 | }; 19 | 20 | selectAllCheckbox.click(function(e) { 21 | selectCheckbox.prop('checked', this.checked); 22 | updateSelectedCount(); 23 | }); 24 | 25 | selectCheckbox.click(function(e) { 26 | updateSelectedCount(); 27 | }); 28 | 29 | 30 | var actionDropdownLink = element.find('.dropdown-menu a'); 31 | actionDropdownLink.click(function (e) { 32 | e.preventDefault(); 33 | var form = $(this).closest('form'); 34 | form.find('input[name="' + $(this).data('name') + '"]').val( 35 | $(this).data('value')); 36 | form.submit(); 37 | }); 38 | 39 | }); 40 | -------------------------------------------------------------------------------- /example/files/locale/it/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the django-admin2 package. 2 | # 3 | # Translators: 4 | # brente , 2013 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: django-admin2\n" 8 | "Report-Msgid-Bugs-To: \n" 9 | "POT-Creation-Date: 2013-09-11 06:09-0500\n" 10 | "PO-Revision-Date: 2013-07-07 17:47+0000\n" 11 | "Last-Translator: brente \n" 12 | "Language-Team: Italian (http://www.transifex.com/projects/p/django-admin2/" 13 | "language/it/)\n" 14 | "Language: it\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 19 | 20 | #: models.py:9 21 | msgid "caption" 22 | msgstr "" 23 | 24 | #: models.py:10 models.py:21 25 | msgid "Uploaded File" 26 | msgstr "" 27 | 28 | #: models.py:16 29 | msgid "Captioned File" 30 | msgstr "" 31 | 32 | #: models.py:17 33 | msgid "Captioned Files" 34 | msgstr "" 35 | 36 | #: models.py:27 37 | msgid "Uncaptioned File" 38 | msgstr "" 39 | 40 | #: models.py:28 41 | msgid "Uncaptioned Files" 42 | msgstr "" 43 | 44 | #: templates/home.html:4 45 | msgid "Example Home" 46 | msgstr "" 47 | 48 | #: templates/home.html:8 49 | msgid "(for reference)" 50 | msgstr "(per riferimento)" 51 | -------------------------------------------------------------------------------- /example/files/locale/tl_PH/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the django-admin2 package. 2 | # 3 | # Translators: 4 | # rukku , 2013 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: django-admin2\n" 8 | "Report-Msgid-Bugs-To: \n" 9 | "POT-Creation-Date: 2013-09-11 06:09-0500\n" 10 | "PO-Revision-Date: 2013-07-09 08:22+0000\n" 11 | "Last-Translator: rukku \n" 12 | "Language-Team: Tagalog (Philippines) (http://www.transifex.com/projects/p/" 13 | "django-admin2/language/tl_PH/)\n" 14 | "Language: tl_PH\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" 19 | 20 | #: models.py:9 21 | msgid "caption" 22 | msgstr "" 23 | 24 | #: models.py:10 models.py:21 25 | msgid "Uploaded File" 26 | msgstr "" 27 | 28 | #: models.py:16 29 | msgid "Captioned File" 30 | msgstr "" 31 | 32 | #: models.py:17 33 | msgid "Captioned Files" 34 | msgstr "" 35 | 36 | #: models.py:27 37 | msgid "Uncaptioned File" 38 | msgstr "" 39 | 40 | #: models.py:28 41 | msgid "Uncaptioned Files" 42 | msgstr "" 43 | 44 | #: templates/home.html:4 45 | msgid "Example Home" 46 | msgstr "" 47 | 48 | #: templates/home.html:8 49 | msgid "(for reference)" 50 | msgstr "" 51 | -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/static/djadmin2theme_bootstrap3/js/sb-admin-2.js: -------------------------------------------------------------------------------- 1 | //Loads the correct sidebar on window load, 2 | //collapses the sidebar on window resize. 3 | // Sets the min-height of #page-wrapper to window size 4 | $(function() { 5 | $(window).bind("load resize", function() { 6 | topOffset = 50; 7 | width = (this.window.innerWidth > 0) ? this.window.innerWidth : this.screen.width; 8 | if (width < 768) { 9 | $('div.navbar-collapse').addClass('collapse'); 10 | topOffset = 100; // 2-row-menu 11 | } else { 12 | $('div.navbar-collapse').removeClass('collapse'); 13 | } 14 | 15 | height = ((this.window.innerHeight > 0) ? this.window.innerHeight : this.screen.height) - 1; 16 | height = height - topOffset; 17 | if (height < 1) height = 1; 18 | if (height > topOffset) { 19 | $("#page-wrapper").css("min-height", (height) + "px"); 20 | } 21 | }); 22 | 23 | var url = window.location; 24 | var element = $('ul.nav a').filter(function() { 25 | return this.href == url || url.href.indexOf(this.href) == 0; 26 | }).addClass('active').parent().parent().addClass('in').parent(); 27 | if (element.is('li')) { 28 | element.addClass('active'); 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /example/files/locale/nl/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the django-admin2 package. 2 | # 3 | # Translators: 4 | # Density21.5 , 2013 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: django-admin2\n" 8 | "Report-Msgid-Bugs-To: \n" 9 | "POT-Creation-Date: 2013-09-11 06:09-0500\n" 10 | "PO-Revision-Date: 2013-07-08 10:46+0000\n" 11 | "Last-Translator: Density21.5 \n" 12 | "Language-Team: Dutch (http://www.transifex.com/projects/p/django-admin2/" 13 | "language/nl/)\n" 14 | "Language: nl\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 19 | 20 | #: models.py:9 21 | msgid "caption" 22 | msgstr "" 23 | 24 | #: models.py:10 models.py:21 25 | msgid "Uploaded File" 26 | msgstr "" 27 | 28 | #: models.py:16 29 | msgid "Captioned File" 30 | msgstr "" 31 | 32 | #: models.py:17 33 | msgid "Captioned Files" 34 | msgstr "" 35 | 36 | #: models.py:27 37 | msgid "Uncaptioned File" 38 | msgstr "" 39 | 40 | #: models.py:28 41 | msgid "Uncaptioned Files" 42 | msgstr "" 43 | 44 | #: templates/home.html:4 45 | msgid "Example Home" 46 | msgstr "Voorbeeld Home" 47 | 48 | #: templates/home.html:8 49 | msgid "(for reference)" 50 | msgstr "(ter verwijzing)" 51 | -------------------------------------------------------------------------------- /example/files/locale/pt_BR/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the django-admin2 package. 2 | # 3 | # Translators: 4 | msgid "" 5 | msgstr "" 6 | "Project-Id-Version: django-admin2\n" 7 | "Report-Msgid-Bugs-To: \n" 8 | "POT-Creation-Date: 2013-09-11 06:09-0500\n" 9 | "PO-Revision-Date: 2013-07-09 05:00+0000\n" 10 | "Last-Translator: Douglas Miranda \n" 11 | "Language-Team: Portuguese (Brazil) (http://www.transifex.com/projects/p/" 12 | "django-admin2/language/pt_BR/)\n" 13 | "Language: pt_BR\n" 14 | "MIME-Version: 1.0\n" 15 | "Content-Type: text/plain; charset=UTF-8\n" 16 | "Content-Transfer-Encoding: 8bit\n" 17 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" 18 | 19 | #: models.py:9 20 | msgid "caption" 21 | msgstr "" 22 | 23 | #: models.py:10 models.py:21 24 | msgid "Uploaded File" 25 | msgstr "" 26 | 27 | #: models.py:16 28 | msgid "Captioned File" 29 | msgstr "" 30 | 31 | #: models.py:17 32 | msgid "Captioned Files" 33 | msgstr "" 34 | 35 | #: models.py:27 36 | msgid "Uncaptioned File" 37 | msgstr "" 38 | 39 | #: models.py:28 40 | msgid "Uncaptioned Files" 41 | msgstr "" 42 | 43 | #: templates/home.html:4 44 | msgid "Example Home" 45 | msgstr "Página inicial de exemplo" 46 | 47 | #: templates/home.html:8 48 | msgid "(for reference)" 49 | msgstr "(para referência)" 50 | -------------------------------------------------------------------------------- /example/files/locale/fr/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the django-admin2 package. 2 | # 3 | # Translators: 4 | # NotSqrt , 2013. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: django-admin2\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2013-09-11 06:09-0500\n" 11 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 12 | "Last-Translator: NotSqrt \n" 13 | "Language-Team: French (http://www.transifex.com/projects/p/django-admin2/" 14 | "language/fr/)\n" 15 | "Language: fr\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" 20 | 21 | #: models.py:9 22 | msgid "caption" 23 | msgstr "" 24 | 25 | #: models.py:10 models.py:21 26 | msgid "Uploaded File" 27 | msgstr "" 28 | 29 | #: models.py:16 30 | msgid "Captioned File" 31 | msgstr "" 32 | 33 | #: models.py:17 34 | msgid "Captioned Files" 35 | msgstr "" 36 | 37 | #: models.py:27 38 | msgid "Uncaptioned File" 39 | msgstr "" 40 | 41 | #: models.py:28 42 | msgid "Uncaptioned Files" 43 | msgstr "" 44 | 45 | #: templates/home.html:4 46 | msgid "Example Home" 47 | msgstr "Exemple de page d'accueil" 48 | 49 | #: templates/home.html:8 50 | msgid "(for reference)" 51 | msgstr "(pour référence)" 52 | -------------------------------------------------------------------------------- /djadmin2/tests/test_actions.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | from ..core import Admin2 4 | from ..actions import get_description 5 | from .models import Thing 6 | 7 | 8 | class TestAction: 9 | description = "Test Action Class" 10 | 11 | 12 | def test_function(): 13 | pass 14 | 15 | 16 | class ActionTest(TestCase): 17 | def setUp(self): 18 | self.admin2 = Admin2() 19 | 20 | def test_action_description(self): 21 | self.admin2.register(Thing) 22 | self.admin2.registry[Thing].list_actions.extend([ 23 | TestAction, 24 | test_function, 25 | ]) 26 | self.assertEqual( 27 | get_description( 28 | self.admin2.registry[Thing].list_actions[0] 29 | ), 30 | 'Delete selected items' 31 | ) 32 | self.assertEqual( 33 | get_description( 34 | self.admin2.registry[Thing].list_actions[1] 35 | ), 36 | 'Test Action Class' 37 | ) 38 | self.assertEqual( 39 | get_description( 40 | self.admin2.registry[Thing].list_actions[2] 41 | ), 42 | 'Test function' 43 | ) 44 | self.admin2.registry[Thing].list_actions.remove(TestAction) 45 | self.admin2.registry[Thing].list_actions.remove(test_function) 46 | -------------------------------------------------------------------------------- /example/files/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | from django.db import migrations, models 2 | 3 | 4 | class Migration(migrations.Migration): 5 | 6 | dependencies = [ 7 | ] 8 | 9 | operations = [ 10 | migrations.CreateModel( 11 | name='CaptionedFile', 12 | fields=[ 13 | ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), 14 | ('caption', models.CharField(max_length=200, verbose_name='caption')), 15 | ('publication', models.FileField(verbose_name='Uploaded File', upload_to='captioned-files')), 16 | ], 17 | options={ 18 | 'verbose_name': 'Captioned File', 19 | 'verbose_name_plural': 'Captioned Files', 20 | }, 21 | ), 22 | migrations.CreateModel( 23 | name='UncaptionedFile', 24 | fields=[ 25 | ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), 26 | ('publication', models.FileField(verbose_name='Uploaded File', upload_to='uncaptioned-files')), 27 | ], 28 | options={ 29 | 'verbose_name': 'Uncaptioned File', 30 | 'verbose_name_plural': 'Uncaptioned Files', 31 | }, 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /example/files/locale/de/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the django-admin2 package. 2 | # 3 | # Translators: 4 | # dbrgn , 2013 5 | # Jannis Leidel , 2013 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: django-admin2\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2013-09-11 06:09-0500\n" 11 | "PO-Revision-Date: 2013-07-08 08:49+0000\n" 12 | "Last-Translator: dbrgn \n" 13 | "Language-Team: German (http://www.transifex.com/projects/p/django-admin2/" 14 | "language/de/)\n" 15 | "Language: de\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 20 | 21 | #: models.py:9 22 | msgid "caption" 23 | msgstr "" 24 | 25 | #: models.py:10 models.py:21 26 | msgid "Uploaded File" 27 | msgstr "" 28 | 29 | #: models.py:16 30 | msgid "Captioned File" 31 | msgstr "" 32 | 33 | #: models.py:17 34 | msgid "Captioned Files" 35 | msgstr "" 36 | 37 | #: models.py:27 38 | msgid "Uncaptioned File" 39 | msgstr "" 40 | 41 | #: models.py:28 42 | msgid "Uncaptioned Files" 43 | msgstr "" 44 | 45 | #: templates/home.html:4 46 | msgid "Example Home" 47 | msgstr "Beispiel Start" 48 | 49 | #: templates/home.html:8 50 | msgid "(for reference)" 51 | msgstr "(für's Protokoll)" 52 | -------------------------------------------------------------------------------- /example/files/locale/sk/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the django-admin2 package. 2 | # 3 | # Translators: 4 | # Ivana Kellyerova , 2013 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: django-admin2\n" 8 | "Report-Msgid-Bugs-To: \n" 9 | "POT-Creation-Date: 2013-09-11 06:09-0500\n" 10 | "PO-Revision-Date: 2013-07-07 12:47+0000\n" 11 | "Last-Translator: marekzelinka \n" 12 | "Language-Team: Slovak (http://www.transifex.com/projects/p/django-admin2/" 13 | "language/sk/)\n" 14 | "Language: sk\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" 19 | 20 | #: models.py:9 21 | msgid "caption" 22 | msgstr "" 23 | 24 | #: models.py:10 models.py:21 25 | msgid "Uploaded File" 26 | msgstr "" 27 | 28 | #: models.py:16 29 | msgid "Captioned File" 30 | msgstr "" 31 | 32 | #: models.py:17 33 | msgid "Captioned Files" 34 | msgstr "" 35 | 36 | #: models.py:27 37 | msgid "Uncaptioned File" 38 | msgstr "" 39 | 40 | #: models.py:28 41 | msgid "Uncaptioned Files" 42 | msgstr "" 43 | 44 | #: templates/home.html:4 45 | msgid "Example Home" 46 | msgstr "Ukážka domovskej stránky" 47 | 48 | #: templates/home.html:8 49 | msgid "(for reference)" 50 | msgstr "(pre informáciu)" 51 | -------------------------------------------------------------------------------- /djadmin2/tests/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class Thing(models.Model): 5 | pass 6 | 7 | 8 | class SmallThing(models.Model): 9 | pass 10 | 11 | 12 | class BigThing(models.Model): 13 | pass 14 | 15 | 16 | class TagsTestsModel(models.Model): 17 | 18 | field1 = models.CharField(max_length=23) 19 | field2 = models.CharField('second field', max_length=42) 20 | 21 | def was_published_recently(self): 22 | return True 23 | was_published_recently.boolean = True 24 | was_published_recently.short_description = 'Published recently?' 25 | 26 | class Meta: 27 | verbose_name = "Tags Test Model" 28 | verbose_name_plural = "Tags Test Models" 29 | 30 | 31 | class RendererTestModel(models.Model): 32 | decimal = models.DecimalField(decimal_places=5, max_digits=10) 33 | 34 | 35 | class UtilsTestModel(models.Model): 36 | 37 | field1 = models.CharField(max_length=23) 38 | field2 = models.CharField('second field', max_length=42) 39 | 40 | def simple_method(self): 41 | return 42 42 | 43 | def was_published_recently(self): 44 | return True 45 | was_published_recently.boolean = True 46 | was_published_recently.short_description = 'Published recently?' 47 | 48 | class Meta: 49 | verbose_name = "Utils Test Model" 50 | verbose_name_plural = "Utils Test Models" 51 | -------------------------------------------------------------------------------- /example/polls/models.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from django.db import models 4 | from django.utils import timezone 5 | from django.utils.translation import gettext_lazy as _ 6 | 7 | 8 | class Poll(models.Model): 9 | question = models.CharField(max_length=200, verbose_name=_('question')) 10 | pub_date = models.DateTimeField(verbose_name=_('date published')) 11 | 12 | def __str__(self): 13 | return self.question 14 | 15 | def was_published_recently(self): 16 | return self.pub_date >= timezone.now() - datetime.timedelta(days=1) 17 | was_published_recently.admin_order_field = 'pub_date' 18 | was_published_recently.boolean = True 19 | was_published_recently.short_description = _('Published recently?') 20 | 21 | class Meta: 22 | verbose_name = _('poll') 23 | verbose_name_plural = _('polls') 24 | 25 | 26 | class Choice(models.Model): 27 | poll = models.ForeignKey( 28 | Poll, 29 | verbose_name=_('poll'), 30 | on_delete=models.CASCADE 31 | ) 32 | choice_text = models.CharField( 33 | max_length=200, verbose_name=_('choice text')) 34 | votes = models.IntegerField(default=0, verbose_name=_('votes')) 35 | 36 | def __str__(self): 37 | return self.choice_text 38 | 39 | class Meta: 40 | verbose_name = _('choice') 41 | verbose_name_plural = _('choices') 42 | -------------------------------------------------------------------------------- /djadmin2/tests/templates/djadmin2theme_bootstrap3/custom_login_template.html: -------------------------------------------------------------------------------- 1 | {% extends "djadmin2theme_bootstrap3/base.html" %} 2 | {% load i18n static admin2_tags %} 3 | 4 | {% block navbar %}{% endblock navbar %} 5 | {% block breacrumbs %}{% endblock breacrumbs %} 6 | 7 | {% block page_header %}{% endblock page_header %} 8 | 9 | {% block content %} 10 |
    11 |
    12 |
    13 | 29 |
    30 |
    31 |
    32 | {% endblock content %} 33 | -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/templates/djadmin2theme_bootstrap3/auth/password_change_form.html: -------------------------------------------------------------------------------- 1 | {% extends "djadmin2theme_bootstrap3/base.html" %} 2 | {% load i18n admin2_tags %} 3 | 4 | {% block page_title %}{% trans "Password change" %}: {{ form.user }}{% endblock page_title %} 5 | 6 | {% block breadcrumbs %} 7 |
  • 8 | {% trans "Home" %} 9 |
  • 10 |
  • {% trans "Password change" %} {{ form.user }}
  • 11 | {% endblock breadcrumbs %} 12 | 13 | {% block content %} 14 |
    15 |
    16 |

    {% trans "Please enter your old password, for security's sake, and then enter your new password twice so we can verify you typed it in correctly." %}

    17 | 18 | {% if form.errors %} 19 |

    20 | {% blocktrans count counter=form.errors.items|length %}Please correct the error below.{% plural %} 21 | Please correct the errors below.{% endblocktrans %} 22 |

    23 | {% endif %} 24 | 25 |
    26 | {% csrf_token %} 27 | {{ form }} 28 | 29 |
    30 |
    31 |
    32 | 33 | {% endblock content %} 34 | -------------------------------------------------------------------------------- /example/files/locale/pl_PL/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the django-admin2 package. 2 | # 3 | # Translators: 4 | # dasm , 2013 5 | # Marcin Jabrzyk , 2013 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: django-admin2\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2013-09-11 06:09-0500\n" 11 | "PO-Revision-Date: 2013-07-08 18:04+0000\n" 12 | "Last-Translator: dasm \n" 13 | "Language-Team: Polish (Poland) (http://www.transifex.com/projects/p/django-" 14 | "admin2/language/pl_PL/)\n" 15 | "Language: pl_PL\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " 20 | "|| n%100>=20) ? 1 : 2);\n" 21 | 22 | #: models.py:9 23 | msgid "caption" 24 | msgstr "" 25 | 26 | #: models.py:10 models.py:21 27 | msgid "Uploaded File" 28 | msgstr "" 29 | 30 | #: models.py:16 31 | msgid "Captioned File" 32 | msgstr "" 33 | 34 | #: models.py:17 35 | msgid "Captioned Files" 36 | msgstr "" 37 | 38 | #: models.py:27 39 | msgid "Uncaptioned File" 40 | msgstr "" 41 | 42 | #: models.py:28 43 | msgid "Uncaptioned Files" 44 | msgstr "" 45 | 46 | #: templates/home.html:4 47 | msgid "Example Home" 48 | msgstr "Początek" 49 | 50 | #: templates/home.html:8 51 | msgid "(for reference)" 52 | msgstr "(dla przykładu)" 53 | -------------------------------------------------------------------------------- /example/polls/locale/zh/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the django-admin2 package. 2 | # 3 | # Translators: 4 | # EricHo , 2013 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: django-admin2\n" 8 | "Report-Msgid-Bugs-To: \n" 9 | "POT-Creation-Date: 2013-07-09 11:57+0200\n" 10 | "PO-Revision-Date: 2013-07-07 11:16+0000\n" 11 | "Last-Translator: EricHo \n" 12 | "Language-Team: Chinese (http://www.transifex.com/projects/p/django-admin2/" 13 | "language/zh/)\n" 14 | "Language: zh\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=1; plural=0;\n" 19 | 20 | #: models.py:12 21 | msgid "question" 22 | msgstr "問題" 23 | 24 | #: models.py:13 25 | msgid "date published" 26 | msgstr "發布日期" 27 | 28 | #: models.py:22 29 | msgid "Published recently?" 30 | msgstr "最近發布?" 31 | 32 | #: models.py:25 models.py:30 33 | msgid "poll" 34 | msgstr "投票" 35 | 36 | #: models.py:26 37 | msgid "polls" 38 | msgstr "投票" 39 | 40 | #: models.py:31 41 | msgid "choice text" 42 | msgstr "選擇文字" 43 | 44 | #: models.py:32 45 | msgid "votes" 46 | msgstr "投票" 47 | 48 | #: models.py:38 49 | msgid "choice" 50 | msgstr "選擇" 51 | 52 | #: models.py:39 53 | msgid "choices" 54 | msgstr "選擇" 55 | 56 | #: templates/home.html:4 57 | msgid "Example Home" 58 | msgstr "首頁範本" 59 | 60 | #: templates/home.html:8 61 | msgid "(for reference)" 62 | msgstr "(參考)" 63 | -------------------------------------------------------------------------------- /docs/ref/built-in-views.rst: -------------------------------------------------------------------------------- 1 | Built-In Views 2 | =============== 3 | 4 | Each of these views contains the list of context variables that are included in 5 | their templates. 6 | 7 | .. note:: TODO: Fix the capitalization of context variables! 8 | 9 | View Constants 10 | --------------- 11 | 12 | The following are available in every view: 13 | 14 | :next: The page to redirect the user to after login 15 | :MEDIA_URL: Specify a directory where file uploads for users who use your site go 16 | :STATIC_URL: Specify a directory for JavaScript, CSS and image files. 17 | :user: Currently logged in user 18 | 19 | View Descriptions 20 | ------------------ 21 | 22 | .. autoclass:: djadmin2.views.IndexView 23 | :members: 24 | 25 | 26 | .. autoclass:: djadmin2.views.AppIndexView 27 | :members: 28 | 29 | .. autoclass:: djadmin2.views.ModelListView 30 | :members: 31 | 32 | 33 | .. autoclass:: djadmin2.views.ModelDetailView 34 | :members: 35 | 36 | .. autoclass:: djadmin2.views.ModelEditFormView 37 | :members: 38 | 39 | .. autoclass:: djadmin2.views.ModelAddFormView 40 | :members: 41 | 42 | .. autoclass:: djadmin2.views.ModelDeleteView 43 | :members: 44 | 45 | .. autoclass:: djadmin2.views.PasswordChangeView 46 | :members: 47 | 48 | .. autoclass:: djadmin2.views.PasswordChangeDoneView 49 | :members: 50 | 51 | 52 | .. autoclass:: djadmin2.views.LoginView 53 | :members: 54 | 55 | .. autoclass:: djadmin2.views.LogoutView 56 | :members: 57 | 58 | -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/templates/djadmin2theme_bootstrap3/auth/login.html: -------------------------------------------------------------------------------- 1 | {% extends "djadmin2theme_bootstrap3/base.html" %} 2 | {% load i18n static admin2_tags %} 3 | 4 | {% block navbar %}{% endblock navbar %} 5 | {% block breacrumbs %}{% endblock breacrumbs %} 6 | 7 | {% block page_header %}{% endblock page_header %} 8 | 9 | {% block content %} 10 |
    11 |
    12 |
    13 | 29 |
    30 |
    31 |
    32 | {% endblock content %} 33 | -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/templates/djadmin2theme_bootstrap3/includes/list_actions.html: -------------------------------------------------------------------------------- 1 | {% load i18n admin2_tags %} 2 | 3 | 32 | -------------------------------------------------------------------------------- /example/polls/locale/tl_PH/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the django-admin2 package. 2 | # 3 | # Translators: 4 | # rukku , 2013 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: django-admin2\n" 8 | "Report-Msgid-Bugs-To: \n" 9 | "POT-Creation-Date: 2013-07-09 11:57+0200\n" 10 | "PO-Revision-Date: 2013-07-09 08:22+0000\n" 11 | "Last-Translator: rukku \n" 12 | "Language-Team: Tagalog (Philippines) (http://www.transifex.com/projects/p/" 13 | "django-admin2/language/tl_PH/)\n" 14 | "Language: tl_PH\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" 19 | 20 | #: models.py:12 21 | msgid "question" 22 | msgstr "katanungan" 23 | 24 | #: models.py:13 25 | msgid "date published" 26 | msgstr "" 27 | 28 | #: models.py:22 29 | msgid "Published recently?" 30 | msgstr "Bagong limbag?" 31 | 32 | #: models.py:25 models.py:30 33 | msgid "poll" 34 | msgstr "botohan" 35 | 36 | #: models.py:26 37 | msgid "polls" 38 | msgstr "mga botohan" 39 | 40 | #: models.py:31 41 | msgid "choice text" 42 | msgstr "" 43 | 44 | #: models.py:32 45 | msgid "votes" 46 | msgstr "mga boto" 47 | 48 | #: models.py:38 49 | msgid "choice" 50 | msgstr "sagot" 51 | 52 | #: models.py:39 53 | msgid "choices" 54 | msgstr "mga sagot" 55 | 56 | #: templates/home.html:4 57 | msgid "Example Home" 58 | msgstr "" 59 | 60 | #: templates/home.html:8 61 | msgid "(for reference)" 62 | msgstr "" 63 | -------------------------------------------------------------------------------- /example/polls/locale/it/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the django-admin2 package. 2 | # 3 | # Translators: 4 | # brente , 2013 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: django-admin2\n" 8 | "Report-Msgid-Bugs-To: \n" 9 | "POT-Creation-Date: 2013-07-09 11:57+0200\n" 10 | "PO-Revision-Date: 2013-07-07 17:47+0000\n" 11 | "Last-Translator: brente \n" 12 | "Language-Team: Italian (http://www.transifex.com/projects/p/django-admin2/" 13 | "language/it/)\n" 14 | "Language: it\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 19 | 20 | #: models.py:12 21 | msgid "question" 22 | msgstr "domanda" 23 | 24 | #: models.py:13 25 | msgid "date published" 26 | msgstr "data pubblicazione" 27 | 28 | #: models.py:22 29 | msgid "Published recently?" 30 | msgstr "Pubblicato di recente?" 31 | 32 | #: models.py:25 models.py:30 33 | msgid "poll" 34 | msgstr "sondaggio" 35 | 36 | #: models.py:26 37 | msgid "polls" 38 | msgstr "sondaggi" 39 | 40 | #: models.py:31 41 | msgid "choice text" 42 | msgstr "" 43 | 44 | #: models.py:32 45 | msgid "votes" 46 | msgstr "voti" 47 | 48 | #: models.py:38 49 | msgid "choice" 50 | msgstr "scelta" 51 | 52 | #: models.py:39 53 | msgid "choices" 54 | msgstr "scelte" 55 | 56 | #: templates/home.html:4 57 | msgid "Example Home" 58 | msgstr "" 59 | 60 | #: templates/home.html:8 61 | msgid "(for reference)" 62 | msgstr "(per riferimento)" 63 | -------------------------------------------------------------------------------- /example/polls/locale/nl/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the django-admin2 package. 2 | # 3 | # Translators: 4 | # Density21.5 , 2013 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: django-admin2\n" 8 | "Report-Msgid-Bugs-To: \n" 9 | "POT-Creation-Date: 2013-07-09 11:57+0200\n" 10 | "PO-Revision-Date: 2013-07-08 10:46+0000\n" 11 | "Last-Translator: Density21.5 \n" 12 | "Language-Team: Dutch (http://www.transifex.com/projects/p/django-admin2/" 13 | "language/nl/)\n" 14 | "Language: nl\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 19 | 20 | #: models.py:12 21 | msgid "question" 22 | msgstr "vraag" 23 | 24 | #: models.py:13 25 | msgid "date published" 26 | msgstr "publicatiedatum" 27 | 28 | #: models.py:22 29 | msgid "Published recently?" 30 | msgstr "Recent gepubliceerd?" 31 | 32 | #: models.py:25 models.py:30 33 | msgid "poll" 34 | msgstr "enquête" 35 | 36 | #: models.py:26 37 | msgid "polls" 38 | msgstr "enquêtes" 39 | 40 | #: models.py:31 41 | msgid "choice text" 42 | msgstr "keuzetekst" 43 | 44 | #: models.py:32 45 | msgid "votes" 46 | msgstr "stemmen" 47 | 48 | #: models.py:38 49 | msgid "choice" 50 | msgstr "keuze" 51 | 52 | #: models.py:39 53 | msgid "choices" 54 | msgstr "keuzes" 55 | 56 | #: templates/home.html:4 57 | msgid "Example Home" 58 | msgstr "Voorbeeld Home" 59 | 60 | #: templates/home.html:8 61 | msgid "(for reference)" 62 | msgstr "(ter verwijzing)" 63 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | build: 10 | if: github.repository == 'jazzband/django-admin2' 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | with: 16 | fetch-depth: 0 17 | 18 | - name: Set up Python 19 | uses: actions/setup-python@v2 20 | with: 21 | python-version: 3.8 22 | 23 | - name: Get pip cache dir 24 | id: pip-cache 25 | run: | 26 | echo "::set-output name=dir::$(pip cache dir)" 27 | 28 | - name: Cache 29 | uses: actions/cache@v2 30 | with: 31 | path: ${{ steps.pip-cache.outputs.dir }} 32 | key: release-${{ hashFiles('**/setup.py') }} 33 | restore-keys: | 34 | release- 35 | 36 | - name: Install dependencies 37 | run: | 38 | python -m pip install -U pip 39 | python -m pip install -U setuptools twine wheel 40 | 41 | - name: Build package 42 | run: | 43 | python setup.py --version 44 | python setup.py sdist --format=gztar bdist_wheel 45 | twine check dist/* 46 | 47 | - name: Upload packages to Jazzband 48 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') 49 | uses: pypa/gh-action-pypi-publish@master 50 | with: 51 | user: jazzband 52 | password: ${{ secrets.JAZZBAND_RELEASE_KEY }} 53 | repository_url: https://jazzband.co/projects/django-admin2/upload 54 | -------------------------------------------------------------------------------- /example/polls/locale/fr/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the django-admin2 package. 2 | # 3 | # Translators: 4 | # NotSqrt , 2013. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: django-admin2\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2013-07-09 11:57+0200\n" 11 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 12 | "Last-Translator: NotSqrt \n" 13 | "Language-Team: French (http://www.transifex.com/projects/p/django-admin2/" 14 | "language/fr/)\n" 15 | "Language: fr\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" 20 | 21 | #: models.py:12 22 | msgid "question" 23 | msgstr "question" 24 | 25 | #: models.py:13 26 | msgid "date published" 27 | msgstr "date de publication" 28 | 29 | #: models.py:22 30 | msgid "Published recently?" 31 | msgstr "Publié récemment ?" 32 | 33 | #: models.py:25 models.py:30 34 | msgid "poll" 35 | msgstr "sondage" 36 | 37 | #: models.py:26 38 | msgid "polls" 39 | msgstr "sondages" 40 | 41 | #: models.py:31 42 | msgid "choice text" 43 | msgstr "Texte du choix" 44 | 45 | #: models.py:32 46 | msgid "votes" 47 | msgstr "votes" 48 | 49 | #: models.py:38 50 | msgid "choice" 51 | msgstr "choix" 52 | 53 | #: models.py:39 54 | msgid "choices" 55 | msgstr "choix" 56 | 57 | #: templates/home.html:4 58 | msgid "Example Home" 59 | msgstr "Exemple de page d'accueil" 60 | 61 | #: templates/home.html:8 62 | msgid "(for reference)" 63 | msgstr "(pour référence)" 64 | -------------------------------------------------------------------------------- /example/polls/locale/pt_BR/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the django-admin2 package. 2 | # 3 | # Translators: 4 | msgid "" 5 | msgstr "" 6 | "Project-Id-Version: django-admin2\n" 7 | "Report-Msgid-Bugs-To: \n" 8 | "POT-Creation-Date: 2013-07-09 11:57+0200\n" 9 | "PO-Revision-Date: 2013-07-09 05:00+0000\n" 10 | "Last-Translator: Douglas Miranda \n" 11 | "Language-Team: Portuguese (Brazil) (http://www.transifex.com/projects/p/" 12 | "django-admin2/language/pt_BR/)\n" 13 | "Language: pt_BR\n" 14 | "MIME-Version: 1.0\n" 15 | "Content-Type: text/plain; charset=UTF-8\n" 16 | "Content-Transfer-Encoding: 8bit\n" 17 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" 18 | 19 | #: models.py:12 20 | msgid "question" 21 | msgstr "pergunta" 22 | 23 | #: models.py:13 24 | msgid "date published" 25 | msgstr "data de publicação" 26 | 27 | #: models.py:22 28 | msgid "Published recently?" 29 | msgstr "Publicada recentemente? " 30 | 31 | #: models.py:25 models.py:30 32 | msgid "poll" 33 | msgstr "enquete" 34 | 35 | #: models.py:26 36 | msgid "polls" 37 | msgstr "enquetes" 38 | 39 | #: models.py:31 40 | msgid "choice text" 41 | msgstr "texto da opção" 42 | 43 | #: models.py:32 44 | msgid "votes" 45 | msgstr "votos" 46 | 47 | #: models.py:38 48 | msgid "choice" 49 | msgstr "opção" 50 | 51 | #: models.py:39 52 | msgid "choices" 53 | msgstr "opções" 54 | 55 | #: templates/home.html:4 56 | msgid "Example Home" 57 | msgstr "Página inicial de exemplo" 58 | 59 | #: templates/home.html:8 60 | msgid "(for reference)" 61 | msgstr "(para referência)" 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Daniel Greenfeld. 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 Daniel Greenfeld nor the names of its contributors may be used 15 | to endorse or promote products derived from this software without 16 | 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. -------------------------------------------------------------------------------- /example/polls/locale/sk/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the django-admin2 package. 2 | # 3 | # Translators: 4 | # Ivana Kellyerova , 2013 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: django-admin2\n" 8 | "Report-Msgid-Bugs-To: \n" 9 | "POT-Creation-Date: 2013-07-09 11:57+0200\n" 10 | "PO-Revision-Date: 2013-07-07 12:47+0000\n" 11 | "Last-Translator: marekzelinka \n" 12 | "Language-Team: Slovak (http://www.transifex.com/projects/p/django-admin2/" 13 | "language/sk/)\n" 14 | "Language: sk\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" 19 | 20 | #: models.py:12 21 | msgid "question" 22 | msgstr "otázka" 23 | 24 | #: models.py:13 25 | msgid "date published" 26 | msgstr "dátum zverejnenia" 27 | 28 | #: models.py:22 29 | msgid "Published recently?" 30 | msgstr "Zverejnené nedávno?" 31 | 32 | #: models.py:25 models.py:30 33 | msgid "poll" 34 | msgstr "anketa" 35 | 36 | #: models.py:26 37 | msgid "polls" 38 | msgstr "ankety" 39 | 40 | #: models.py:31 41 | msgid "choice text" 42 | msgstr "text možnosti" 43 | 44 | #: models.py:32 45 | msgid "votes" 46 | msgstr "hlasy" 47 | 48 | #: models.py:38 49 | msgid "choice" 50 | msgstr "možnosť" 51 | 52 | #: models.py:39 53 | msgid "choices" 54 | msgstr "možnosti" 55 | 56 | #: templates/home.html:4 57 | msgid "Example Home" 58 | msgstr "Ukážka domovskej stránky" 59 | 60 | #: templates/home.html:8 61 | msgid "(for reference)" 62 | msgstr "(pre informáciu)" 63 | -------------------------------------------------------------------------------- /example/polls/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | from django.db import migrations, models 2 | 3 | 4 | class Migration(migrations.Migration): 5 | 6 | dependencies = [ 7 | ] 8 | 9 | operations = [ 10 | migrations.CreateModel( 11 | name='Choice', 12 | fields=[ 13 | ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), 14 | ('choice_text', models.CharField(max_length=200, verbose_name='choice text')), 15 | ('votes', models.IntegerField(verbose_name='votes', default=0)), 16 | ], 17 | options={ 18 | 'verbose_name': 'choice', 19 | 'verbose_name_plural': 'choices', 20 | }, 21 | ), 22 | migrations.CreateModel( 23 | name='Poll', 24 | fields=[ 25 | ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), 26 | ('question', models.CharField(max_length=200, verbose_name='question')), 27 | ('pub_date', models.DateTimeField(verbose_name='date published')), 28 | ], 29 | options={ 30 | 'verbose_name': 'poll', 31 | 'verbose_name_plural': 'polls', 32 | }, 33 | ), 34 | migrations.AddField( 35 | model_name='choice', 36 | name='poll', 37 | field=models.ForeignKey( 38 | verbose_name='poll', 39 | to='polls.Poll', 40 | on_delete=models.CASCADE), 41 | ), 42 | ] 43 | -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/templates/djadmin2theme_bootstrap3/edit_inlines/tabular.html: -------------------------------------------------------------------------------- 1 | {% load i18n admin2_tags %} 2 | 3 | 4 | 5 | 6 | {% for field in formset|formset_visible_fieldlist %} 7 | 8 | {% endfor %} 9 | 10 | 11 | 12 | {% for inline_form in formset %} 13 | 14 | {% for field in inline_form.visible_fields %} 15 | 23 | {% endfor %} 24 | {% if not inline_form.visible_fields %} 25 | 30 | {% endif %} 31 | 32 | {% endfor %} 33 | 34 | 41 | 42 | 43 |
    {{ field }}
    16 | {% if forloop.first %} 17 | {% for hidden_field in inline_form.hidden_fields %} 18 | {{ hidden_field }} 19 | {% endfor %} 20 | {% endif %} 21 | {{ field }} 22 | 26 |

    27 | {% trans "This form doesn't have visible fields. This doesn't mean there are no hidden fields." %} 28 |

    29 |
    35 | 40 |
    44 | -------------------------------------------------------------------------------- /example/polls/locale/de/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the django-admin2 package. 2 | # 3 | # Translators: 4 | # dbrgn , 2013 5 | # Jannis Leidel , 2013 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: django-admin2\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2013-07-09 11:57+0200\n" 11 | "PO-Revision-Date: 2013-07-08 08:49+0000\n" 12 | "Last-Translator: dbrgn \n" 13 | "Language-Team: German (http://www.transifex.com/projects/p/django-admin2/" 14 | "language/de/)\n" 15 | "Language: de\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 20 | 21 | #: models.py:12 22 | msgid "question" 23 | msgstr "Frage" 24 | 25 | #: models.py:13 26 | msgid "date published" 27 | msgstr "Veröffentlichungsdatum" 28 | 29 | #: models.py:22 30 | msgid "Published recently?" 31 | msgstr "Kürzlich veröffentlicht?" 32 | 33 | #: models.py:25 models.py:30 34 | msgid "poll" 35 | msgstr "Abstimmung" 36 | 37 | #: models.py:26 38 | msgid "polls" 39 | msgstr "Abstimmungen" 40 | 41 | #: models.py:31 42 | msgid "choice text" 43 | msgstr "Auswahltext" 44 | 45 | #: models.py:32 46 | msgid "votes" 47 | msgstr "Stimmen" 48 | 49 | #: models.py:38 50 | msgid "choice" 51 | msgstr "Auswahl" 52 | 53 | #: models.py:39 54 | msgid "choices" 55 | msgstr "Auswahlmöglichkeiten" 56 | 57 | #: templates/home.html:4 58 | msgid "Example Home" 59 | msgstr "Beispiel Start" 60 | 61 | #: templates/home.html:8 62 | msgid "(for reference)" 63 | msgstr "(für's Protokoll)" 64 | -------------------------------------------------------------------------------- /example/blog/admin2.py: -------------------------------------------------------------------------------- 1 | 2 | from django.utils.translation import gettext_lazy 3 | 4 | from djadmin2 import renderers 5 | from djadmin2.actions import DeleteSelectedAction 6 | 7 | # Import your custom models 8 | from djadmin2.site import djadmin2_site 9 | from djadmin2.types import Admin2TabularInline, ModelAdmin2 10 | from .actions import (CustomPublishAction, PublishAllItemsAction, 11 | unpublish_items, unpublish_all_items) 12 | from .models import Post, Comment 13 | 14 | 15 | class CommentInline(Admin2TabularInline): 16 | model = Comment 17 | 18 | 19 | class PostAdmin(ModelAdmin2): 20 | list_actions = [ 21 | DeleteSelectedAction, CustomPublishAction, 22 | PublishAllItemsAction, unpublish_items, 23 | unpublish_all_items, 24 | ] 25 | inlines = [CommentInline] 26 | search_fields = ('title', '^body') 27 | list_display = ('title', 'body', 'published', "published_date",) 28 | field_renderers = { 29 | 'title': renderers.title_renderer, 30 | } 31 | save_on_top = True 32 | date_hierarchy = "published_date" 33 | ordering = ["-published_date", "title", ] 34 | 35 | 36 | class CommentAdmin(ModelAdmin2): 37 | search_fields = ('body', '=post__title') 38 | list_filter = ['post', ] 39 | actions_on_top = True 40 | actions_on_bottom = True 41 | actions_selection_counter = False 42 | 43 | 44 | # Register the blog app with a verbose name 45 | djadmin2_site.register_app_verbose_name( 46 | 'blog', 47 | gettext_lazy('My Blog') 48 | ) 49 | 50 | # Register each model with the admin 51 | djadmin2_site.register(Post, PostAdmin) 52 | djadmin2_site.register(Comment, CommentAdmin) 53 | -------------------------------------------------------------------------------- /example/polls/locale/pl_PL/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the django-admin2 package. 2 | # 3 | # Translators: 4 | # dasm , 2013 5 | # Marcin Jabrzyk , 2013 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: django-admin2\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2013-07-09 11:57+0200\n" 11 | "PO-Revision-Date: 2013-07-08 18:04+0000\n" 12 | "Last-Translator: dasm \n" 13 | "Language-Team: Polish (Poland) (http://www.transifex.com/projects/p/django-" 14 | "admin2/language/pl_PL/)\n" 15 | "Language: pl_PL\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " 20 | "|| n%100>=20) ? 1 : 2);\n" 21 | 22 | #: models.py:12 23 | msgid "question" 24 | msgstr "pytanie" 25 | 26 | #: models.py:13 27 | msgid "date published" 28 | msgstr "data publikacji" 29 | 30 | #: models.py:22 31 | msgid "Published recently?" 32 | msgstr "Ostatnio opublikowane?" 33 | 34 | #: models.py:25 models.py:30 35 | msgid "poll" 36 | msgstr "ankieta" 37 | 38 | #: models.py:26 39 | msgid "polls" 40 | msgstr "ankiety" 41 | 42 | #: models.py:31 43 | msgid "choice text" 44 | msgstr "tekst opcji" 45 | 46 | #: models.py:32 47 | msgid "votes" 48 | msgstr "głosy" 49 | 50 | #: models.py:38 51 | msgid "choice" 52 | msgstr "opcja" 53 | 54 | #: models.py:39 55 | msgid "choices" 56 | msgstr "opcje" 57 | 58 | #: templates/home.html:4 59 | msgid "Example Home" 60 | msgstr "Początek" 61 | 62 | #: templates/home.html:8 63 | msgid "(for reference)" 64 | msgstr "(dla przykładu)" 65 | -------------------------------------------------------------------------------- /example/files/tests/test_models.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | 3 | from django.contrib.auth.models import User 4 | from django.test import TestCase 5 | from django.urls import reverse 6 | from files.models import CaptionedFile 7 | 8 | 9 | fixture_dir = path.join(path.abspath(path.dirname(__file__)), 'fixtures') 10 | 11 | 12 | class CaptionedFileTestCase(TestCase): 13 | 14 | def setUp(self): 15 | self.captioned_file = CaptionedFile.objects.create( 16 | caption="this is a file", 17 | publication=path.join('pubtest.txt') 18 | ) 19 | self.captioned_file.save() 20 | 21 | def test_creation(self): 22 | cf = CaptionedFile.objects.create( 23 | caption="lo lo", 24 | publication=path.join('pubtest.txt') 25 | ) 26 | cf.save() 27 | self.assertEqual(CaptionedFile.objects.count(), 2) 28 | # Cause setup created one already 29 | 30 | def test_update(self): 31 | self.captioned_file.caption = "I like text files" 32 | self.captioned_file.save() 33 | 34 | cf = CaptionedFile.objects.get() 35 | self.assertEqual(cf.caption, "I like text files") 36 | 37 | def test_delete(self): 38 | cf = CaptionedFile.objects.get() 39 | cf.delete() 40 | 41 | self.assertEqual(CaptionedFile.objects.count(), 0) 42 | 43 | 44 | class MultiEncodedAdminFormTest(TestCase): 45 | def setUp(self): 46 | self.user = User( 47 | username='admin', 48 | is_staff=True, 49 | is_superuser=True) 50 | self.user.set_password('admin') 51 | self.user.save() 52 | self.create_url = reverse('admin2:example3_captioned_file_create') 53 | -------------------------------------------------------------------------------- /example/blog/templates/djadmin2/bootstrap/actions/publish_selected_items.html: -------------------------------------------------------------------------------- 1 | {% extends "djadmin2theme_bootstrap3/base.html" %} 2 | {% load admin2_tags i18n %} 3 | 4 | {% block title %}{% trans "Are you sure?" %}{% endblock title %} 5 | 6 | {% block page_title %}{% trans "Are you sure?" %}{% endblock page_title %} 7 | 8 | {% block breadcrumbs %} 9 |
  • 10 | {% trans "Home" %} 11 |
  • 12 |
  • 13 | {{ app_label|title }} 14 |
  • 15 |
  • 16 | {{ model_name_pluralized|title }} 17 |
  • 18 |
  • 19 | {% trans "Publish" %} 20 |
  • 21 | {% endblock breadcrumbs %} 22 | 23 | 24 | {% block content %} 25 |

    26 | {% blocktrans with objects_name=objects_name count counter=deletable_objects|length %}Are you sure you want to publish the selected {{ objects_name }}? 27 | The following item will be published: 28 | {% plural %}Are you sure you want to publish the selected {{ objects_name }}? 29 | The following items will be published: 30 | {% endblocktrans %} 31 |

    32 | 33 |
      34 | {{ deletable_objects|unordered_list }} 35 |
    36 | 37 |
    38 | {% csrf_token %} 39 | 40 | 41 | {% for item in queryset %} 42 | 43 | {% endfor %} 44 | 45 |
    46 | {% endblock content %} 47 | -------------------------------------------------------------------------------- /djadmin2/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | from django.db import migrations, models 2 | from django.conf import settings 3 | 4 | 5 | class Migration(migrations.Migration): 6 | 7 | dependencies = [ 8 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 9 | ('contenttypes', '0002_remove_content_type_name'), 10 | ] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name='LogEntry', 15 | fields=[ 16 | ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), 17 | ('action_time', models.DateTimeField(verbose_name='action time', auto_now=True)), 18 | ('object_id', models.TextField(verbose_name='object id', null=True, blank=True)), 19 | ('object_repr', models.CharField(max_length=200, verbose_name='object repr')), 20 | ('action_flag', models.PositiveSmallIntegerField(verbose_name='action flag')), 21 | ('change_message', models.TextField(verbose_name='change message', blank=True)), 22 | ('content_type', models.ForeignKey( 23 | related_name='log_entries', 24 | null=True, 25 | blank=True, 26 | to='contenttypes.ContentType', 27 | on_delete=models.CASCADE)), 28 | ('user', models.ForeignKey( 29 | related_name='log_entries', 30 | to=settings.AUTH_USER_MODEL, 31 | on_delete=models.CASCADE)), 32 | ], 33 | options={ 34 | 'verbose_name': 'log entry', 35 | 'ordering': ('-action_time',), 36 | 'verbose_name_plural': 'log entries', 37 | }, 38 | ), 39 | ] 40 | -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/templates/djadmin2theme_bootstrap3/actions/delete_selected_confirmation.html: -------------------------------------------------------------------------------- 1 | {% extends "djadmin2theme_bootstrap3/base.html" %} 2 | {% load i18n admin2_tags %} 3 | 4 | {% block title %}{% trans "Are you sure?" %}{% endblock title %} 5 | 6 | {% block page_title %}{% trans "Are you sure?" %}{% endblock page_title %} 7 | 8 | {% block breadcrumbs %} 9 |
  • 10 | {% trans "Home" %} 11 |
  • 12 |
  • 13 | {% firstof app_verbose_name app_label|title %} 14 |
  • 15 |
  • 16 | {{ model_name_pluralized|title }} 17 |
  • 18 |
  • {% trans "Delete" %}
  • 19 | {% endblock breadcrumbs %} 20 | 21 | {% block content %} 22 |

    23 | {% blocktrans with objects_name=objects_name count counter=deletable_objects|length %}Are you sure you want to delete the selected {{ objects_name }}? The following item will be deleted: 24 | {% plural %}Are you sure you want to delete the selected {{ objects_name }}? The following items will be deleted: 25 | {% endblocktrans %} 26 |

    27 | 28 |
      29 | {{ deletable_objects|unordered_list }} 30 |
    31 | 32 |
    33 | {% csrf_token %} 34 | 35 | 36 | {% for item in queryset %} 37 | 38 | {% endfor %} 39 | 40 |
    41 | {% endblock content %} 42 | -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/templates/djadmin2theme_bootstrap3/model_confirm_delete.html: -------------------------------------------------------------------------------- 1 | {% extends "djadmin2theme_bootstrap3/base.html" %} 2 | {% load i18n admin2_tags %} 3 | 4 | {% block title %}{% trans "Are you sure?" %}{% endblock title %} 5 | 6 | {% block page_title %}{% trans "Are you sure?" %}{% endblock page_title %} 7 | 8 | {% block breadcrumbs %} 9 |
  • 10 | {% trans "Home" %} 11 |
  • 12 |
  • 13 | {% firstof app_verbose_name app_label|title %} 14 |
  • 15 |
  • 16 | {{ model_name_pluralized|title }} 17 |
  • 18 |
  • 19 | {{ object }} 20 |
  • 21 |
  • {% trans "Delete" %}
  • 22 | {% endblock breadcrumbs %} 23 | 24 | {% block content %} 25 |

    26 | {# Translators : this is singular, example : delete the post "My Title" #} 27 | {% blocktrans with model_name=model_name object=object %} 28 | Are you sure you want to delete the {{ model_name }} "{{ object }}"? 29 | {% endblocktrans %} 30 | 31 | {% blocktrans count counter=deletable_objects|length %} 32 | The following item will be deleted: 33 | {% plural %} 34 | All of the following items will be deleted: 35 | {% endblocktrans %} 36 |

    37 | 38 |
      39 | {{ deletable_objects|unordered_list }} 40 |
    41 | 42 |
    43 | {% csrf_token %} 44 | {{ form.as_p }} 45 | 48 |
    49 | {% endblock content %} 50 | -------------------------------------------------------------------------------- /djadmin2/admin2.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.contrib.auth.models import Group, User 3 | from django.contrib.sites.models import Site 4 | from rest_framework.relations import PrimaryKeyRelatedField 5 | 6 | from djadmin2.apiviews import Admin2APISerializer 7 | from djadmin2.forms import UserCreationForm, UserChangeForm 8 | from djadmin2.site import djadmin2_site 9 | from djadmin2.types import ModelAdmin2 10 | 11 | 12 | class GroupSerializer(Admin2APISerializer): 13 | permissions = PrimaryKeyRelatedField(many=True, read_only=True) 14 | 15 | class Meta: 16 | model = Group 17 | fields = '__all__' 18 | 19 | 20 | class GroupAdmin2(ModelAdmin2): 21 | api_serializer_class = GroupSerializer 22 | 23 | 24 | class UserSerializer(Admin2APISerializer): 25 | user_permissions = PrimaryKeyRelatedField(many=True, read_only=True) 26 | 27 | class Meta: 28 | model = User 29 | exclude = ('password',) 30 | 31 | 32 | class UserAdmin2(ModelAdmin2): 33 | create_form_class = UserCreationForm 34 | update_form_class = UserChangeForm 35 | search_fields = ('username', 'groups__name', 'first_name', 'last_name', 36 | 'email') 37 | list_filter = ('is_staff', 'is_superuser', 'is_active', 'groups') 38 | list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff') 39 | 40 | api_serializer_class = UserSerializer 41 | 42 | 43 | # Register each model with the admin 44 | djadmin2_site.register(User, UserAdmin2) 45 | djadmin2_site.register(Group, GroupAdmin2) 46 | 47 | 48 | # Register the sites app if it's been activated in INSTALLED_APPS 49 | if "django.contrib.sites" in settings.INSTALLED_APPS: 50 | 51 | class SiteAdmin2(ModelAdmin2): 52 | list_display = ('domain', 'name') 53 | search_fields = ('domain', 'name') 54 | 55 | djadmin2_site.register(Site, SiteAdmin2) 56 | -------------------------------------------------------------------------------- /example/blog/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.utils.translation import gettext_lazy as _ 3 | 4 | 5 | class Post(models.Model): 6 | title = models.CharField(max_length=255, verbose_name=_('title')) 7 | body = models.TextField(verbose_name=_('body')) 8 | published = models.BooleanField(default=False, verbose_name=_('published')) 9 | published_date = models.DateField(blank=True, null=True) 10 | 11 | def __str__(self): 12 | return self.title 13 | 14 | class Meta: 15 | verbose_name = _('post') 16 | verbose_name_plural = _('posts') 17 | 18 | 19 | class Comment(models.Model): 20 | post = models.ForeignKey( 21 | Post, verbose_name=_('post'), related_name="comments", 22 | on_delete=models.CASCADE) 23 | body = models.TextField(verbose_name=_('body')) 24 | 25 | def __str__(self): 26 | return self.body 27 | 28 | class Meta: 29 | verbose_name = _('comment') 30 | verbose_name_plural = _('comments') 31 | 32 | 33 | # Models needed for testing NestedObjects 34 | 35 | class Count(models.Model): 36 | num = models.PositiveSmallIntegerField() 37 | parent = models.ForeignKey('self', null=True, on_delete=models.CASCADE) 38 | 39 | def __str__(self): 40 | return str(self.num) 41 | 42 | 43 | class Event(models.Model): 44 | date = models.DateTimeField(auto_now_add=True) 45 | 46 | 47 | class Location(models.Model): 48 | event = models.OneToOneField( 49 | Event, verbose_name='awesome event', 50 | on_delete=models.CASCADE 51 | ) 52 | 53 | 54 | class Guest(models.Model): 55 | event = models.OneToOneField(Event, on_delete=models.CASCADE) 56 | name = models.CharField(max_length=255) 57 | 58 | class Meta: 59 | verbose_name = "awesome guest" 60 | 61 | 62 | class EventGuide(models.Model): 63 | event = models.ForeignKey(Event, on_delete=models.DO_NOTHING) 64 | -------------------------------------------------------------------------------- /example/blog/tests/test_filters.py: -------------------------------------------------------------------------------- 1 | import django_filters 2 | from django.test import TestCase 3 | from django.test.client import RequestFactory 4 | from django.urls import reverse 5 | 6 | from djadmin2 import filters as djadmin2_filters 7 | from djadmin2.types import ModelAdmin2 8 | from ..models import Post 9 | 10 | 11 | class ListFilterBuilderTest(TestCase): 12 | 13 | def setUp(self): 14 | self.rf = RequestFactory() 15 | 16 | def test_filter_building(self): 17 | class PostAdminSimple(ModelAdmin2): 18 | list_filter = ['published', ] 19 | 20 | class PostAdminWithFilterInstances(ModelAdmin2): 21 | list_filter = [ 22 | django_filters.BooleanFilter(field_name='published'), 23 | ] 24 | 25 | class FS(django_filters.FilterSet): 26 | class Meta: 27 | model = Post 28 | fields = ['published'] 29 | 30 | class PostAdminWithFilterSetInst(ModelAdmin2): 31 | list_filter = FS 32 | 33 | Post.objects.create(title="post_1_title", body="body") 34 | Post.objects.create(title="post_2_title", body="another body") 35 | request = self.rf.get(reverse("admin2:dashboard")) 36 | list_filter_inst = djadmin2_filters.build_list_filter( 37 | request, 38 | PostAdminSimple, 39 | Post.objects.all(), 40 | ) 41 | self.assertTrue( 42 | issubclass(list_filter_inst.__class__, django_filters.FilterSet) 43 | ) 44 | list_filter_inst = djadmin2_filters.build_list_filter( 45 | request, 46 | PostAdminWithFilterInstances, 47 | Post.objects.all(), 48 | ) 49 | list_filter_inst = djadmin2_filters.build_list_filter( 50 | request, 51 | PostAdminWithFilterSetInst, 52 | Post.objects.all(), 53 | ) 54 | self.assertTrue(isinstance(list_filter_inst, FS)) 55 | -------------------------------------------------------------------------------- /docs/ref/modeladmin.rst: -------------------------------------------------------------------------------- 1 | =========== 2 | ModelAdmin2 3 | =========== 4 | 5 | The `ModelAdmin2` class is the representation of a model in the admin interface. These are stored in a file named `admin2.py` in your application. Let’s take a look at a very simple example of the ModelAdmin2: 6 | 7 | .. code-block:: python 8 | 9 | from .models import Post 10 | from djadmin2.site import djadmin2_site 11 | from djadmin2.types import ModelAdmin2 12 | 13 | class PostAdmin(ModelAdmin2): 14 | pass 15 | 16 | djadmin2_site.register(Post, PostAdmin) 17 | 18 | Adding a new view 19 | ================= 20 | 21 | To add a new view to a ModelAdmin2, it's need add an attribute that is an 22 | instance of the `views.AdminView`. 23 | 24 | The `view.AdminView` takes tree parameters: `url`, `view` and `name`. 25 | The `url` is expected a string for the url pattern for your view. 26 | The `view` is expected a view and `name` is an optional parameter and 27 | is expected a string that is the name of your view. 28 | 29 | .. code-block:: python 30 | 31 | from .models import Post 32 | from djadmin2 import views 33 | from djadmin2.site import djadmin2_site 34 | from djadmin2.types import ModelAdmin2 35 | 36 | class PostAdmin(ModelAdmin2): 37 | preview_post = views.AdminView(r'^preview/$', views.PreviewPostView) 38 | 39 | djadmin2_site.register(Post, PostAdmin) 40 | 41 | Replacing an existing view 42 | ========================== 43 | 44 | To replacing an existing admin view, it's need add an attribute with the same name that 45 | the view that you want replace: 46 | 47 | .. code-block:: python 48 | 49 | from .models import Post 50 | from djadmin2 import views 51 | from djadmin2.site import djadmin2_site 52 | from djadmin2.types import ModelAdmin2 53 | 54 | class PostAdmin(ModelAdmin2): 55 | create_view = views.AdminView(r'^create/$', views.MyCustomCreateView) 56 | 57 | djadmin2_site.register(Post, PostAdmin) 58 | -------------------------------------------------------------------------------- /example/blog/templates/blog/home.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load static i18n %} 3 | 4 | {% block content %} 5 |
    6 |
    7 |

    Example.com

    8 | 9 |

    {% trans "Imagine that this is a real site" %}

    10 | 11 |

    {% trans "Pretend that this is the homepage of a big Django site." %}

    12 | 13 |

    {% trans "Imagine lots of things are here:" %}

    14 |
      15 |
    • {% trans "A blog" %}
    • 16 |
    • {% trans "Comments" %}
    • 17 |
    • {% trans "And so on..." %}
    • 18 |
    19 | 20 |

    {% trans "In other words, these are items that we can introspect through the Django admin." %}

    21 | 22 |

    {% trans "Under the hood" %}

    23 | 24 |

    {% trans "Now, explore the Django admin for example.com. Click on either of the following:" %}

    25 |
    26 |
    27 |
    28 |
    29 | {% url "admin:index" as admin_url %} 30 |

    {% blocktrans %}The original Django Admin{% endblocktrans %}

    31 | 32 | 33 | 34 | 35 | 36 |

    {% trans "Powered by django.contrib.admin. This is just here for reference." %}

    37 |
    38 |
    39 | {% url "admin2:dashboard" as admin2_url %} 40 |

    {% blocktrans %}The new Admin2{% endblocktrans %}

    41 | 42 | 43 | 44 | 45 | 46 |

    {% trans "Powered by django-admin2." %}

    47 |
    48 |
    49 |
    50 | {% url 'blog_list' as blog_url %} 51 |

    {% blocktrans %}See the Blog in Action{% endblocktrans %}

    52 | {% endblock %} 53 | -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/templates/djadmin2theme_bootstrap3/model_history.html: -------------------------------------------------------------------------------- 1 | {% extends "djadmin2theme_bootstrap3/base.html" %} 2 | {% load i18n admin2_tags %} 3 | 4 | {% block title %}{% trans "History for" %} {{ object }}{% endblock title %} 5 | 6 | {% block page_title %}{% trans "History for" %} {{ object }}{% endblock page_title %} 7 | 8 | {% block breadcrumbs %} 9 |
  • 10 | {% trans "Home" %} 11 |
  • 12 |
  • 13 | {% firstof app_verbose_name app_label|title %} 14 |
  • 15 |
  • 16 | {{ model_name_pluralized|title }} 17 |
  • 18 |
  • 19 | {{ object }} 20 |
  • 21 |
  • {% trans "History" %}
  • 22 | {% endblock breadcrumbs %} 23 | 24 | {% block content %} 25 |

    26 | {% blocktrans with object=object %}History for {{ object }}{% endblocktrans %} 27 | 28 | {% if object_list %} 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | {% for log in object_list %} 40 | 41 | 42 | 43 | 44 | 45 | 46 | {% endfor %} 47 | 48 |
    {% trans "Date/Time" %}{% trans "User" %}{% trans "Action" %}{% trans "Message" %}
    {{ log.action_time }}{{ log.user }}{{ log.action_type|capfirst }}{{ log.change_message }}
    49 | {% else %} 50 |

    {% trans "No history for this object." %}

    51 | {% endif %} 52 | {% endblock content %} 53 | -------------------------------------------------------------------------------- /docs/ref/renderers.rst: -------------------------------------------------------------------------------- 1 | ================ 2 | Custom Renderers 3 | ================ 4 | 5 | It is possible to create custom renderers for specific fields. Currently they 6 | are only used in the object list view, for example to render boolean values 7 | using icons. Another example would be to customize the rendering of dates. 8 | 9 | 10 | Renderers 11 | --------- 12 | 13 | A renderer is a function that accepts a value and the field and returns a HTML 14 | representation of it. For example, the very simple builtin datetime renderer 15 | works like this: 16 | 17 | .. code-block:: python 18 | 19 | def title_renderer(value, field): 20 | """Render a string in title case (capitalize every word).""" 21 | return unicode(value).title() 22 | 23 | In this case the ``field`` argument is not used. Sometimes it useful though: 24 | 25 | .. code-block:: python 26 | 27 | def number_renderer(value, field): 28 | """Format a number.""" 29 | if isinstance(field, models.DecimalField): 30 | return formats.number_format(value, field.decimal_places) 31 | return formats.number_format(value) 32 | 33 | You can create your renderers anywhere in your code, but it is recommended to 34 | put them in a file called ``renderers.py`` in your project. 35 | 36 | 37 | Using Renderers 38 | --------------- 39 | 40 | The renderers can be specified in the Admin2 class using the 41 | ``field_renderers`` attribute. The attribute contains a dictionary that maps a 42 | field name to a renderer function. 43 | 44 | By default, some renderers are automatically applied, for example the boolean 45 | renderer when processing boolean values. If you want to suppress that renderer, 46 | you can assign ``None`` to the field in the ``field_renderers`` dictionary. 47 | 48 | .. code-block:: python 49 | 50 | class PostAdmin(djadmin2.ModelAdmin2): 51 | list_display = ('title', 'body', 'published') 52 | field_renderers = { 53 | 'title': renderers.title_renderer, 54 | 'published': None, 55 | } 56 | 57 | 58 | Builtin Renderers 59 | ----------------- 60 | 61 | .. automodule:: djadmin2.renderers 62 | :members: 63 | -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/templates/djadmin2theme_bootstrap3/includes/app_model_list.html: -------------------------------------------------------------------------------- 1 | {% load i18n admin2_tags %} 2 | 3 | 4 | 5 | 6 | 13 | 14 | 15 | 16 | {% for model_class, model_admin in registry.items %} 17 | {% with permissions|for_admin:model_admin as permissions %} 18 | {% if permissions.has_view_permission or permissions.has_add_permission or permissions.has_change_permission %} 19 | 20 | 27 | 34 | 41 | 42 | {% endif %} 43 | {% endwith %} 44 | {% endfor %} 45 | 46 |
    7 | 8 | {% with app_verbose_names|verbose_name_for:app_label as verbose_name %} 9 | {% firstof verbose_name app_label|title %} 10 | {% endwith %} 11 | 12 |
    21 | {% if permissions.has_view_permission %} 22 | 23 | {% endif %} 24 | {{ model_admin.verbose_name_plural|title }} 25 | {% if permissions.has_view_permission %}{% endif %} 26 | 28 | {% if permissions.has_add_permission %} 29 | 30 | {% trans "Add" %} 31 | 32 | {% endif %} 33 | 35 | {% if permissions.has_change_permission %} 36 | 37 | {% trans "Change" %} 38 | 39 | {% endif %} 40 |
    47 | -------------------------------------------------------------------------------- /example/blog/templates/base.html: -------------------------------------------------------------------------------- 1 | {% load i18n %}{% load static %} 2 | 3 | 4 | 5 | 6 | 7 | Example.com 8 | {% block css %} 9 | 12 | 15 | 18 | 19 | 20 | 24 | {% endblock css %} 25 | 26 | 27 |
    28 | {% block content %}{% endblock content %} 29 |
    30 | {% block javascript %} 31 | 33 | 36 | {% endblock javascript %} 37 | 38 | 39 | -------------------------------------------------------------------------------- /example/blog/actions.py: -------------------------------------------------------------------------------- 1 | from django.contrib import messages 2 | from django.utils.translation import gettext_lazy, pgettext_lazy 3 | 4 | from djadmin2 import permissions 5 | from djadmin2.actions import BaseListAction 6 | 7 | 8 | class CustomPublishAction(BaseListAction): 9 | 10 | permission_classes = BaseListAction.permission_classes + ( 11 | permissions.ModelChangePermission, 12 | ) 13 | 14 | description = gettext_lazy('Publish selected items') 15 | success_message = pgettext_lazy( 16 | 'singular form', 17 | 'Successfully published %(count)s %(items)s') 18 | success_message_plural = pgettext_lazy( 19 | 'plural form', 20 | 'Successfully published %(count)s %(items)s') 21 | 22 | default_template_name = "actions/publish_selected_items.html" 23 | 24 | def process_queryset(self): 25 | self.get_queryset().update(published=True) 26 | 27 | 28 | class PublishAllItemsAction(BaseListAction): 29 | permission_classes = BaseListAction.permission_classes + ( 30 | permissions.ModelChangePermission, 31 | ) 32 | 33 | description = gettext_lazy('Publish all items') 34 | success_message = pgettext_lazy( 35 | 'singular form', 36 | 'Successfully published %(count)s %(items)s', 37 | ) 38 | 39 | success_message_plural = pgettext_lazy( 40 | 'plural form', 41 | 'Successfully published %(count)s %(items)s', 42 | ) 43 | 44 | default_template_name = "model_list.html" 45 | only_selected = False 46 | 47 | def process_queryset(self): 48 | self.get_queryset().update(published=True) 49 | 50 | 51 | def unpublish_items(request, queryset): 52 | queryset.update(published=False) 53 | messages.add_message(request, messages.INFO, 54 | gettext_lazy(u'Items unpublished')) 55 | 56 | 57 | # Translators : action description 58 | unpublish_items.description = gettext_lazy('Unpublish selected items') 59 | 60 | 61 | def unpublish_all_items(request, queryset): 62 | queryset.update(published=False) 63 | messages.add_message( 64 | request, 65 | messages.INFO, 66 | gettext_lazy('Items unpublished'), 67 | ) 68 | 69 | 70 | unpublish_all_items.description = gettext_lazy('Unpublish all items') 71 | unpublish_all_items.only_selected = False 72 | -------------------------------------------------------------------------------- /docs/faq.rst: -------------------------------------------------------------------------------- 1 | Frequently Asked Questions 2 | =========================== 3 | 4 | Is this intended to go into Django contrib? 5 | ---------------------------------------------- 6 | 7 | No. 8 | 9 | Reasons why it won't be going into Django core: 10 | 11 | 1. We want to rely on external dependencies 12 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 13 | 14 | We think certain packages can do a lot of the heavy lifting for us, and rewriting them is more time taken away from fixing bugs and implementing features. Since the Django core team isn't likely to accept external dependencies, especially ones that rely on Django itself, this alone is reason enough for django-admin2 to never make it into Django contrib. 15 | 16 | 2. We want increased Speed of Development 17 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 18 | 19 | Django is a huge project with a lot of people relying on it. The conservative pace at which any change or enhancement is accepted is usually boon to the community of developers who work with it. Also, the committee-based management system means everyone gets a voice. This means things often happen at a slow and steady pace. 20 | 21 | However, there are times when it's good to be outside of core, especially for experimental replacements for core functionality. Working outside of Django core means we can do what we want, when we want it. 22 | 23 | What's wrong with the Django Admin? 24 | ----------------------------------- 25 | 26 | The existing Django Admin is a powerful tool with pretty extensive extension capabilities. That said, it does have several significant issues. 27 | 28 | Doesn't handle a million-record foreign key relation 29 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 30 | 31 | Say you have a million users and a model with a foreign key relation to them. You go the model detail field in the admin and you know what happens? The Django admin tries to serve out a million option links to your browser. Django doesn't handle this well, and neither does your browser. You can fix this yourself, find a third-party package to do it for you, or use django-admin2. 32 | 33 | Yes, before release 1.0 of django-admin2 it will handle this problem for you. 34 | 35 | Uses an early version of Class-Based Views 36 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 37 | 38 | TODO 39 | 40 | Very Challenging to Theme 41 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 42 | 43 | TODO -------------------------------------------------------------------------------- /djadmin2/tests/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | from django.db import migrations, models 2 | 3 | 4 | class Migration(migrations.Migration): 5 | 6 | dependencies = [ 7 | ] 8 | 9 | operations = [ 10 | migrations.CreateModel( 11 | name='BigThing', 12 | fields=[ 13 | ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), 14 | ], 15 | ), 16 | migrations.CreateModel( 17 | name='RendererTestModel', 18 | fields=[ 19 | ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), 20 | ('decimal', models.DecimalField(max_digits=10, decimal_places=5)), 21 | ], 22 | ), 23 | migrations.CreateModel( 24 | name='SmallThing', 25 | fields=[ 26 | ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), 27 | ], 28 | ), 29 | migrations.CreateModel( 30 | name='TagsTestsModel', 31 | fields=[ 32 | ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), 33 | ('field1', models.CharField(max_length=23)), 34 | ('field2', models.CharField(max_length=42, verbose_name='second field')), 35 | ], 36 | options={ 37 | 'verbose_name': 'Tags Test Model', 38 | 'verbose_name_plural': 'Tags Test Models', 39 | }, 40 | ), 41 | migrations.CreateModel( 42 | name='Thing', 43 | fields=[ 44 | ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), 45 | ], 46 | ), 47 | migrations.CreateModel( 48 | name='UtilsTestModel', 49 | fields=[ 50 | ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), 51 | ('field1', models.CharField(max_length=23)), 52 | ('field2', models.CharField(max_length=42, verbose_name='second field')), 53 | ], 54 | options={ 55 | 'verbose_name': 'Utils Test Model', 56 | 'verbose_name_plural': 'Utils Test Models', 57 | }, 58 | ), 59 | ] 60 | -------------------------------------------------------------------------------- /example/blog/tests/test_builtin_api_resources.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import Group, User 2 | from django.urls import reverse 3 | 4 | from .test_apiviews import APITestCase 5 | 6 | 7 | class UserAPITest(APITestCase): 8 | def test_list_response_ok(self): 9 | self.client.login(username='admin', password='admin') 10 | response = self.client.get(reverse('admin2:auth_user_api_list')) 11 | self.assertEqual(response.status_code, 200) 12 | 13 | def test_list_view_permission(self): 14 | response = self.client.get(reverse('admin2:auth_user_api_list')) 15 | self.assertEqual(response.status_code, 403) 16 | 17 | def test_detail_response_ok(self): 18 | self.client.login(username='admin', password='admin') 19 | user = User.objects.create_user( 20 | username='Foo', 21 | password='bar') 22 | response = self.client.get( 23 | reverse('admin2:auth_user_api_detail', args=(user.pk,))) 24 | self.assertEqual(response.status_code, 200) 25 | 26 | def test_detail_view_permission(self): 27 | user = User.objects.create_user( 28 | username='Foo', 29 | password='bar') 30 | response = self.client.get( 31 | reverse('admin2:auth_user_api_detail', args=(user.pk,))) 32 | self.assertEqual(response.status_code, 403) 33 | 34 | 35 | class GroupAPITest(APITestCase): 36 | def test_list_response_ok(self): 37 | self.client.login(username='admin', password='admin') 38 | response = self.client.get(reverse('admin2:auth_group_api_list')) 39 | self.assertEqual(response.status_code, 200) 40 | 41 | def test_list_view_permission(self): 42 | response = self.client.get(reverse('admin2:auth_group_api_list')) 43 | self.assertEqual(response.status_code, 403) 44 | 45 | def test_detail_response_ok(self): 46 | self.client.login(username='admin', password='admin') 47 | group = Group.objects.create(name='group') 48 | response = self.client.get( 49 | reverse('admin2:auth_group_api_detail', args=(group.pk,))) 50 | self.assertEqual(response.status_code, 200) 51 | 52 | def test_detail_view_permission(self): 53 | group = Group.objects.create(name='group') 54 | response = self.client.get( 55 | reverse('admin2:auth_group_api_detail', args=(group.pk,))) 56 | self.assertEqual(response.status_code, 403) 57 | -------------------------------------------------------------------------------- /djadmin2/tests/test_auth_admin.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.contrib.auth.models import User 3 | from django.test import TestCase 4 | from django.test.client import RequestFactory 5 | from django.urls import reverse 6 | 7 | 8 | from djadmin2.site import djadmin2_site 9 | from ..admin2 import UserAdmin2 10 | 11 | 12 | class UserAdminTest(TestCase): 13 | def setUp(self): 14 | self.factory = RequestFactory() 15 | self.user = User( 16 | username='admin', 17 | is_staff=True, 18 | is_superuser=True) 19 | self.user.set_password('admin') 20 | self.user.save() 21 | 22 | def test_create_form_uses_floppyform_widgets(self): 23 | form = UserAdmin2.create_form_class() 24 | self.assertTrue( 25 | isinstance(form.fields['username'].widget, 26 | forms.TextInput)) 27 | 28 | request = self.factory.get(reverse('admin2:auth_user_create')) 29 | request.user = self.user 30 | model_admin = UserAdmin2(User, djadmin2_site) 31 | view = model_admin.create_view.view.as_view( 32 | **model_admin.get_create_kwargs()) 33 | response = view(request) 34 | form = response.context_data['form'] 35 | self.assertTrue( 36 | isinstance(form.fields['username'].widget, 37 | forms.TextInput)) 38 | 39 | def test_update_form_uses_floppyform_widgets(self): 40 | form = UserAdmin2.update_form_class() 41 | self.assertTrue( 42 | isinstance(form.fields['username'].widget, 43 | forms.TextInput)) 44 | self.assertTrue( 45 | isinstance(form.fields['date_joined'].widget, 46 | forms.DateTimeInput)) 47 | 48 | request = self.factory.get( 49 | reverse('admin2:auth_user_update', args=(self.user.pk,))) 50 | request.user = self.user 51 | model_admin = UserAdmin2(User, djadmin2_site) 52 | view = model_admin.update_view.view.as_view( 53 | **model_admin.get_update_kwargs()) 54 | response = view(request, pk=self.user.pk) 55 | form = response.context_data['form'] 56 | self.assertTrue( 57 | isinstance(form.fields['username'].widget, 58 | forms.TextInput)) 59 | self.assertTrue( 60 | isinstance(form.fields['date_joined'].widget, 61 | forms.DateTimeInput)) 62 | -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/static/djadmin2theme_bootstrap3/libs/html5shiv.js: -------------------------------------------------------------------------------- 1 | /* 2 | HTML5 Shiv v3.7.0 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | (function(l,f){function m(){var a=e.elements;return"string"==typeof a?a.split(" "):a}function i(a){var b=n[a[o]];b||(b={},h++,a[o]=h,n[h]=b);return b}function p(a,b,c){b||(b=f);if(g)return b.createElement(a);c||(c=i(b));b=c.cache[a]?c.cache[a].cloneNode():r.test(a)?(c.cache[a]=c.createElem(a)).cloneNode():c.createElem(a);return b.canHaveChildren&&!s.test(a)?c.frag.appendChild(b):b}function t(a,b){if(!b.cache)b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag(); 5 | a.createElement=function(c){return!e.shivMethods?b.createElem(c):p(c,a,b)};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/[\w\-]+/g,function(a){b.createElem(a);b.frag.createElement(a);return'c("'+a+'")'})+");return n}")(e,b.frag)}function q(a){a||(a=f);var b=i(a);if(e.shivCSS&&!j&&!b.hasCSS){var c,d=a;c=d.createElement("p");d=d.getElementsByTagName("head")[0]||d.documentElement;c.innerHTML="x"; 6 | c=d.insertBefore(c.lastChild,d.firstChild);b.hasCSS=!!c}g||t(a,b);return a}var k=l.html5||{},s=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,r=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,j,o="_html5shiv",h=0,n={},g;(function(){try{var a=f.createElement("a");a.innerHTML="";j="hidden"in a;var b;if(!(b=1==a.childNodes.length)){f.createElement("a");var c=f.createDocumentFragment();b="undefined"==typeof c.cloneNode|| 7 | "undefined"==typeof c.createDocumentFragment||"undefined"==typeof c.createElement}g=b}catch(d){g=j=!0}})();var e={elements:k.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output progress section summary template time video",version:"3.7.0",shivCSS:!1!==k.shivCSS,supportsUnknownElements:g,shivMethods:!1!==k.shivMethods,type:"default",shivDocument:q,createElement:p,createDocumentFragment:function(a,b){a||(a=f); 8 | if(g)return a.createDocumentFragment();for(var b=b||i(a),c=b.frag.cloneNode(),d=0,e=m(),h=e.length;dthis form." % self.get_update_password_url()) 62 | 63 | def get_update_password_url(self): 64 | if self.instance and self.instance.pk: 65 | return reverse_lazy('admin2:password_change', args=[self.instance.pk]) 66 | return 'password/' 67 | -------------------------------------------------------------------------------- /djadmin2/tests/test_core.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import Group, User 2 | from django.contrib.sites.models import Site 3 | from django.core.exceptions import ImproperlyConfigured 4 | from django.test import TestCase 5 | 6 | from djadmin2.site import djadmin2_site 7 | from .models import SmallThing 8 | from ..core import Admin2 9 | from ..types import ModelAdmin2 10 | 11 | APP_LABEL, APP_VERBOSE_NAME = 'app_one_label', 'App One Verbose Name' 12 | 13 | 14 | class Admin2Test(TestCase): 15 | def setUp(self): 16 | self.admin2 = Admin2() 17 | 18 | def test_register(self): 19 | self.admin2.register(SmallThing) 20 | self.assertTrue(isinstance(self.admin2.registry[SmallThing], ModelAdmin2)) 21 | 22 | def test_register_error(self): 23 | self.admin2.register(SmallThing) 24 | self.assertRaises(ImproperlyConfigured, self.admin2.register, SmallThing) 25 | 26 | def test_deregister(self): 27 | self.admin2.register(SmallThing) 28 | self.admin2.deregister(SmallThing) 29 | self.assertTrue(SmallThing not in self.admin2.registry) 30 | 31 | def test_deregister_error(self): 32 | self.assertRaises(ImproperlyConfigured, self.admin2.deregister, SmallThing) 33 | 34 | def test_register_app_verbose_name(self): 35 | self.admin2.register_app_verbose_name(APP_LABEL, APP_VERBOSE_NAME) 36 | self.assertEqual( 37 | self.admin2.app_verbose_names[APP_LABEL], 38 | APP_VERBOSE_NAME 39 | ) 40 | 41 | def test_register_app_verbose_name_error(self): 42 | self.admin2.register_app_verbose_name(APP_LABEL, APP_VERBOSE_NAME) 43 | self.assertRaises( 44 | ImproperlyConfigured, 45 | self.admin2.register_app_verbose_name, 46 | APP_LABEL, 47 | APP_VERBOSE_NAME 48 | ) 49 | 50 | def test_deregister_app_verbose_name(self): 51 | self.admin2.register_app_verbose_name(APP_LABEL, APP_VERBOSE_NAME) 52 | self.admin2.deregister_app_verbose_name(APP_LABEL) 53 | self.assertTrue(APP_LABEL not in self.admin2.app_verbose_names) 54 | 55 | def test_deregister_app_verbose_name_error(self): 56 | self.assertRaises( 57 | ImproperlyConfigured, 58 | self.admin2.deregister_app_verbose_name, 59 | APP_LABEL 60 | ) 61 | 62 | def test_get_urls(self): 63 | self.admin2.register(SmallThing) 64 | self.assertEqual(8, len(self.admin2.get_urls())) 65 | 66 | def test_default_entries(self): 67 | expected_default_models = (User, Group, Site) 68 | for model in expected_default_models: 69 | self.assertTrue(isinstance(djadmin2_site.registry[model], ModelAdmin2)) 70 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | CONTRIBUTORS 2 | ============ 3 | 4 | Project Lead 5 | ------------ 6 | 7 | * Asif Saif Uddin (@auvipy) 8 | * Daniel Greenfeld (@pydanny / ) 9 | 10 | Translation Managers 11 | -------------------- 12 | 13 | * Henri Colas (@NotSqrt) 14 | * Danilo Bargen (@dbrgn) 15 | * Asif Saif Uddin (@auvipy) 16 | 17 | Developers 18 | ---------- 19 | 20 | * Audrey Roy (@audreyr) 21 | * Peter Ingelsby (@inglesp) 22 | * Ludvig Wadenstein (@ludw) 23 | * Raphael Kimmig (@RaphaelKimmig) 24 | * Andrew Ingram (@AndrewIngram) 25 | * Gregor Müllegger (@gregmuellegger) 26 | * Rivo Laks (@rivol) 27 | * Chris Lawlor (@chrislawlor) 28 | * Ben Tappin 29 | * Allison Kapture (@akapture) 30 | * Roman Gladkov (@d1ffuz0r / ) 31 | * Pau Rosello Van Schoor (@paurosello) 32 | * Wade Austin (@waustin) 33 | * the5fire (@the5fire) 34 | * Andrews Medina (@andrewsmedina / ) 35 | * Wade Austin (@waustin) 36 | * Douglas Miranda (@douglasmiranda / ) 37 | * Ethan Soergel (@esoergel / ) 38 | * Ryan Balfanz (@RyanBalfanz / ) 39 | * Tom Christie (@tomchristie) 40 | * Chris Jones (@chrisjones-brack3t / ) 41 | * Danilo Bargen (@dbrgn) 42 | * Ignasi Fosch Alonso (@ifosch) 43 | * Henri Colas (@NotSqrt) 44 | * Andy Boot (@bootandy) 45 | * Eleonore Mayola (@Eleonore9) 46 | * Michal Kuffa (@beezz / Michal Kuffa) 47 | * Tom Viner (@tomviner) 48 | * Marek Zelinkaa (@marekzelinka) 49 | * Andrea de Marco (@z4r) 50 | * Kenneth Love (@kennethlove / ) 51 | * Kevin Diale (@powersurge360 / ) 52 | * James Rivett-Carnac (@yarbelk / james.rivettcarnac@gmail.com) 53 | * Andrew Mosson (@amosson / amosson@tippit.com) 54 | * marangonico 55 | * Kamil Gałuszka (@galuszkak / galuszkak@gmail.com) 56 | * Germano Gabbianelli (@tyrion) 57 | * Arthur (@arthur-wsw / arthur@wallstreetweb.net) 58 | 59 | Translators 60 | ----------- 61 | 62 | Catalan 63 | 64 | * Ignasi Fosch Alonso (@NaTx) 65 | 66 | Chinese 67 | 68 | * Eric Ho (@EricHo) 69 | * Shiyao.Ma (@introom) 70 | 71 | French 72 | 73 | * Henri Colas (@NotSqrt) 74 | 75 | German 76 | 77 | * Danilo Bargen (@dbrgn) 78 | * Jannis Leidel (@jezdez) 79 | * Albrecht Mühlenschulte (@a7p) 80 | 81 | Italian 82 | 83 | * Margherita Zamponi (@margherita.zamponi) 84 | 85 | Polish 86 | 87 | * Dariusz Smigiel (@dasm) 88 | * Marcin Jabrzyk (@bzyx) 89 | 90 | Portuguese (Brazil) 91 | 92 | * andrewsmedina 93 | 94 | Slovak 95 | 96 | * Marek Zelinkaa (@marekzelinka) 97 | * Ivana Kellyérová (@eruraina) 98 | 99 | Spanish 100 | 101 | * Ignasi Fosch Alonso (@NaTx) 102 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | ========================================= 2 | Welcome to django-admin2's documentation! 3 | ========================================= 4 | 5 | .. image:: https://github.com/jazzband/django-admin2/workflows/Test/badge.svg 6 | :target: https://github.com/jazzband/django-admin2/actions 7 | :alt: GitHub Actions 8 | 9 | **Warning:** This project is currently in an **alpha** state and currently not meant for real projects. 10 | 11 | One of the most useful parts of ``django.contrib.admin`` is the ability to configure various views that touch and alter data. django-admin2 is a complete rewrite of that library using modern Class-Based Views and enjoying a design focused on extendibility and adaptability. By starting over, we can avoid the legacy code and make it easier to write extensions and themes. 12 | 13 | **django-admin2** aims to replace django's built-in admin that lives in 14 | ``django.contrib.admin``. Come and help us, read the :doc:`design` and 15 | :doc:`contributing` pages, and visit the `GitHub`_ project. 16 | 17 | This project is intentionally backwards-incompatible with ``django.contrib.admin``. 18 | 19 | Features 20 | ============= 21 | 22 | * Rewrite of the Django Admin backend 23 | * Drop-in themes 24 | * Built-in RESTful API 25 | 26 | 27 | Basic API 28 | ============== 29 | 30 | If you've worked with Django, this implementation should look familiar: 31 | 32 | .. code-block:: python 33 | 34 | # myapp/admin2.py 35 | # Import your custom models 36 | from django.contrib.auth.forms import UserCreationForm, UserChangeForm 37 | from django.contrib.auth.models import User 38 | 39 | from .models import Post, Comment 40 | 41 | from djadmin2.site import djadmin2_site 42 | from djadmin2.types import ModelAdmin2 43 | 44 | 45 | class UserAdmin2(ModelAdmin2): 46 | create_form_class = UserCreationForm 47 | update_form_class = UserChangeForm 48 | 49 | 50 | # Register each model with the admin 51 | djadmin2_site.register(Post) 52 | djadmin2_site.register(Comment) 53 | djadmin2_site.register(User, UserAdmin2) 54 | 55 | .. _GitHub: https://github.com/twoscoops/django-admin2 56 | 57 | Content 58 | ------- 59 | 60 | .. toctree:: 61 | :maxdepth: 2 62 | 63 | installation 64 | contributing 65 | design 66 | faq 67 | api 68 | themes 69 | meta 70 | reference 71 | internationalization 72 | tutorial 73 | 74 | Reference 75 | ----------- 76 | 77 | 78 | .. toctree:: 79 | :maxdepth: 2 80 | 81 | ref/themes 82 | ref/api 83 | ref/actions 84 | ref/forms 85 | ref/permissions 86 | ref/views 87 | ref/modeladmin 88 | ref/built-in-views 89 | ref/renderers 90 | ref/meta 91 | 92 | Indices and tables 93 | ================== 94 | 95 | * :ref:`genindex` 96 | * :ref:`search` 97 | -------------------------------------------------------------------------------- /docs/_ext/djangodocs.py: -------------------------------------------------------------------------------- 1 | # Taken from https://github.com/django/django/blob/main/docs/_ext/djangodocs.py 2 | 3 | import re 4 | from sphinx import addnodes 5 | 6 | 7 | # RE for option descriptions without a '--' prefix 8 | simple_option_desc_re = re.compile( 9 | r'([-_a-zA-Z0-9]+)(\s*.*?)(?=,\s+(?:/|-|--)|$)') 10 | 11 | 12 | def setup(app): 13 | app.add_crossref_type( 14 | directivename="setting", 15 | rolename="setting", 16 | indextemplate="pair: %s; setting", 17 | ) 18 | app.add_crossref_type( 19 | directivename="templatetag", 20 | rolename="ttag", 21 | indextemplate="pair: %s; template tag" 22 | ) 23 | app.add_crossref_type( 24 | directivename="templatefilter", 25 | rolename="tfilter", 26 | indextemplate="pair: %s; template filter" 27 | ) 28 | app.add_crossref_type( 29 | directivename="fieldlookup", 30 | rolename="lookup", 31 | indextemplate="pair: %s; field lookup type", 32 | ) 33 | app.add_description_unit( 34 | directivename="django-admin", 35 | rolename="djadmin", 36 | indextemplate="pair: %s; django-admin command", 37 | parse_node=parse_django_admin_node, 38 | ) 39 | app.add_description_unit( 40 | directivename="django-admin-option", 41 | rolename="djadminopt", 42 | indextemplate="pair: %s; django-admin command-line option", 43 | parse_node=parse_django_adminopt_node, 44 | ) 45 | 46 | 47 | def parse_django_admin_node(env, sig, signode): 48 | command = sig.split(' ')[0] 49 | env._django_curr_admin_command = command 50 | title = "django-admin.py %s" % sig 51 | signode += addnodes.desc_name(title, title) 52 | return sig 53 | 54 | 55 | def parse_django_adminopt_node(env, sig, signode): 56 | """A copy of sphinx.directives.CmdoptionDesc.parse_signature()""" 57 | from sphinx.domains.std import option_desc_re 58 | count = 0 59 | firstname = '' 60 | for m in option_desc_re.finditer(sig): 61 | optname, args = m.groups() 62 | if count: 63 | signode += addnodes.desc_addname(', ', ', ') 64 | signode += addnodes.desc_name(optname, optname) 65 | signode += addnodes.desc_addname(args, args) 66 | if not count: 67 | firstname = optname 68 | count += 1 69 | if not count: 70 | for m in simple_option_desc_re.finditer(sig): 71 | optname, args = m.groups() 72 | if count: 73 | signode += addnodes.desc_addname(', ', ', ') 74 | signode += addnodes.desc_name(optname, optname) 75 | signode += addnodes.desc_addname(args, args) 76 | if not count: 77 | firstname = optname 78 | count += 1 79 | if not firstname: 80 | raise ValueError 81 | return firstname 82 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Installation 3 | ============ 4 | 5 | .. index:: installation 6 | 7 | Adding django-admin2 to your project 8 | ==================================== 9 | 10 | 11 | Use pip to install from PyPI: 12 | 13 | .. code-block:: python 14 | 15 | pip install django-admin2 16 | 17 | Add djadmin2 and rest_framework to your settings file: 18 | 19 | .. code-block:: python 20 | 21 | INSTALLED_APPS = ( 22 | ... 23 | 'djadmin2', 24 | 'djadmin2.themes.djadmin2theme_bootstrap3', # for the default theme 25 | 'rest_framework', # for the browsable API templates 26 | ... 27 | ) 28 | 29 | REST_FRAMEWORK = { 30 | 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 31 | 'PAGE_SIZE': 10 32 | } 33 | ADMIN2_THEME_DIRECTORY = "djadmin2theme_bootstrap3" 34 | 35 | Add djadmin2 urls to your URLconf: 36 | 37 | .. code-block:: python 38 | 39 | # urls.py 40 | from django.conf.urls import include 41 | 42 | from djadmin2.site import djadmin2_site 43 | 44 | djadmin2_site.autodiscover() 45 | 46 | urlpatterns = [ 47 | ... 48 | url(r'^admin2/', include(djadmin2_site.urls)), 49 | ] 50 | 51 | Development Installation 52 | ========================= 53 | 54 | See :doc:`contributing`. 55 | 56 | Migrating from 0.6.x 57 | ==================== 58 | 59 | - The default theme has been updated to bootstrap3, be sure to replace your reference to the new one. 60 | - Django rest framework also include multiple pagination system, the only one supported now is the PageNumberPagination. 61 | 62 | Therefore, your `settings` need to include this: 63 | 64 | .. code-block:: python 65 | 66 | # In settings.py 67 | INSTALLED_APPS += ('djadmin2.themes.djadmin2theme_bootstrap3',) 68 | ADMIN2_THEME_DIRECTORY = "djadmin2theme_bootstrap3" 69 | 70 | REST_FRAMEWORK = { 71 | 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 72 | 'PAGE_SIZE': 10 73 | } 74 | 75 | 76 | The default admin2 site has move into djadmin2.site make sure your use the news djadmin2_site in your urls.py: 77 | 78 | .. code-block:: python 79 | 80 | # urls.py 81 | from django.conf.urls import include 82 | 83 | from djadmin2.site import djadmin2_site 84 | 85 | djadmin2_site.autodiscover() 86 | 87 | urlpatterns = [ 88 | ... 89 | url(r'^admin2/', include(djadmin2_site.urls)), 90 | ] 91 | 92 | 93 | Migrating from 0.5.x 94 | ==================== 95 | 96 | Themes are now defined explicitly, including the default theme. Therefore, your `settings` need to include this: 97 | 98 | .. code-block:: python 99 | 100 | # In settings.py 101 | INSTALLED_APPS += ('djadmin2.themes.djadmin2theme_default',) 102 | ADMIN2_THEME_DIRECTORY = "djadmin2theme_default" 103 | -------------------------------------------------------------------------------- /example/polls/tests/test_models.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from django.utils import timezone 3 | from polls.models import Choice 4 | from polls.models import Poll 5 | 6 | 7 | class PollTestCase(TestCase): 8 | 9 | def setUp(self): 10 | self.poll = Poll.objects.create( 11 | question="mine", 12 | pub_date=timezone.now() 13 | ) 14 | self.poll.save() 15 | 16 | def test_creation(self): 17 | p = Poll.objects.create( 18 | question="lo lo", 19 | pub_date=timezone.now() 20 | ) 21 | p.save() 22 | self.assertEqual(Poll.objects.count(), 2) 23 | # Cause setup created one already 24 | 25 | def test_update(self): 26 | # TODO Add code 27 | # change self.poll.question to "yours" 28 | self.poll.question = "yours" 29 | # do self.poll.save() 30 | self.poll.save() 31 | 32 | # TODO Add assertions 33 | # make p = Poll.objects.get() 34 | p = Poll.objects.get() 35 | # add self.assertEqual(p.question, "yours") 36 | self.assertEqual(p.question, "yours") 37 | 38 | def test_delete(self): 39 | # TODO Add code 40 | # get from the db using poll question 41 | p = Poll.objects.get() 42 | # delete poll from the db 43 | p.delete() 44 | 45 | # TODO Add assertions 46 | # check if d is empty 47 | self.assertEqual(Poll.objects.count(), 0) 48 | 49 | 50 | class ChoiceTestCase(TestCase): 51 | 52 | def setUp(self): 53 | self.poll = Poll.objects.create( 54 | question="mine", 55 | pub_date=timezone.now() 56 | ) 57 | self.poll.save() 58 | self.choice = Choice.objects.create( 59 | poll=self.poll, 60 | choice_text="first text", 61 | votes=2 62 | ) 63 | 64 | def test_choice_creation(self): 65 | # code 66 | # add another choice 67 | p = Choice.objects.create( 68 | poll=self.poll, 69 | choice_text="second text", 70 | votes=5 71 | ) 72 | p.save() 73 | 74 | # assertion 75 | #check that there are two choices 76 | self.assertEqual(Choice.objects.count(), 2) 77 | 78 | def test_choice_update(self): 79 | # code 80 | # change a choice 81 | self.choice.choice_text = "third text" 82 | self.choice.save() 83 | p = Choice.objects.get() 84 | 85 | # assertion 86 | # check the choice is egal to the new choice 87 | self.assertEqual(p.choice_text, "third text") 88 | 89 | def test_choice_delete(self): 90 | # code 91 | # get Choice obj and delete it 92 | p = Choice.objects.get() 93 | p.delete() 94 | 95 | # assertion 96 | # check there are nothing in db 97 | self.assertEqual(Choice.objects.count(), 0) 98 | -------------------------------------------------------------------------------- /djadmin2/tests/test_admin2tags.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.forms.formsets import formset_factory 3 | from django.test import TestCase 4 | 5 | from ..templatetags import admin2_tags 6 | from ..views import IndexView 7 | from .models import TagsTestsModel 8 | 9 | 10 | class TagsTestForm(forms.Form): 11 | visible_1 = forms.CharField() 12 | visible_2 = forms.CharField() 13 | invisible_1 = forms.HiddenInput() 14 | 15 | 16 | TagsTestFormSet = formset_factory(TagsTestForm) 17 | 18 | 19 | class TagsTests(TestCase): 20 | 21 | def setUp(self): 22 | self.instance = TagsTestsModel() 23 | 24 | def test_admin2_urlname(self): 25 | self.assertEqual( 26 | "admin2:None_None_index", 27 | admin2_tags.admin2_urlname(IndexView, "index") 28 | ) 29 | 30 | def test_model_verbose_name_as_model_class(self): 31 | self.assertEqual( 32 | TagsTestsModel._meta.verbose_name, 33 | admin2_tags.model_verbose_name(TagsTestsModel) 34 | ) 35 | 36 | def test_model_verbose_name_as_model_instance(self): 37 | self.assertEqual( 38 | self.instance._meta.verbose_name, 39 | admin2_tags.model_verbose_name(self.instance) 40 | ) 41 | 42 | def test_model_verbose_name_plural_as_model_class(self): 43 | self.assertEqual( 44 | TagsTestsModel._meta.verbose_name_plural, 45 | admin2_tags.model_verbose_name_plural(TagsTestsModel) 46 | ) 47 | 48 | def test_model_verbose_name_plural_as_model_instance(self): 49 | self.assertEqual( 50 | self.instance._meta.verbose_name_plural, 51 | admin2_tags.model_verbose_name_plural(self.instance) 52 | ) 53 | 54 | def test_model_field_verbose_name_autogenerated(self): 55 | self.assertEqual( 56 | 'field1', 57 | admin2_tags.model_attr_verbose_name(self.instance, 'field1') 58 | ) 59 | 60 | def test_model_field_verbose_name_overridden(self): 61 | self.assertEqual( 62 | 'second field', 63 | admin2_tags.model_attr_verbose_name(self.instance, 'field2') 64 | ) 65 | 66 | def test_model_method_verbose_name(self): 67 | self.assertEqual( 68 | 'Published recently?', 69 | admin2_tags.model_attr_verbose_name(self.instance, 'was_published_recently') 70 | ) 71 | 72 | def test_formset_visible_fieldlist(self): 73 | formset = TagsTestFormSet() 74 | self.assertEqual( 75 | admin2_tags.formset_visible_fieldlist(formset), 76 | ['Visible 1', 'Visible 2'] 77 | ) 78 | 79 | def test_verbose_name_for(self): 80 | app_verbose_names = { 81 | 'app_one_label': 'App One Verbose Name', 82 | } 83 | self.assertEqual( 84 | "App One Verbose Name", 85 | admin2_tags.verbose_name_for(app_verbose_names, 'app_one_label') 86 | ) 87 | -------------------------------------------------------------------------------- /djadmin2/tests/test_types.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | from .. import views 4 | from ..types import ModelAdmin2, immutable_admin_factory 5 | from ..core import Admin2 6 | from .models import BigThing 7 | 8 | 9 | class ModelAdmin: 10 | model_admin_attributes = ['a', 'b', 'c'] 11 | a = 1 # covered 12 | b = 2 # covered 13 | c = 3 # covered 14 | d = 4 # not covered 15 | 16 | 17 | class ImmutableAdminFactoryTests(TestCase): 18 | 19 | def setUp(self): 20 | self.immutable_admin = immutable_admin_factory(ModelAdmin) 21 | 22 | def test_immutability(self): 23 | with self.assertRaises(AttributeError): 24 | # can't set attribute 25 | self.immutable_admin.a = 10 26 | with self.assertRaises(AttributeError): 27 | # 'ImmutableAdmin' object has no attribute 'e' 28 | self.immutable_admin.e = 5 29 | with self.assertRaises(AttributeError): 30 | # can't delete attribute 31 | del self.immutable_admin.a 32 | 33 | def test_attributes(self): 34 | self.assertEqual(self.immutable_admin.a, 1) 35 | self.assertEqual(self.immutable_admin.b, 2) 36 | self.assertEqual(self.immutable_admin.c, 3) 37 | with self.assertRaises(AttributeError): 38 | # 'ImmutableAdmin' object has no attribute 'd' 39 | self.immutable_admin.d 40 | 41 | 42 | class ModelAdminTest(TestCase): 43 | 44 | def setUp(self): 45 | class MyModelAdmin(ModelAdmin2): 46 | my_view = views.AdminView(r'^$', views.ModelListView) 47 | 48 | self.model_admin = MyModelAdmin 49 | 50 | def test_views(self): 51 | views = [self.model_admin.my_view] + ModelAdmin2.views 52 | self.assertListEqual(self.model_admin.views, views) 53 | 54 | def test_views_not_same(self): 55 | self.assertIsNot(self.model_admin.views, ModelAdmin2.views) 56 | 57 | def test_get_index_kwargs(self): 58 | admin_instance = ModelAdmin2(BigThing, Admin2) 59 | self.assertIn( 60 | 'paginate_by', 61 | admin_instance.get_index_kwargs().keys() 62 | ) 63 | 64 | def test_get_urls(self): 65 | admin_instance = ModelAdmin2(BigThing, Admin2) 66 | self.assertEqual(6, len(admin_instance.get_urls())) 67 | 68 | def test_get_urls_throws_type_error(self): 69 | with self.assertRaises(TypeError): 70 | try: 71 | admin_instance = ModelAdmin2(BigThing, Admin2) 72 | admin_instance.views = [views.AdminView(None, None, None)] 73 | admin_instance.get_urls() 74 | 75 | except TypeError as e: 76 | message = "Cannot instantiate admin view " \ 77 | '"ModelAdmin2.None". The error that got raised was: ' \ 78 | "'NoneType' object has no attribute 'as_view'" 79 | self.assertEqual(e.args[0], message) 80 | raise 81 | -------------------------------------------------------------------------------- /docs/ref/views.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | Views 3 | ===== 4 | 5 | TODO list 6 | 7 | * Describe customization of model views 8 | * Show how to use ModelAdmin2 inheritance so an entire project works off a custom base view. 9 | 10 | Customizing the Dashboard view 11 | ============================== 12 | 13 | When you first log into django-admin2, just like ``django.contrib.admin`` you are presented with a display of apps and models. While this is useful for developers, it isn't friendly for end-users. Fortunately, django-admin2 makes it trivial to switch out the standard dashboard view. 14 | 15 | However, because this is the dashboard view, the method of customization and configuration is different than other django-admin2 views. 16 | 17 | In your Django project's root URLconf module (``urls.py``) modify the code to include the commented code before the ``djadmin2_site.autodiscover()``: 18 | 19 | .. code-block:: python 20 | 21 | from django.conf.urls import include, url 22 | 23 | from djadmin2.site import djadmin2_site 24 | from djadmin2.views import IndexView 25 | 26 | 27 | ######### Begin django-admin2 customization code 28 | # Create a new django-admin2 index view 29 | class CustomIndexView(IndexView): 30 | 31 | # specify the template 32 | default_template_name = "custom_dashboard_template.html" 33 | 34 | # override the default index_view 35 | djadmin2_site.index_view = CustomIndexView 36 | ######### end django-admin2 customization code 37 | 38 | djadmin2_site.autodiscover() 39 | 40 | urlpatterns = [ 41 | url(r'^admin2/', include(djadmin2_site.urls)), 42 | # ... Place the rest of the project URLs here 43 | ] 44 | 45 | In real projects the new IndexView would likely be placed into a ``views.py`` module. 46 | 47 | .. note:: Considering that dashboard is more intuitive of a name, perhaps the ``IndexView`` should be renamed ``DashboardView``? 48 | 49 | Customizing the Login view 50 | ========================== 51 | 52 | The login view could also be customized. 53 | 54 | In your Django project's root URLconf module (``urls.py``) modify the code to include the commented code before the ``djadmin2.default.autodiscover()``: 55 | 56 | .. code-block:: python 57 | 58 | from django.conf.urls import patterns, include, url 59 | 60 | from djadmin2.site import djadmin2_site 61 | from djadmin2.views import LoginView 62 | 63 | 64 | ######### Begin django-admin2 customization code 65 | # Create a new django-admin2 index view 66 | class CustomLoginView(LoginView): 67 | 68 | # specify the template 69 | default_template_name = "custom_login_template.html" 70 | 71 | # override the default index_view 72 | djadmin2_site.login_view = CustomLoginView 73 | ######### end django-admin2 customization code 74 | 75 | djadmin2_site.autodiscover() 76 | 77 | urlpatterns = patterns('', 78 | url(r'^admin2/', include(djadmin2_site.urls)), 79 | # ... Place the rest of the project URLs here 80 | ) 81 | 82 | In real projects the new LoginView would likely be placed into a ``views.py`` module. 83 | -------------------------------------------------------------------------------- /djadmin2/themes/djadmin2theme_bootstrap3/templates/djadmin2theme_bootstrap3/model_update_form.html: -------------------------------------------------------------------------------- 1 | {% extends "djadmin2theme_bootstrap3/base.html" %} 2 | 3 | {% load i18n admin2_tags %} 4 | 5 | {# Translators : examples : Add post, Change object #} 6 | {% block title %}{% blocktrans with action=action model_name=model_name %}{{ action_name }} {{ model_name }} 7 | {% endblocktrans %}{% endblock title %} 8 | 9 | {# Translators : examples : Add post, Change object #} 10 | {% block page_title %}{% blocktrans with action=action model_name=model_name %}{{ action_name }} {{ model_name }} 11 | {% endblocktrans %}{% endblock page_title %} 12 | 13 | {% block page_title_link %} 14 | {% if object.pk %} 15 | {% trans "History" %} 16 | {% endif %} 17 | {% endblock page_title_link %} 18 | 19 | {% block breadcrumbs %} 20 |
  • 21 | {% trans "Home" %} 22 | 23 |
  • 24 |
  • 25 | {% firstof app_verbose_name app_label|title %} 26 | 27 |
  • 28 |
  • 29 | {{ model_name_pluralized|title }} 30 | 31 |
  • 32 | {% if action == 'Add' %} 33 |
  • {{ action_name }}
  • 34 | {% else %} 35 |
  • 36 | {{ object }} 37 | 38 |
  • 39 |
  • {% trans 'Change' %}
  • 40 | {% endif %} 41 | {% endblock breadcrumbs %} 42 | 43 | 44 | {% block content %} 45 |
    46 |
    47 | {% if view.model_admin.save_on_top %} 48 |
    49 | {% include "djadmin2theme_bootstrap3/includes/save_buttons.html" %} 50 |
    51 | {% endif %} 52 | 53 |
    54 |
    55 | {% csrf_token %} 56 | {{ form.as_p }} 57 | {% if not form.visible_fields %} 58 |

    59 | {% trans "This form doesn't have visible fields. This doesn't mean there are no hidden fields." %} 60 |

    61 | {% endif %} 62 | {% for formset in inlines %} 63 |
    64 |
    65 |

    {{ formset.model|model_verbose_name_plural|capfirst }}

    66 | {{ formset.management_form }} 67 | {% include formset.template %} 68 | {% endfor %} 69 |
    70 |
    71 | {% if view.model_admin.save_on_bottom %} 72 | 75 | {% endif %} 76 |
    77 |
    78 | {% endblock content %} 79 | -------------------------------------------------------------------------------- /docs/ref/forms.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | Forms 3 | ===== 4 | 5 | Replicating `django.contrib.admin`'s user management 6 | ====================================================== 7 | 8 | If you have users, it's assumed you will have a Django app to manage them, called something like `accounts`, `users`, or `profiles`. For this exercise, we'll assume the app is called `accounts`. 9 | 10 | Step 1 - The admin2.py module 11 | ----------------------------- 12 | 13 | In the `accounts` app, create an ``admin2.py`` module. 14 | 15 | Step 2 - Web Integration 16 | ------------------------ 17 | 18 | Enter the following code in ``accounts/admin2.py``: 19 | 20 | .. code-block:: python 21 | 22 | # Import the User and Group model from django.contrib.auth 23 | from django.contrib.auth import get_user_model 24 | from django.contrib.auth.models import Group 25 | 26 | from djadmin2.site import djadmin2_site 27 | from djadmin2.forms import UserCreationForm, UserChangeForm 28 | from djadmin2.types import ModelAdmin2 29 | 30 | # fetch the User model 31 | User = get_user_model() 32 | 33 | # Incorporate the 34 | class UserAdmin2(ModelAdmin2): 35 | create_form_class = UserCreationForm 36 | update_form_class = UserChangeForm 37 | 38 | djadmin2_site.register(User, UserAdmin2) 39 | djadmin2_site.register(Group) 40 | 41 | Done! The User and Group controls will appear in your django-admin2 dashboard. 42 | 43 | Well... almost. We still need to incorporate the API components. 44 | 45 | Step 3 - API Integration 46 | ------------------------ 47 | 48 | Change ``accounts/admin2.py`` to the following: 49 | 50 | .. code-block:: python 51 | 52 | # Import the User and Group model from django.contrib.auth 53 | from django.contrib.auth import get_user_model 54 | from django.contrib.auth.models import Group 55 | 56 | from rest_framework.relations import PrimaryKeyRelatedField 57 | 58 | import djadmin2 59 | 60 | # fetch the User model 61 | User = get_user_model() 62 | 63 | 64 | # Serialize the groups 65 | class GroupSerializer(Admin2APISerializer): 66 | permissions = PrimaryKeyRelatedField(many=True) 67 | 68 | class Meta: 69 | model = Group 70 | 71 | # The GroupAdmin2 object is synonymous with GroupAdmin 72 | class GroupAdmin2(djadmin2.ModelAdmin2): 73 | api_serializer_class = GroupSerializer 74 | 75 | 76 | # Serialize the users, excluding password data 77 | class UserSerializer(djadmin2.apiviews.Admin2APISerializer): 78 | user_permissions = PrimaryKeyRelatedField(many=True) 79 | 80 | class Meta: 81 | model = User 82 | exclude = ('passwords',) 83 | 84 | 85 | # The UserAdmin2 object is synonymous with UserAdmin 86 | class UserAdmin2(djadmin2.ModelAdmin2): 87 | create_form_class = UserCreationForm 88 | update_form_class = UserChangeForm 89 | 90 | api_serializer_class = UserSerializer 91 | 92 | djadmin2.default.register(User, UserAdmin2) 93 | djadmin2.default.register(Group, GroupAdmin2) 94 | 95 | Things to Do 96 | ================= 97 | 98 | 99 | * Consider breaking the user management reference into more steps 100 | * Create default UserAdmin2 and GroupAdmin2 classes 101 | * Demonstrate how to easy it is to customize and HTML5-ize forms 102 | * Demonstrate how easy it is to customize widgets -------------------------------------------------------------------------------- /docs/design.rst: -------------------------------------------------------------------------------- 1 | ====== 2 | Design 3 | ====== 4 | 5 | .. index:: Design 6 | single: Design; Constraints 7 | 8 | Constraints 9 | ------------ 10 | 11 | This section outlines the design constraints that django-admin2 follows: 12 | 13 | 1. There will be nothing imported from ``django.contrib.admin``. 14 | 2. The original bootstrap/ theme shall contain no UI enhancements beyond the original ``django.contrib.admin`` UI. (However, future themes can and should be experimental.) 15 | 3. External package dependencies are allowed but should be very limited. 16 | 4. Building a django-admin2 theme cannot involve learning Python, which explains why we are not using tools like django-crispy-forms. (One of our goals is to make it easier for designers to explore theming django-admin2). 17 | 18 | .. index:: 19 | single: Design; Backend Goals 20 | 21 | Backend Goals 22 | --------------- 23 | 24 | Rather than creating yet another project that skins ``django.contrib.admin``, our goal is to rewrite ``django.contrib.admin`` from the ground up using Class-Based Views, better state management, and attention to all the lessons learned from difficult admin customizations over the years. 25 | 26 | While the internal API for the backend may be drastically different, the end goal is to achieve relative parity with existing functionality in an extendable way: 27 | 28 | * Relative functional parity with ``django.contrib.admin``. This is our desire to replicate much of the existing functionality, but not have to worry too much about coding ourselves into an overly-architected corner. 29 | * Ability handle well under high load situations with many concurrent users. This is diametrically opposite from `django.contrib.admin` which doesn't work well in this regard. 30 | * Extensible presentation and data views in such a way that it does not violate Constraint #4. To cover many cases, we will provide instructions on how to use the REST API to fetch data rather than create overly complex backend code. 31 | * Create an architecture that follows the "*Principle of least surprise*". Things should behave as you expect them to, and you should be blocked from making dangerous mistakes. This is the reason for the ImmutableAdmin type. 32 | 33 | Clean code with substantial documentation is also a goal: 34 | 35 | 1. Create a clearly understandable/testable code base. 36 | 2. All classes/methods/functions documented. 37 | 3. Provide a wealth of in-line code documentation. 38 | 39 | .. index:: 40 | single: Design; REST API Goals 41 | 42 | REST API Goals 43 | ---------------- 44 | 45 | There are a lot of various cases that are hard to handle with pure HTML projects, but are trivial to resolve if a REST API is available. For example, using unmodified ``django.contrib.admin`` on projects with millions of database records combined with foreign key lookups. In order to handle these cases, rather than explore each edge case, ``django-admin2`` provides a RESTFUL API as of version 0.2.0. 46 | 47 | Goals: 48 | 49 | 1. Provide a extendable self-documenting API (django-rest-framework). 50 | 2. Reuse components from the HTML view. 51 | 3. Backwards compatibility: Use a easily understood API versioning system so we can expand functionality of the API without breaking existing themes. 52 | 53 | .. index:: 54 | single: Design; UI Goals 55 | 56 | UI Goals 57 | --------- 58 | 59 | 1. Replicate the old admin UI as closely as possible in the bootstrap/ theme. This helps us ensure that admin2/ functionality has parity with admin/. 60 | 61 | 2. Once (1) is complete and we have a stable underlying API, experiment with more interesting UI variations. 62 | -------------------------------------------------------------------------------- /djadmin2/models.py: -------------------------------------------------------------------------------- 1 | """ Boilerplate for now, will serve a purpose soon! """ 2 | from django.conf import settings 3 | from django.contrib.contenttypes.models import ContentType 4 | from django.db import models 5 | from django.utils.encoding import force_str 6 | from django.utils.translation import gettext, gettext_lazy as _ 7 | 8 | from .utils import quote 9 | 10 | 11 | class LogEntryManager(models.Manager): 12 | def log_action(self, user_id, obj, action_flag, change_message=''): 13 | content_type_id = ContentType.objects.get_for_model(obj).id 14 | e = self.model(None, None, user_id, content_type_id, 15 | force_str(obj.id), force_str(obj)[:200], 16 | action_flag, change_message) 17 | e.save() 18 | 19 | 20 | class LogEntry(models.Model): 21 | ADDITION = 1 22 | CHANGE = 2 23 | DELETION = 3 24 | 25 | action_time = models.DateTimeField(_('action time'), auto_now=True) 26 | user = models.ForeignKey(settings.AUTH_USER_MODEL, 27 | related_name='log_entries', 28 | on_delete=models.CASCADE) 29 | content_type = models.ForeignKey(ContentType, blank=True, null=True, 30 | related_name='log_entries', 31 | on_delete=models.CASCADE) 32 | object_id = models.TextField(_('object id'), blank=True, null=True) 33 | object_repr = models.CharField(_('object repr'), max_length=200) 34 | action_flag = models.PositiveSmallIntegerField(_('action flag')) 35 | change_message = models.TextField(_('change message'), blank=True) 36 | 37 | objects = LogEntryManager() 38 | 39 | class Meta: 40 | verbose_name = _('log entry') 41 | verbose_name_plural = _('log entries') 42 | ordering = ('-action_time',) 43 | 44 | def __repr__(self): 45 | return force_str(self.action_time) 46 | 47 | def __str__(self): 48 | if self.action_flag == self.ADDITION: 49 | return gettext('Added "%(object)s".') % { 50 | 'object': self.object_repr} 51 | elif self.action_flag == self.CHANGE: 52 | return gettext('Changed "%(object)s" - %(changes)s') % { 53 | 'object': self.object_repr, 54 | 'changes': self.change_message, 55 | } 56 | elif self.action_flag == self.DELETION: 57 | return gettext('Deleted "%(object)s."') % { 58 | 'object': self.object_repr} 59 | 60 | return gettext('LogEntry Object') 61 | 62 | def is_addition(self): 63 | return self.action_flag == self.ADDITION 64 | 65 | def is_change(self): 66 | return self.action_flag == self.CHANGE 67 | 68 | def is_deletion(self): 69 | return self.action_flag == self.DELETION 70 | 71 | @property 72 | def action_type(self): 73 | if self.is_addition(): 74 | return _('added') 75 | if self.is_change(): 76 | return _('changed') 77 | if self.is_deletion(): 78 | return _('deleted') 79 | return '' 80 | 81 | def get_edited_object(self): 82 | "Returns the edited object represented by this log entry" 83 | return self.content_type.get_object_for_this_type(pk=self.object_id) 84 | 85 | def get_admin_url(self): 86 | """ 87 | Returns the admin URL to edit the object represented by this log entry. 88 | This is relative to the Django admin index page. 89 | """ 90 | if self.content_type and self.object_id: 91 | return '{0.app_label}/{0.model}/{1}'.format( 92 | self.content_type, 93 | quote(self.object_id) 94 | ) 95 | return None 96 | -------------------------------------------------------------------------------- /example/blog/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | from django.db import migrations, models 2 | import django.db.models.deletion 3 | 4 | 5 | class Migration(migrations.Migration): 6 | 7 | dependencies = [ 8 | ] 9 | 10 | operations = [ 11 | migrations.CreateModel( 12 | name='Comment', 13 | fields=[ 14 | ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), 15 | ('body', models.TextField(verbose_name='body')), 16 | ], 17 | options={ 18 | 'verbose_name': 'comment', 19 | 'verbose_name_plural': 'comments', 20 | }, 21 | ), 22 | migrations.CreateModel( 23 | name='Count', 24 | fields=[ 25 | ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), 26 | ('num', models.PositiveSmallIntegerField()), 27 | ('parent', models.ForeignKey( 28 | to='blog.Count', null=True, on_delete=models.CASCADE)), 29 | ], 30 | ), 31 | migrations.CreateModel( 32 | name='Event', 33 | fields=[ 34 | ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), 35 | ('date', models.DateTimeField(auto_now_add=True)), 36 | ], 37 | ), 38 | migrations.CreateModel( 39 | name='EventGuide', 40 | fields=[ 41 | ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), 42 | ('event', models.ForeignKey(to='blog.Event', on_delete=django.db.models.deletion.DO_NOTHING)), 43 | ], 44 | ), 45 | migrations.CreateModel( 46 | name='Guest', 47 | fields=[ 48 | ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), 49 | ('name', models.CharField(max_length=255)), 50 | ('event', models.OneToOneField( 51 | to='blog.Event', 52 | on_delete=models.CASCADE)), 53 | ], 54 | options={ 55 | 'verbose_name': 'awesome guest', 56 | }, 57 | ), 58 | migrations.CreateModel( 59 | name='Location', 60 | fields=[ 61 | ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), 62 | ('event', models.OneToOneField( 63 | verbose_name='awesome event', 64 | to='blog.Event', 65 | on_delete=models.CASCADE)), 66 | ], 67 | ), 68 | migrations.CreateModel( 69 | name='Post', 70 | fields=[ 71 | ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), 72 | ('title', models.CharField(max_length=255, verbose_name='title')), 73 | ('body', models.TextField(verbose_name='body')), 74 | ('published', models.BooleanField(verbose_name='published', default=False)), 75 | ('published_date', models.DateField(blank=True, null=True)), 76 | ], 77 | options={ 78 | 'verbose_name': 'post', 79 | 'verbose_name_plural': 'posts', 80 | }, 81 | ), 82 | migrations.AddField( 83 | model_name='comment', 84 | name='post', 85 | field=models.ForeignKey( 86 | related_name='comments', 87 | verbose_name='post', 88 | to='blog.Post', 89 | on_delete=models.CASCADE), 90 | ), 91 | ] 92 | -------------------------------------------------------------------------------- /djadmin2/tests/test_renderers.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | from decimal import Decimal 3 | 4 | from django.test import TestCase 5 | from django.utils.translation import activate 6 | 7 | from .. import renderers 8 | from .models import RendererTestModel 9 | 10 | 11 | class BooleanRendererTest(TestCase): 12 | 13 | def setUp(self): 14 | self.renderer = renderers.boolean_renderer 15 | 16 | def test_boolean(self): 17 | out1 = self.renderer(True, None) 18 | self.assertIn('fa fa-check', out1) 19 | out2 = self.renderer(False, None) 20 | self.assertIn('fa fa-minus', out2) 21 | 22 | def test_string(self): 23 | out1 = self.renderer('yeah', None) 24 | self.assertIn('fa fa-check', out1) 25 | out2 = self.renderer('', None) 26 | self.assertIn('fa fa-minus', out2) 27 | 28 | 29 | class DatetimeRendererTest(TestCase): 30 | 31 | def setUp(self): 32 | self.renderer = renderers.datetime_renderer 33 | 34 | def tearDown(self): 35 | activate('en_US') 36 | 37 | def test_date_german(self): 38 | activate('de') 39 | out = self.renderer(dt.date(2013, 7, 6), None) 40 | self.assertEqual('6. Juli 2013', out) 41 | 42 | def test_date_spanish(self): 43 | activate('es') 44 | out = self.renderer(dt.date(2013, 7, 6), None) 45 | self.assertEqual('6 de Julio de 2013', out) 46 | 47 | def test_date_default(self): 48 | out = self.renderer(dt.date(2013, 7, 6), None) 49 | self.assertEqual('July 6, 2013', out) 50 | 51 | def test_time_german(self): 52 | activate('de') 53 | out = self.renderer(dt.time(13, 37, 1), None) 54 | self.assertEqual('13:37', out) 55 | 56 | def test_time_chinese(self): 57 | activate('zh') 58 | out = self.renderer(dt.time(13, 37, 1), None) 59 | self.assertEqual('1:37 p.m.', out) 60 | 61 | def test_datetime(self): 62 | out = self.renderer(dt.datetime(2013, 7, 6, 13, 37, 1), None) 63 | self.assertEqual('July 6, 2013, 1:37 p.m.', out) 64 | 65 | def test_date_as_string(self): 66 | out = self.renderer('13:37:01', None) 67 | self.assertEqual('13:37', out) 68 | 69 | # TODO test timezone localization 70 | 71 | 72 | class TitleRendererTest(TestCase): 73 | 74 | def setUp(self): 75 | self.renderer = renderers.title_renderer 76 | 77 | def testLowercase(self): 78 | out = self.renderer('oh hello there!', None) 79 | self.assertEqual('Oh Hello There!', out) 80 | 81 | def testTitlecase(self): 82 | out = self.renderer('Oh Hello There!', None) 83 | self.assertEqual('Oh Hello There!', out) 84 | 85 | def testUppercase(self): 86 | out = self.renderer('OH HELLO THERE!', None) 87 | self.assertEqual('Oh Hello There!', out) 88 | 89 | 90 | class NumberRendererTest(TestCase): 91 | 92 | def setUp(self): 93 | self.renderer = renderers.number_renderer 94 | 95 | def testInteger(self): 96 | out = self.renderer(42, None) 97 | self.assertEqual('42', out) 98 | 99 | def testFloat(self): 100 | out = self.renderer(42.5, None) 101 | self.assertEqual('42.5', out) 102 | 103 | def testEndlessFloat(self): 104 | out = self.renderer(1.0 / 3, None) 105 | self.assertEqual('0.3333333333333333', out) 106 | 107 | def testPlainDecimal(self): 108 | number = '0.123456789123456789123456789' 109 | out = self.renderer(Decimal(number), None) 110 | self.assertEqual(number, out) 111 | 112 | def testFieldDecimal(self): 113 | field = RendererTestModel._meta.get_field('decimal') 114 | out = self.renderer(Decimal('0.123456789'), field) 115 | self.assertEqual('0.12345', out) 116 | -------------------------------------------------------------------------------- /example/blog/locale/en/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the django-admin2 package. 2 | # 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: django-admin2\n" 6 | "Report-Msgid-Bugs-To: \n" 7 | "POT-Creation-Date: 2013-07-09 11:57+0200\n" 8 | "PO-Revision-Date: 2013-07-09 11:57+0200\n" 9 | "Last-Translator: FULL NAME \n" 10 | "Language-Team: LANGUAGE \n" 11 | "Language: \n" 12 | "MIME-Version: 1.0\n" 13 | "Content-Type: text/plain; charset=UTF-8\n" 14 | "Content-Transfer-Encoding: 8bit\n" 15 | 16 | #: actions.py:16 17 | msgid "Publish selected items" 18 | msgstr "" 19 | 20 | #: actions.py:18 21 | #, python-format 22 | msgctxt "singular form" 23 | msgid "Successfully published %(count)s %(items)s" 24 | msgstr "" 25 | 26 | #: actions.py:20 27 | #, python-format 28 | msgctxt "plural form" 29 | msgid "Successfully published %(count)s %(items)s" 30 | msgstr "" 31 | 32 | #: admin2.py:22 33 | msgid "Items unpublished" 34 | msgstr "" 35 | 36 | #. Translators : action description 37 | #: admin2.py:25 38 | msgid "Unpublish selected items" 39 | msgstr "" 40 | 41 | #: models.py:11 42 | msgid "title" 43 | msgstr "" 44 | 45 | #: models.py:12 models.py:25 46 | msgid "body" 47 | msgstr "" 48 | 49 | #: models.py:13 50 | msgid "published" 51 | msgstr "" 52 | 53 | #: models.py:19 models.py:24 54 | msgid "post" 55 | msgstr "" 56 | 57 | #: models.py:20 58 | msgid "posts" 59 | msgstr "" 60 | 61 | #: models.py:31 62 | msgid "comment" 63 | msgstr "" 64 | 65 | #: models.py:32 66 | msgid "comments" 67 | msgstr "" 68 | 69 | #: templates/blog/home.html:9 70 | msgid "Imagine that this is a real site" 71 | msgstr "" 72 | 73 | #: templates/blog/home.html:11 74 | msgid "Pretend that this is the homepage of a big Django site." 75 | msgstr "" 76 | 77 | #: templates/blog/home.html:13 78 | msgid "Imagine lots of things are here:" 79 | msgstr "" 80 | 81 | #: templates/blog/home.html:15 82 | msgid "A blog" 83 | msgstr "" 84 | 85 | #: templates/blog/home.html:16 86 | msgid "Comments" 87 | msgstr "" 88 | 89 | #: templates/blog/home.html:17 90 | msgid "And so on..." 91 | msgstr "" 92 | 93 | #: templates/blog/home.html:20 94 | msgid "" 95 | "In other words, these are items that we can introspect through the Django " 96 | "admin." 97 | msgstr "" 98 | 99 | #: templates/blog/home.html:22 100 | msgid "Under the hood" 101 | msgstr "" 102 | 103 | #: templates/blog/home.html:24 104 | msgid "" 105 | "Now, explore the Django admin for example.com. Click on either of the " 106 | "following:" 107 | msgstr "" 108 | 109 | #: templates/blog/home.html:30 110 | #, python-format 111 | msgid "The original Django Admin" 112 | msgstr "" 113 | 114 | #: templates/blog/home.html:36 115 | msgid "Powered by django.contrib.admin. This is just here for reference." 116 | msgstr "" 117 | 118 | #: templates/blog/home.html:39 119 | #, python-format 120 | msgid "The new Admin2" 121 | msgstr "" 122 | 123 | #: templates/blog/home.html:45 124 | msgid "Powered by django-admin2." 125 | msgstr "" 126 | 127 | #: templates/djadmin2/bootstrap/actions/publish_selected_items.html:4 128 | #: templates/djadmin2/bootstrap/actions/publish_selected_items.html:6 129 | msgid "Are you sure?" 130 | msgstr "" 131 | 132 | #: templates/djadmin2/bootstrap/actions/publish_selected_items.html:9 133 | msgid "Home" 134 | msgstr "" 135 | 136 | #: templates/djadmin2/bootstrap/actions/publish_selected_items.html:12 137 | #: templates/djadmin2/bootstrap/actions/publish_selected_items.html:37 138 | msgid "Publish" 139 | msgstr "" 140 | 141 | #: templates/djadmin2/bootstrap/actions/publish_selected_items.html:19 142 | #, python-format 143 | msgid "" 144 | "Are you sure you want to publish the selected %(objects_name)s?\n" 145 | " The following item will be published:\n" 146 | msgid_plural "" 147 | "Are you sure you want to publish the selected %(objects_name)s?\n" 148 | " The following items will be published:\n" 149 | msgstr[0] "" 150 | msgstr[1] "" 151 | -------------------------------------------------------------------------------- /docs/ref/actions.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Actions 3 | ======= 4 | 5 | .. index:: Actions 6 | 7 | Actions are defined to work on a single view type. Currently, actions are only implemented against the ``ModelListView``. This view contains the default ``DeleteSelectedAction`` method, which in end functionality mirrors ``django.contrib.admin.delete_selected``. 8 | 9 | However, under the hood, django-admin2's actions work very differently. Instead of functions with assigned attributes, they can either be functions or full fledged objects. Which means you can more easily extend them to suit your needs. 10 | 11 | The documentation works off a simple set of models, as listed below: 12 | 13 | .. code-block:: python 14 | 15 | # blog/models.py 16 | from django.db import models 17 | 18 | STATUS_CHOICES = ( 19 | ('d', 'Draft'), 20 | ('p', 'Published'), 21 | ('w', 'Withdrawn'), 22 | ) 23 | 24 | 25 | class Post(models.Model): 26 | title = models.CharField(max_length=255) 27 | body = models.TextField() 28 | status = models.CharField(max_length=1, choices=STATUS_CHOICES) 29 | 30 | def __unicode__(self): 31 | return self.title 32 | 33 | 34 | class Comment(models.Model): 35 | post = models.ForeignKey(Post) 36 | body = models.TextField() 37 | 38 | def __unicode__(self): 39 | return self.body 40 | 41 | Writing List Actions 42 | ----------------------- 43 | 44 | .. index:: 45 | single: Actions; Writing List Actions 46 | 47 | The basic workflow of Django’s admin is, in a nutshell, “select an object, then change it.” This works well for a majority of use cases. However, if you need to make the same change to many objects at once, this workflow can be quite tedious. 48 | 49 | In these cases, Django’s admin lets you write and register “actions” – simple functions that get called with a list of objects selected on the change list page. 50 | 51 | If you look at any change list in the admin, you’ll see this feature in action; Django ships with a “delete selected objects” action available to all models. Using our sample models, let's pretend we wrote a blog article about Django and our mother put in a whole bunch of embarressing comments. Rather than cherry-pick the comments, we want to delete the whole batch. 52 | 53 | In our blog/admin.py module we write: 54 | 55 | .. code-block:: python 56 | 57 | from djadmin2.actions import BaseListAction 58 | from djadmin2.site import djadmin2_site 59 | from djadmin2.types import ModelAdmin2 60 | 61 | from .models import Post, Comment 62 | 63 | class DeleteAllComments(BaseListAction): 64 | 65 | description = 'Delete selected items' 66 | default_template_name = 'actions/delete_all_comments_confirmation.html' 67 | success_message = 'Successfully deleted %d %s' # first argument - items count, second - verbose_name[_plural] 68 | 69 | def process_queryset(self): 70 | """Every action must provide this method""" 71 | self.get_queryset().delete() 72 | 73 | 74 | def custom_function_action(request, queryset): 75 | print(queryset.count()) 76 | 77 | custom_function_action.description = 'Do other action' 78 | 79 | class PostAdmin(ModelAdmin2): 80 | actions = [DeleteAllComments, custom_function_action] 81 | 82 | djadmin2_site.register(Post, PostAdmin) 83 | djadmin2_site.register(Comment) 84 | 85 | 86 | .. warning:: 87 | 88 | The “delete selected objects” action uses `QuerySet.delete()`_ for efficiency reasons, which has an important caveat: your model’s delete() method will not be called. 89 | 90 | If you wish to override this behavior, simply write a custom action which accomplishes deletion in your preferred manner – for example, by calling ``Model.delete()`` for each of the selected items. 91 | 92 | For more background on bulk deletion, see the documentation on `object deletion`_. 93 | 94 | .. _`QuerySet.delete()`: https://docs.djangoproject.com/en/dev/ref/models/querysets/#django.db.models.query.QuerySet.delete 95 | .. _`Object deletion`: https://docs.djangoproject.com/en/dev/topics/db/queries/#topics-db-queries-delete 96 | 97 | Read on to find out how to add your own actions to this list. 98 | --------------------------------------------------------------------------------