mirror of
https://github.com/jazzband/django-admin2.git
synced 2026-05-16 03:03:14 +00:00
Compare commits
105 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7770da8a49 | ||
|
|
bbfa3daa0c | ||
|
|
933489a491 | ||
|
|
2a5156005b | ||
|
|
ca28d88c88 | ||
|
|
9227439625 | ||
|
|
78112a85bd | ||
|
|
951c7e5bcf | ||
|
|
5608ae91ea | ||
|
|
8cee4de75d | ||
|
|
f5ee0d12c3 | ||
|
|
245b5911e6 | ||
|
|
39c6d80b7b | ||
|
|
2c6e5f004a | ||
|
|
f893867652 | ||
|
|
9025d59308 | ||
|
|
5fa2670643 | ||
|
|
acc36d9e3e | ||
|
|
d76590de4c | ||
|
|
eba98f9cae | ||
|
|
65c7f21730 | ||
|
|
c59a19f336 | ||
|
|
49f569cc6c | ||
|
|
08867d7e13 | ||
|
|
c724c332c6 | ||
|
|
fbd1ab5931 | ||
|
|
74788d96ff | ||
|
|
09ed44fe56 | ||
|
|
5425e37524 | ||
|
|
ccdbfdd013 | ||
|
|
93a7a213df | ||
|
|
673b0f8eb6 | ||
|
|
fbd3caf0e3 | ||
|
|
77ed1eb117 | ||
|
|
58f38b55cd | ||
|
|
bfe2a8b207 | ||
|
|
26727eb2a5 | ||
|
|
ddc1e39cea | ||
|
|
7ea3d7fc28 | ||
|
|
439f250423 | ||
|
|
69d0387b6d | ||
|
|
32f5c93c84 | ||
|
|
6473fbea4d | ||
|
|
c056ad816e | ||
|
|
769bd37a9a | ||
|
|
9d7286b5aa | ||
|
|
a7be23c4d9 | ||
|
|
3c20e75a0d | ||
|
|
bdfcc5a65b | ||
|
|
18c8c95443 | ||
|
|
563a330db1 | ||
|
|
78e18161dc | ||
|
|
85a5d3b291 | ||
|
|
e8bde774cf | ||
|
|
f60fb8530c | ||
|
|
18d0d18edd | ||
|
|
c4462f79d2 | ||
|
|
ffb7f2d595 | ||
|
|
09545a663e | ||
|
|
0cbaf7f089 | ||
|
|
b285164379 | ||
|
|
7a754a3469 | ||
|
|
2f1959b43e | ||
|
|
2473fa3069 | ||
|
|
f212393331 | ||
|
|
8636f6ce98 | ||
|
|
6ae2354d2a | ||
|
|
8169c11e31 | ||
|
|
2091f9649d | ||
|
|
6263f2a340 | ||
|
|
647260dfb7 | ||
|
|
5f0c3c323d | ||
|
|
75c7d4180a | ||
|
|
68eb9c7c59 | ||
|
|
394b04c2f9 | ||
|
|
5d4ba598d7 | ||
|
|
d44f64f3fd | ||
|
|
26fc9ff235 | ||
|
|
cfe9c1ffa2 | ||
|
|
606118cb04 | ||
|
|
27421081d4 | ||
|
|
90713245bc | ||
|
|
b01ff0b2f6 | ||
|
|
f86f5a1ee2 | ||
|
|
be3a67b9c5 | ||
|
|
4a715d95c0 | ||
|
|
33b41fa311 | ||
|
|
e3822dee39 | ||
|
|
e7200b7b65 | ||
|
|
cce430242c | ||
|
|
2c73396391 | ||
|
|
c8a4153a4d | ||
|
|
9db0b200ff | ||
|
|
aac3e1062b | ||
|
|
c34a0dab19 | ||
|
|
cf822c745b | ||
|
|
28702ef165 | ||
|
|
18fae8d5ed | ||
|
|
dcb43cdb02 | ||
|
|
4aa18f3a8d | ||
|
|
f8bd4be24c | ||
|
|
1054fb25fd | ||
|
|
2b5c180792 | ||
|
|
a95e211d65 | ||
|
|
b20ec951c3 |
82 changed files with 755 additions and 679 deletions
53
.github/workflows/release.yml
vendored
Normal file
53
.github/workflows/release.yml
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- '*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
if: github.repository == 'jazzband/django-admin2'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: 3.8
|
||||||
|
|
||||||
|
- name: Get pip cache dir
|
||||||
|
id: pip-cache
|
||||||
|
run: |
|
||||||
|
echo "::set-output name=dir::$(pip cache dir)"
|
||||||
|
|
||||||
|
- name: Cache
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ${{ steps.pip-cache.outputs.dir }}
|
||||||
|
key: release-${{ hashFiles('**/setup.py') }}
|
||||||
|
restore-keys: |
|
||||||
|
release-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install -U pip
|
||||||
|
python -m pip install -U setuptools twine wheel
|
||||||
|
|
||||||
|
- name: Build package
|
||||||
|
run: |
|
||||||
|
python setup.py --version
|
||||||
|
python setup.py sdist --format=gztar bdist_wheel
|
||||||
|
twine check dist/*
|
||||||
|
|
||||||
|
- name: Upload packages to Jazzband
|
||||||
|
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
|
||||||
|
uses: pypa/gh-action-pypi-publish@master
|
||||||
|
with:
|
||||||
|
user: jazzband
|
||||||
|
password: ${{ secrets.JAZZBAND_RELEASE_KEY }}
|
||||||
|
repository_url: https://jazzband.co/projects/django-admin2/upload
|
||||||
47
.github/workflows/test.yml
vendored
Normal file
47
.github/workflows/test.yml
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
name: Test
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
max-parallel: 5
|
||||||
|
matrix:
|
||||||
|
python-version: [3.5, 3.6, 3.7, 3.8]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
|
- name: Get pip cache dir
|
||||||
|
id: pip-cache
|
||||||
|
run: |
|
||||||
|
echo "::set-output name=dir::$(pip cache dir)"
|
||||||
|
|
||||||
|
- name: Cache
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ${{ steps.pip-cache.outputs.dir }}
|
||||||
|
key:
|
||||||
|
-${{ matrix.python-version }}-v1-${{ hashFiles('**/setup.py') }}
|
||||||
|
restore-keys: |
|
||||||
|
-${{ matrix.python-version }}-v1-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
python -m pip install --upgrade tox tox-gh-actions
|
||||||
|
|
||||||
|
- name: Tox tests
|
||||||
|
run: |
|
||||||
|
tox -v
|
||||||
|
|
||||||
|
- name: Upload coverage
|
||||||
|
uses: codecov/codecov-action@v1
|
||||||
|
with:
|
||||||
|
name: Python ${{ matrix.python-version }}
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -64,4 +64,5 @@ media
|
||||||
# PyCharm
|
# PyCharm
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
.cache
|
.cache
|
||||||
|
.pytest_cache
|
||||||
46
.travis.yml
46
.travis.yml
|
|
@ -1,46 +0,0 @@
|
||||||
sudo: false
|
|
||||||
language: python
|
|
||||||
cache:
|
|
||||||
directories:
|
|
||||||
- "~/.cache/pip"
|
|
||||||
python:
|
|
||||||
- '2.7'
|
|
||||||
- '3.3'
|
|
||||||
- '3.4'
|
|
||||||
- '3.5'
|
|
||||||
env:
|
|
||||||
- DJANGO=1.8
|
|
||||||
- DJANGO=1.9
|
|
||||||
- DJANGO=1.10
|
|
||||||
- DJANGO=master
|
|
||||||
matrix:
|
|
||||||
exclude:
|
|
||||||
- python: '3.3'
|
|
||||||
env: DJANGO=1.9
|
|
||||||
- python: '3.3'
|
|
||||||
env: DJANGO=1.10
|
|
||||||
- python: '3.3'
|
|
||||||
env: DJANGO=master
|
|
||||||
allow_failures:
|
|
||||||
- python: '2.7'
|
|
||||||
env: DJANGO=master
|
|
||||||
- python: '3.4'
|
|
||||||
env: DJANGO=master
|
|
||||||
- python: '3.5'
|
|
||||||
env: DJANGO=master
|
|
||||||
install:
|
|
||||||
- pip install tox
|
|
||||||
script:
|
|
||||||
- tox -e py${TRAVIS_PYTHON_VERSION//[.]/}-$DJANGO
|
|
||||||
deploy:
|
|
||||||
provider: pypi
|
|
||||||
user: jazzband
|
|
||||||
distributions: sdist bdist_wheel
|
|
||||||
password:
|
|
||||||
secure: UQh5R++1zOsJm31oIL0w4YsIyP1A83kv/EdpwhuXqs8HZB+Mpo5h2ylu/qVioTaiXvYEVfO1myY57du7YvC+u6oOxD8tyduBOyZMJXa15+/kdVbvjlRWJ/c8wGgmjlOAun209g9WoTAMVX8t1+thHEJqWxx07nlnPCAJSmdms+w=
|
|
||||||
on:
|
|
||||||
tags: true
|
|
||||||
repo: jazzband/django-admin2
|
|
||||||
# only do the PyPI release for exactly one scenario of the test matrix:
|
|
||||||
condition: "$DJANGO = 1.9"
|
|
||||||
python: 2.7
|
|
||||||
|
|
@ -4,6 +4,7 @@ CONTRIBUTORS
|
||||||
Project Lead
|
Project Lead
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
* Asif Saif Uddin (@auvipy)
|
||||||
* Daniel Greenfeld (@pydanny / <pydanny@gmail.com>)
|
* Daniel Greenfeld (@pydanny / <pydanny@gmail.com>)
|
||||||
|
|
||||||
Translation Managers
|
Translation Managers
|
||||||
|
|
@ -11,6 +12,7 @@ Translation Managers
|
||||||
|
|
||||||
* Henri Colas (@NotSqrt)
|
* Henri Colas (@NotSqrt)
|
||||||
* Danilo Bargen (@dbrgn)
|
* Danilo Bargen (@dbrgn)
|
||||||
|
* Asif Saif Uddin (@auvipy)
|
||||||
|
|
||||||
Developers
|
Developers
|
||||||
----------
|
----------
|
||||||
|
|
|
||||||
46
CODE_OF_CONDUCT.md
Normal file
46
CODE_OF_CONDUCT.md
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
# Code of Conduct
|
||||||
|
|
||||||
|
As contributors and maintainers of the Jazzband projects, and in the interest of
|
||||||
|
fostering an open and welcoming community, we pledge to respect all people who
|
||||||
|
contribute through reporting issues, posting feature requests, updating documentation,
|
||||||
|
submitting pull requests or patches, and other activities.
|
||||||
|
|
||||||
|
We are committed to making participation in the Jazzband a harassment-free experience
|
||||||
|
for everyone, regardless of the level of experience, gender, gender identity and
|
||||||
|
expression, sexual orientation, disability, personal appearance, body size, race,
|
||||||
|
ethnicity, age, religion, or nationality.
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
- The use of sexualized language or imagery
|
||||||
|
- Personal attacks
|
||||||
|
- Trolling or insulting/derogatory comments
|
||||||
|
- Public or private harassment
|
||||||
|
- Publishing other's private information, such as physical or electronic addresses,
|
||||||
|
without explicit permission
|
||||||
|
- Other unethical or unprofessional conduct
|
||||||
|
|
||||||
|
The Jazzband roadies have the right and responsibility to remove, edit, or reject
|
||||||
|
comments, commits, code, wiki edits, issues, and other contributions that are not
|
||||||
|
aligned to this Code of Conduct, or to ban temporarily or permanently any contributor
|
||||||
|
for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||||
|
|
||||||
|
By adopting this Code of Conduct, the roadies commit themselves to fairly and
|
||||||
|
consistently applying these principles to every aspect of managing the jazzband
|
||||||
|
projects. Roadies who do not follow or enforce the Code of Conduct may be permanently
|
||||||
|
removed from the Jazzband roadies.
|
||||||
|
|
||||||
|
This code of conduct applies both within project spaces and in public spaces when an
|
||||||
|
individual is representing the project or its community.
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by
|
||||||
|
contacting the roadies at `roadies@jazzband.co`. All complaints will be reviewed and
|
||||||
|
investigated and will result in a response that is deemed necessary and appropriate to
|
||||||
|
the circumstances. Roadies are obligated to maintain confidentiality with regard to the
|
||||||
|
reporter of an incident.
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version
|
||||||
|
1.3.0, available at [https://contributor-covenant.org/version/1/3/0/][version]
|
||||||
|
|
||||||
|
[homepage]: https://contributor-covenant.org
|
||||||
|
[version]: https://contributor-covenant.org/version/1/3/0/
|
||||||
26
README.rst
26
README.rst
|
|
@ -5,14 +5,12 @@ django-admin2
|
||||||
.. image:: https://jazzband.co/static/img/badge.svg
|
.. image:: https://jazzband.co/static/img/badge.svg
|
||||||
:target: https://jazzband.co/
|
:target: https://jazzband.co/
|
||||||
:alt: Jazzband
|
:alt: Jazzband
|
||||||
.. image:: https://travis-ci.org/jazzband/django-admin2.png
|
.. image:: https://github.com/jazzband/django-admin2/workflows/Test/badge.svg
|
||||||
:alt: Build Status
|
:target: https://github.com/jazzband/django-admin2/actions
|
||||||
:target: https://travis-ci.org/jazzband/django-admin2
|
:alt: GitHub Actions
|
||||||
.. image:: https://coveralls.io/repos/github/jazzband/django-admin2/badge.svg?branch=develop
|
.. image:: https://codecov.io/gh/jazzband/django-admin2/branch/main/graph/badge.svg?token=PcC594rhI4
|
||||||
:alt: Coverage Status
|
:target: https://codecov.io/gh/jazzband/django-admin2
|
||||||
:target: https://coveralls.io/github/jazzband/django-admin2?branch=develop
|
:alt: Code coverage
|
||||||
.. image:: https://badges.gitter.im/Join Chat.svg
|
|
||||||
:target: https://gitter.im/pydanny/django-admin2?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
|
|
||||||
|
|
||||||
One of the most useful parts of ``django.contrib.admin`` is the ability to
|
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
|
configure various views that touch and alter data. django-admin2 is a complete
|
||||||
|
|
@ -32,23 +30,23 @@ Features
|
||||||
Screenshots
|
Screenshots
|
||||||
===========
|
===========
|
||||||
|
|
||||||
.. image:: https://github.com/jazzband/django-admin2/raw/develop/screenshots/Site_administration.png
|
.. image:: https://github.com/jazzband/django-admin2/raw/main/screenshots/Site_administration.png
|
||||||
:width: 722px
|
:width: 722px
|
||||||
:alt: Site administration
|
:alt: Site administration
|
||||||
:align: center
|
:align: center
|
||||||
:target: https://github.com/jazzband/django-admin2/raw/develop/screenshots/Site_administration.png
|
:target: https://github.com/jazzband/django-admin2/raw/main/screenshots/Site_administration.png
|
||||||
|
|
||||||
.. image:: https://github.com/jazzband/django-admin2/raw/develop/screenshots/Select_user.png
|
.. image:: https://github.com/jazzband/django-admin2/raw/main/screenshots/Select_user.png
|
||||||
:width: 722px
|
:width: 722px
|
||||||
:alt: Select user
|
:alt: Select user
|
||||||
:align: center
|
:align: center
|
||||||
:target: https://github.com/jazzband/django-admin2/raw/develop/screenshots/Select_user.png
|
:target: https://github.com/jazzband/django-admin2/raw/main/screenshots/Select_user.png
|
||||||
|
|
||||||
Requirements
|
Requirements
|
||||||
============
|
============
|
||||||
|
|
||||||
* Django 1.7+
|
* Django 2.2+
|
||||||
* Python 2.7+ or Python 3.3+
|
* Python 3.5+
|
||||||
* django-braces_
|
* django-braces_
|
||||||
* django-extra-views_
|
* django-extra-views_
|
||||||
* django-rest-framework_
|
* django-rest-framework_
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,4 @@
|
||||||
# -*- coding: utf-8 -*-
|
__version__ = '0.7.1'
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
|
||||||
|
|
||||||
|
|
||||||
__version__ = '0.7.0'
|
|
||||||
|
|
||||||
__author__ = 'Daniel Greenfeld & Contributors'
|
__author__ = 'Daniel Greenfeld & Contributors'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.db import router
|
from django.db import router
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_str
|
||||||
from django.utils.text import capfirst
|
from django.utils.text import capfirst
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import gettext as _
|
||||||
from django.utils.translation import ugettext_lazy, ungettext, pgettext_lazy
|
from django.utils.translation import gettext_lazy, ngettext, pgettext_lazy
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
from . import permissions, utils
|
from . import permissions, utils
|
||||||
|
|
@ -26,7 +23,7 @@ class BaseListAction(Admin2ModelMixin, TemplateView):
|
||||||
|
|
||||||
permission_classes = (permissions.IsStaffPermission,)
|
permission_classes = (permissions.IsStaffPermission,)
|
||||||
|
|
||||||
empty_message = ugettext_lazy(
|
empty_message = gettext_lazy(
|
||||||
'Items must be selected in order to perform actions '
|
'Items must be selected in order to perform actions '
|
||||||
'on them. No items have been changed.'
|
'on them. No items have been changed.'
|
||||||
)
|
)
|
||||||
|
|
@ -50,9 +47,9 @@ class BaseListAction(Admin2ModelMixin, TemplateView):
|
||||||
objects_name = options.verbose_name
|
objects_name = options.verbose_name
|
||||||
else:
|
else:
|
||||||
objects_name = options.verbose_name_plural
|
objects_name = options.verbose_name_plural
|
||||||
self.objects_name = force_text(objects_name)
|
self.objects_name = force_str(objects_name)
|
||||||
|
|
||||||
super(BaseListAction, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
""" Replaced `get_queryset` from `Admin2ModelMixin`"""
|
""" Replaced `get_queryset` from `Admin2ModelMixin`"""
|
||||||
|
|
@ -88,12 +85,12 @@ class BaseListAction(Admin2ModelMixin, TemplateView):
|
||||||
""" Utility method when you want to display nested objects
|
""" Utility method when you want to display nested objects
|
||||||
(such as during a bulk update/delete)
|
(such as during a bulk update/delete)
|
||||||
"""
|
"""
|
||||||
context = super(BaseListAction, self).get_context_data()
|
context = super().get_context_data()
|
||||||
|
|
||||||
def _format_callback(obj):
|
def _format_callback(obj):
|
||||||
opts = utils.model_options(obj)
|
opts = utils.model_options(obj)
|
||||||
return '%s: %s' % (force_text(capfirst(opts.verbose_name)),
|
return '%s: %s' % (force_str(capfirst(opts.verbose_name)),
|
||||||
force_text(obj))
|
force_str(obj))
|
||||||
|
|
||||||
using = router.db_for_write(self.model)
|
using = router.db_for_write(self.model)
|
||||||
|
|
||||||
|
|
@ -111,7 +108,7 @@ class BaseListAction(Admin2ModelMixin, TemplateView):
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
if self.item_count > 0:
|
if self.item_count > 0:
|
||||||
return super(BaseListAction, self).get(request)
|
return super().get(request)
|
||||||
|
|
||||||
message = _(self.empty_message)
|
message = _(self.empty_message)
|
||||||
messages.add_message(request, messages.INFO, message)
|
messages.add_message(request, messages.INFO, message)
|
||||||
|
|
@ -122,7 +119,7 @@ class BaseListAction(Admin2ModelMixin, TemplateView):
|
||||||
if self.process_queryset() is None:
|
if self.process_queryset() is None:
|
||||||
|
|
||||||
# objects_name should already be pluralized, see __init__
|
# objects_name should already be pluralized, see __init__
|
||||||
message = ungettext(
|
message = ngettext(
|
||||||
self.success_message,
|
self.success_message,
|
||||||
self.success_message_plural,
|
self.success_message_plural,
|
||||||
self.item_count
|
self.item_count
|
||||||
|
|
@ -146,7 +143,7 @@ class DeleteSelectedAction(BaseListAction):
|
||||||
|
|
||||||
default_template_name = "actions/delete_selected_confirmation.html"
|
default_template_name = "actions/delete_selected_confirmation.html"
|
||||||
|
|
||||||
description = ugettext_lazy("Delete selected items")
|
description = gettext_lazy("Delete selected items")
|
||||||
|
|
||||||
success_message = pgettext_lazy(
|
success_message = pgettext_lazy(
|
||||||
'singular form',
|
'singular form',
|
||||||
|
|
@ -163,7 +160,7 @@ class DeleteSelectedAction(BaseListAction):
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
if request.POST.get('confirmed'):
|
if request.POST.get('confirmed'):
|
||||||
super(DeleteSelectedAction, self).post(request)
|
super().post(request)
|
||||||
else:
|
else:
|
||||||
# The user has not confirmed that they want to delete the
|
# The user has not confirmed that they want to delete the
|
||||||
# objects, so render a template asking for their confirmation.
|
# objects, so render a template asking for their confirmation.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import Group, User
|
from django.contrib.auth.models import Group, User
|
||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
|
|
@ -17,6 +14,7 @@ class GroupSerializer(Admin2APISerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Group
|
model = Group
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
class GroupAdmin2(ModelAdmin2):
|
class GroupAdmin2(ModelAdmin2):
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
|
||||||
|
|
||||||
from django.utils.encoding import force_str
|
from django.utils.encoding import force_str
|
||||||
from rest_framework import fields, generics, serializers
|
from rest_framework import fields, generics, serializers
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.reverse import reverse
|
from rest_framework.reverse import reverse as drf_reverse
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
from . import utils
|
from . import utils
|
||||||
|
|
@ -20,7 +17,7 @@ class Admin2APISerializer(serializers.HyperlinkedModelSerializer):
|
||||||
__unicode__ = fields.ReadOnlyField(source='__str__')
|
__unicode__ = fields.ReadOnlyField(source='__str__')
|
||||||
|
|
||||||
def get_extra_kwargs(self):
|
def get_extra_kwargs(self):
|
||||||
extra_kwargs = super(Admin2APISerializer, self).get_extra_kwargs()
|
extra_kwargs = super().get_extra_kwargs()
|
||||||
extra_kwargs.update({
|
extra_kwargs.update({
|
||||||
'url': {'view_name': self._get_default_view_name(self.Meta.model)}
|
'url': {'view_name': self._get_default_view_name(self.Meta.model)}
|
||||||
})
|
})
|
||||||
|
|
@ -56,9 +53,10 @@ class Admin2APIMixin(Admin2Mixin):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = model_class
|
model = model_class
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
return ModelAPISerilizer
|
return ModelAPISerilizer
|
||||||
return super(Admin2APIMixin, self).get_serializer_class()
|
return super().get_serializer_class()
|
||||||
|
|
||||||
|
|
||||||
class IndexAPIView(Admin2APIMixin, APIView):
|
class IndexAPIView(Admin2APIMixin, APIView):
|
||||||
|
|
@ -75,7 +73,7 @@ class IndexAPIView(Admin2APIMixin, APIView):
|
||||||
'app_label': model_options.app_label,
|
'app_label': model_options.app_label,
|
||||||
'model_name': model_options.object_name.lower(),
|
'model_name': model_options.object_name.lower(),
|
||||||
}
|
}
|
||||||
model_url = reverse(
|
model_url = drf_reverse(
|
||||||
'%(current_app)s:%(app_label)s_%(model_name)s_api_list' % opts,
|
'%(current_app)s:%(app_label)s_%(model_name)s_api_list' % opts,
|
||||||
request=self.request,
|
request=self.request,
|
||||||
format=self.kwargs.get('format'))
|
format=self.kwargs.get('format'))
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
from django.db.models.signals import post_migrate
|
from django.db.models.signals import post_migrate
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from djadmin2.permissions import create_view_permissions
|
from djadmin2.permissions import create_view_permissions
|
||||||
|
|
||||||
|
|
@ -10,5 +10,7 @@ class Djadmin2Config(AppConfig):
|
||||||
verbose_name = _("Django Admin2")
|
verbose_name = _("Django Admin2")
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
post_migrate.connect(create_view_permissions,
|
post_migrate.connect(
|
||||||
dispatch_uid="django-admin2.djadmin2.permissions.create_view_permissions")
|
create_view_permissions,
|
||||||
|
dispatch_uid="django-admin2.djadmin2.permissions.create_view_permissions"
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,7 @@ def _create_multiwidget(widget_class, copy_attributes=(), init_arguments=()):
|
||||||
return multiwidget
|
return multiwidget
|
||||||
return create_new_multiwidget
|
return create_new_multiwidget
|
||||||
|
|
||||||
|
|
||||||
# this dictionary keeps a mapping from django's widget classes to a callable
|
# this dictionary keeps a mapping from django's widget classes to a callable
|
||||||
# that will accept an instance of this class. It will return a new instance of
|
# that will accept an instance of this class. It will return a new instance of
|
||||||
# a corresponding floppyforms widget, with the same semantics -- all relevant
|
# a corresponding floppyforms widget, with the same semantics -- all relevant
|
||||||
|
|
|
||||||
113
djadmin2/core.py
113
djadmin2/core.py
|
|
@ -1,14 +1,11 @@
|
||||||
# -*- coding: utf-8 -*-:
|
|
||||||
"""
|
"""
|
||||||
WARNING: This file about to undergo major refactoring by @pydanny per
|
WARNING: This file about to undergo major refactoring by @pydanny per
|
||||||
Issue #99.
|
Issue #99.
|
||||||
"""
|
"""
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
|
||||||
|
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls import include, url
|
from django.urls import re_path
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
|
||||||
from . import apiviews
|
from . import apiviews
|
||||||
|
|
@ -17,7 +14,7 @@ from . import utils
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
|
|
||||||
class Admin2(object):
|
class Admin2:
|
||||||
"""
|
"""
|
||||||
The base Admin2 object.
|
The base Admin2 object.
|
||||||
It keeps a registry of all registered Models and collects the urls of their
|
It keeps a registry of all registered Models and collects the urls of their
|
||||||
|
|
@ -26,12 +23,13 @@ class Admin2(object):
|
||||||
It also provides an index view that serves as an entry point to the
|
It also provides an index view that serves as an entry point to the
|
||||||
admin site.
|
admin site.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
index_view = views.IndexView
|
index_view = views.IndexView
|
||||||
login_view = views.LoginView
|
login_view = views.LoginView
|
||||||
app_index_view = views.AppIndexView
|
app_index_view = views.AppIndexView
|
||||||
api_index_view = apiviews.IndexAPIView
|
api_index_view = apiviews.IndexAPIView
|
||||||
|
|
||||||
def __init__(self, name='admin2'):
|
def __init__(self, name="admin2"):
|
||||||
self.registry = {}
|
self.registry = {}
|
||||||
self.apps = {}
|
self.apps = {}
|
||||||
self.app_verbose_names = {}
|
self.app_verbose_names = {}
|
||||||
|
|
@ -51,7 +49,8 @@ class Admin2(object):
|
||||||
"""
|
"""
|
||||||
if model in self.registry:
|
if model in self.registry:
|
||||||
raise ImproperlyConfigured(
|
raise ImproperlyConfigured(
|
||||||
'%s is already registered in django-admin2' % model)
|
"%s is already registered in django-admin2" % model
|
||||||
|
)
|
||||||
if not model_admin:
|
if not model_admin:
|
||||||
model_admin = types.ModelAdmin2
|
model_admin = types.ModelAdmin2
|
||||||
self.registry[model] = model_admin(model, admin=self, **kwargs)
|
self.registry[model] = model_admin(model, admin=self, **kwargs)
|
||||||
|
|
@ -74,7 +73,8 @@ class Admin2(object):
|
||||||
del self.registry[model]
|
del self.registry[model]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise ImproperlyConfigured(
|
raise ImproperlyConfigured(
|
||||||
'%s was never registered in django-admin2' % model)
|
"%s was never registered in django-admin2" % model
|
||||||
|
)
|
||||||
|
|
||||||
# Remove the model from the apps registry
|
# Remove the model from the apps registry
|
||||||
# Get the app label
|
# Get the app label
|
||||||
|
|
@ -96,7 +96,8 @@ class Admin2(object):
|
||||||
"""
|
"""
|
||||||
if app_label in self.app_verbose_names:
|
if app_label in self.app_verbose_names:
|
||||||
raise ImproperlyConfigured(
|
raise ImproperlyConfigured(
|
||||||
'%s is already registered in django-admin2' % app_label)
|
"%s is already registered in django-admin2" % app_label
|
||||||
|
)
|
||||||
|
|
||||||
self.app_verbose_names[app_label] = app_verbose_name
|
self.app_verbose_names[app_label] = app_verbose_name
|
||||||
|
|
||||||
|
|
@ -112,7 +113,8 @@ class Admin2(object):
|
||||||
del self.app_verbose_names[app_label]
|
del self.app_verbose_names[app_label]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise ImproperlyConfigured(
|
raise ImproperlyConfigured(
|
||||||
'%s app label was never registered in django-admin2' % app_label)
|
"%s app label was never registered in django-admin2" % app_label
|
||||||
|
)
|
||||||
|
|
||||||
def autodiscover(self):
|
def autodiscover(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -123,7 +125,7 @@ class Admin2(object):
|
||||||
try:
|
try:
|
||||||
import_module("%s.admin2" % app_name)
|
import_module("%s.admin2" % app_name)
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
if str(e).startswith("No module named") and 'admin2' in str(e):
|
if str(e).startswith("No module named") and "admin2" in str(e):
|
||||||
continue
|
continue
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
|
@ -135,71 +137,74 @@ class Admin2(object):
|
||||||
for object_admin in self.registry.values():
|
for object_admin in self.registry.values():
|
||||||
if object_admin.name == name:
|
if object_admin.name == name:
|
||||||
return object_admin
|
return object_admin
|
||||||
raise ValueError(
|
raise ValueError("No object admin found with name {}".format(repr(name)))
|
||||||
u'No object admin found with name {}'.format(repr(name)))
|
|
||||||
|
|
||||||
def get_index_kwargs(self):
|
def get_index_kwargs(self):
|
||||||
return {
|
return {
|
||||||
'registry': self.registry,
|
"registry": self.registry,
|
||||||
'app_verbose_names': self.app_verbose_names,
|
"app_verbose_names": self.app_verbose_names,
|
||||||
'apps': self.apps,
|
"apps": self.apps,
|
||||||
'login_view': self.login_view,
|
"login_view": self.login_view,
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_app_index_kwargs(self):
|
def get_app_index_kwargs(self):
|
||||||
return {
|
return {
|
||||||
'registry': self.registry,
|
"registry": self.registry,
|
||||||
'app_verbose_names': self.app_verbose_names,
|
"app_verbose_names": self.app_verbose_names,
|
||||||
'apps': self.apps,
|
"apps": self.apps,
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_api_index_kwargs(self):
|
def get_api_index_kwargs(self):
|
||||||
return {
|
return {
|
||||||
'registry': self.registry,
|
"registry": self.registry,
|
||||||
'app_verbose_names': self.app_verbose_names,
|
"app_verbose_names": self.app_verbose_names,
|
||||||
'apps': self.apps,
|
"apps": self.apps,
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_urls(self):
|
def get_urls(self):
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(regex=r'^$',
|
re_path(
|
||||||
|
r"^$",
|
||||||
view=self.index_view.as_view(**self.get_index_kwargs()),
|
view=self.index_view.as_view(**self.get_index_kwargs()),
|
||||||
name='dashboard'
|
name="dashboard",
|
||||||
),
|
),
|
||||||
url(regex='^auth/user/(?P<pk>\d+)/update/password/$',
|
re_path(
|
||||||
|
r"^auth/user/(?P<pk>\d+)/update/password/$",
|
||||||
view=views.PasswordChangeView.as_view(),
|
view=views.PasswordChangeView.as_view(),
|
||||||
name='password_change'
|
name="password_change",
|
||||||
),
|
),
|
||||||
url(regex='^password_change_done/$',
|
re_path(
|
||||||
|
"^password_change_done/$",
|
||||||
view=views.PasswordChangeDoneView.as_view(),
|
view=views.PasswordChangeDoneView.as_view(),
|
||||||
name='password_change_done'
|
name="password_change_done",
|
||||||
),
|
),
|
||||||
url(regex='^logout/$',
|
re_path("^logout/$", view=views.LogoutView.as_view(), name="logout"),
|
||||||
view=views.LogoutView.as_view(),
|
re_path(
|
||||||
name='logout'
|
r"^(?P<app_label>\w+)/$",
|
||||||
),
|
view=self.app_index_view.as_view(**self.get_app_index_kwargs()),
|
||||||
url(regex=r'^(?P<app_label>\w+)/$',
|
name="app_index",
|
||||||
view=self.app_index_view.as_view(
|
),
|
||||||
**self.get_app_index_kwargs()),
|
re_path(
|
||||||
name='app_index'
|
r"^api/v0/$",
|
||||||
),
|
view=self.api_index_view.as_view(**self.get_api_index_kwargs()),
|
||||||
url(regex=r'^api/v0/$',
|
name="api_index",
|
||||||
view=self.api_index_view.as_view(
|
),
|
||||||
**self.get_api_index_kwargs()),
|
|
||||||
name='api_index'
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
for model, model_admin in self.registry.items():
|
for model, model_admin in self.registry.items():
|
||||||
model_options = utils.model_options(model)
|
model_options = utils.model_options(model)
|
||||||
urlpatterns += [
|
urlpatterns += [
|
||||||
url('^{}/{}/'.format(
|
re_path(
|
||||||
model_options.app_label,
|
"^{}/{}/".format(
|
||||||
model_options.object_name.lower()),
|
model_options.app_label, model_options.object_name.lower()
|
||||||
include(model_admin.urls)),
|
),
|
||||||
url('^api/v0/{}/{}/'.format(
|
model_admin.urls,
|
||||||
model_options.app_label,
|
),
|
||||||
model_options.object_name.lower()),
|
re_path(
|
||||||
include(model_admin.api_urls)),
|
"^api/v0/{}/{}/".format(
|
||||||
|
model_options.app_label, model_options.object_name.lower()
|
||||||
|
),
|
||||||
|
model_admin.api_urls,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
return urlpatterns
|
return urlpatterns
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,14 @@
|
||||||
# -*- coding: utf-8 -*-
|
import collections.abc
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
|
||||||
|
|
||||||
import collections
|
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
|
||||||
import django_filters
|
import django_filters
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.forms import widgets as django_widgets
|
from django.forms import widgets as django_widgets
|
||||||
from django.forms.utils import flatatt
|
from django.forms.utils import flatatt
|
||||||
from django.utils import six
|
from django.utils.encoding import force_str
|
||||||
from django.utils.encoding import force_text
|
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import ugettext_lazy
|
from django.utils.translation import gettext_lazy
|
||||||
|
|
||||||
from .utils import type_str
|
from .utils import type_str
|
||||||
|
|
||||||
|
|
@ -24,15 +20,16 @@ class NumericDateFilter(django_filters.DateFilter):
|
||||||
|
|
||||||
|
|
||||||
class ChoicesAsLinksWidget(django_widgets.Select):
|
class ChoicesAsLinksWidget(django_widgets.Select):
|
||||||
"""Select form widget taht renders links for choices
|
"""Select form widget that renders links for choices
|
||||||
instead of select element with options.
|
instead of select element with options.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def render(self, name, value, attrs=None, choices=()):
|
def render(self, name, value, attrs=None, choices=()):
|
||||||
links = []
|
links = []
|
||||||
for choice_value, choice_label in chain(self.choices, choices):
|
for choice_value, choice_label in chain(self.choices, choices):
|
||||||
links.append(format_html(
|
links.append(format_html(
|
||||||
LINK_TEMPLATE,
|
LINK_TEMPLATE,
|
||||||
name, choice_value, flatatt(attrs), force_text(choice_label),
|
name, choice_value, flatatt(attrs), force_str(choice_label),
|
||||||
))
|
))
|
||||||
return mark_safe(u"<br />".join(links))
|
return mark_safe(u"<br />".join(links))
|
||||||
|
|
||||||
|
|
@ -44,11 +41,12 @@ class NullBooleanLinksWidget(
|
||||||
def __init__(self, attrs=None, choices=()):
|
def __init__(self, attrs=None, choices=()):
|
||||||
super(ChoicesAsLinksWidget, self).__init__(attrs)
|
super(ChoicesAsLinksWidget, self).__init__(attrs)
|
||||||
self.choices = [
|
self.choices = [
|
||||||
('1', ugettext_lazy('Unknown')),
|
('1', gettext_lazy('Unknown')),
|
||||||
('2', ugettext_lazy('Yes')),
|
('2', gettext_lazy('Yes')),
|
||||||
('3', ugettext_lazy('No')),
|
('3', gettext_lazy('No')),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
#: Maps `django_filter`'s field filters types to our
|
#: Maps `django_filter`'s field filters types to our
|
||||||
#: custom form widget.
|
#: custom form widget.
|
||||||
FILTER_TYPE_TO_WIDGET = {
|
FILTER_TYPE_TO_WIDGET = {
|
||||||
|
|
@ -68,7 +66,7 @@ def build_list_filter(request, model_admin, queryset):
|
||||||
`request.GET` and `queryset`.
|
`request.GET` and `queryset`.
|
||||||
"""
|
"""
|
||||||
# if ``list_filter`` is not iterable return it right away
|
# if ``list_filter`` is not iterable return it right away
|
||||||
if not isinstance(model_admin.list_filter, collections.Iterable):
|
if not isinstance(model_admin.list_filter, collections.abc.Iterable):
|
||||||
return model_admin.list_filter(
|
return model_admin.list_filter(
|
||||||
request.GET,
|
request.GET,
|
||||||
queryset=queryset,
|
queryset=queryset,
|
||||||
|
|
@ -76,7 +74,7 @@ def build_list_filter(request, model_admin, queryset):
|
||||||
# otherwise build :mod:`django_filters.FilterSet`
|
# otherwise build :mod:`django_filters.FilterSet`
|
||||||
filters = []
|
filters = []
|
||||||
for field_filter in model_admin.list_filter:
|
for field_filter in model_admin.list_filter:
|
||||||
if isinstance(field_filter, six.string_types):
|
if isinstance(field_filter, str):
|
||||||
filters.append(get_filter_for_field_name(
|
filters.append(get_filter_for_field_name(
|
||||||
queryset.model,
|
queryset.model,
|
||||||
field_filter,
|
field_filter,
|
||||||
|
|
@ -85,7 +83,7 @@ def build_list_filter(request, model_admin, queryset):
|
||||||
filters.append(field_filter)
|
filters.append(field_filter)
|
||||||
filterset_dict = {}
|
filterset_dict = {}
|
||||||
for field_filter in filters:
|
for field_filter in filters:
|
||||||
filterset_dict[field_filter.name] = field_filter
|
filterset_dict[field_filter.field_name] = field_filter
|
||||||
fields = list(filterset_dict.keys())
|
fields = list(filterset_dict.keys())
|
||||||
filterset_dict['Meta'] = type(
|
filterset_dict['Meta'] = type(
|
||||||
type_str('Meta'),
|
type_str('Meta'),
|
||||||
|
|
@ -101,18 +99,23 @@ def build_list_filter(request, model_admin, queryset):
|
||||||
def build_date_filter(request, model_admin, queryset, field_name="published_date"):
|
def build_date_filter(request, model_admin, queryset, field_name="published_date"):
|
||||||
filterset_dict = {
|
filterset_dict = {
|
||||||
"year": NumericDateFilter(
|
"year": NumericDateFilter(
|
||||||
name=field_name,
|
field_name=field_name,
|
||||||
lookup_type="year",
|
lookup_expr="year",
|
||||||
),
|
),
|
||||||
"month": NumericDateFilter(
|
"month": NumericDateFilter(
|
||||||
name=field_name,
|
field_name=field_name,
|
||||||
lookup_type="month",
|
lookup_expr="month",
|
||||||
),
|
),
|
||||||
"day": NumericDateFilter(
|
"day": NumericDateFilter(
|
||||||
name=field_name,
|
field_name=field_name,
|
||||||
lookup_type="day",
|
lookup_expr="day",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
filterset_dict["Meta"] = type(
|
||||||
|
type_str('Meta'),
|
||||||
|
(object, ),
|
||||||
|
{"model": queryset.model, "fields": [field_name]},
|
||||||
|
)
|
||||||
|
|
||||||
return type(
|
return type(
|
||||||
type_str('%sDateFilterSet' % queryset.model.__name__),
|
type_str('%sDateFilterSet' % queryset.model.__name__),
|
||||||
|
|
@ -128,8 +131,9 @@ def get_filter_for_field_name(model, field_name):
|
||||||
django_filters.filterset.get_model_field(model, field_name,),
|
django_filters.filterset.get_model_field(model, field_name,),
|
||||||
field_name,
|
field_name,
|
||||||
)
|
)
|
||||||
|
print("EXTRA!!!!")
|
||||||
|
print(filter_.extra)
|
||||||
filter_.widget = FILTER_TYPE_TO_WIDGET.get(
|
filter_.widget = FILTER_TYPE_TO_WIDGET.get(
|
||||||
filter_.__class__,
|
filter_.__class__
|
||||||
filter_.widget,
|
|
||||||
)
|
)
|
||||||
return filter_
|
return filter_
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,11 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.auth import authenticate
|
from django.contrib.auth import authenticate
|
||||||
from django.contrib.auth.forms import AuthenticationForm
|
from django.contrib.auth.forms import AuthenticationForm
|
||||||
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
|
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.urlresolvers import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
# Translators : %(username)s will be replaced by the username_field name
|
# Translators : %(username)s will be replaced by the username_field name
|
||||||
|
|
@ -59,7 +56,7 @@ class Admin2UserCreationForm(UserCreationForm):
|
||||||
class Admin2UserChangeForm(UserChangeForm):
|
class Admin2UserChangeForm(UserChangeForm):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(Admin2UserChangeForm, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
print(self.fields['password'].help_text)
|
print(self.fields['password'].help_text)
|
||||||
self.fields['password'].help_text = _("Raw passwords are not stored, so there is no way to see this user's password, but you can change the password using <a href=\"%s\">this form</a>." % self.get_update_password_url())
|
self.fields['password'].help_text = _("Raw passwords are not stored, so there is no way to see this user's password, but you can change the password using <a href=\"%s\">this form</a>." % self.get_update_password_url())
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
@ -22,8 +19,16 @@ class Migration(migrations.Migration):
|
||||||
('object_repr', models.CharField(max_length=200, verbose_name='object repr')),
|
('object_repr', models.CharField(max_length=200, verbose_name='object repr')),
|
||||||
('action_flag', models.PositiveSmallIntegerField(verbose_name='action flag')),
|
('action_flag', models.PositiveSmallIntegerField(verbose_name='action flag')),
|
||||||
('change_message', models.TextField(verbose_name='change message', blank=True)),
|
('change_message', models.TextField(verbose_name='change message', blank=True)),
|
||||||
('content_type', models.ForeignKey(related_name='log_entries', null=True, blank=True, to='contenttypes.ContentType')),
|
('content_type', models.ForeignKey(
|
||||||
('user', models.ForeignKey(related_name='log_entries', to=settings.AUTH_USER_MODEL)),
|
related_name='log_entries',
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
to='contenttypes.ContentType',
|
||||||
|
on_delete=models.CASCADE)),
|
||||||
|
('user', models.ForeignKey(
|
||||||
|
related_name='log_entries',
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
on_delete=models.CASCADE)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'log entry',
|
'verbose_name': 'log entry',
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,9 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
""" Boilerplate for now, will serve a purpose soon! """
|
""" Boilerplate for now, will serve a purpose soon! """
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_str
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
from django.utils.translation import gettext, gettext_lazy as _
|
||||||
from django.utils.encoding import smart_text
|
|
||||||
from django.utils.translation import ugettext, ugettext_lazy as _
|
|
||||||
|
|
||||||
from .utils import quote
|
from .utils import quote
|
||||||
|
|
||||||
|
|
@ -17,12 +12,11 @@ class LogEntryManager(models.Manager):
|
||||||
def log_action(self, user_id, obj, action_flag, change_message=''):
|
def log_action(self, user_id, obj, action_flag, change_message=''):
|
||||||
content_type_id = ContentType.objects.get_for_model(obj).id
|
content_type_id = ContentType.objects.get_for_model(obj).id
|
||||||
e = self.model(None, None, user_id, content_type_id,
|
e = self.model(None, None, user_id, content_type_id,
|
||||||
smart_text(obj.id), force_text(obj)[:200],
|
force_str(obj.id), force_str(obj)[:200],
|
||||||
action_flag, change_message)
|
action_flag, change_message)
|
||||||
e.save()
|
e.save()
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class LogEntry(models.Model):
|
class LogEntry(models.Model):
|
||||||
ADDITION = 1
|
ADDITION = 1
|
||||||
CHANGE = 2
|
CHANGE = 2
|
||||||
|
|
@ -30,9 +24,11 @@ class LogEntry(models.Model):
|
||||||
|
|
||||||
action_time = models.DateTimeField(_('action time'), auto_now=True)
|
action_time = models.DateTimeField(_('action time'), auto_now=True)
|
||||||
user = models.ForeignKey(settings.AUTH_USER_MODEL,
|
user = models.ForeignKey(settings.AUTH_USER_MODEL,
|
||||||
related_name='log_entries')
|
related_name='log_entries',
|
||||||
|
on_delete=models.CASCADE)
|
||||||
content_type = models.ForeignKey(ContentType, blank=True, null=True,
|
content_type = models.ForeignKey(ContentType, blank=True, null=True,
|
||||||
related_name='log_entries')
|
related_name='log_entries',
|
||||||
|
on_delete=models.CASCADE)
|
||||||
object_id = models.TextField(_('object id'), blank=True, null=True)
|
object_id = models.TextField(_('object id'), blank=True, null=True)
|
||||||
object_repr = models.CharField(_('object repr'), max_length=200)
|
object_repr = models.CharField(_('object repr'), max_length=200)
|
||||||
action_flag = models.PositiveSmallIntegerField(_('action flag'))
|
action_flag = models.PositiveSmallIntegerField(_('action flag'))
|
||||||
|
|
@ -46,22 +42,22 @@ class LogEntry(models.Model):
|
||||||
ordering = ('-action_time',)
|
ordering = ('-action_time',)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return smart_text(self.action_time)
|
return force_str(self.action_time)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self.action_flag == self.ADDITION:
|
if self.action_flag == self.ADDITION:
|
||||||
return ugettext('Added "%(object)s".') % {
|
return gettext('Added "%(object)s".') % {
|
||||||
'object': self.object_repr}
|
'object': self.object_repr}
|
||||||
elif self.action_flag == self.CHANGE:
|
elif self.action_flag == self.CHANGE:
|
||||||
return ugettext('Changed "%(object)s" - %(changes)s') % {
|
return gettext('Changed "%(object)s" - %(changes)s') % {
|
||||||
'object': self.object_repr,
|
'object': self.object_repr,
|
||||||
'changes': self.change_message,
|
'changes': self.change_message,
|
||||||
}
|
}
|
||||||
elif self.action_flag == self.DELETION:
|
elif self.action_flag == self.DELETION:
|
||||||
return ugettext('Deleted "%(object)s."') % {
|
return gettext('Deleted "%(object)s."') % {
|
||||||
'object': self.object_repr}
|
'object': self.object_repr}
|
||||||
|
|
||||||
return ugettext('LogEntry Object')
|
return gettext('LogEntry Object')
|
||||||
|
|
||||||
def is_addition(self):
|
def is_addition(self):
|
||||||
return self.action_flag == self.ADDITION
|
return self.action_flag == self.ADDITION
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
djadmin2's permission handling. The permission classes have the same API as
|
djadmin2's permission handling. The permission classes have the same API as
|
||||||
the permission handling classes of the django-rest-framework. That way, we can
|
the permission handling classes of the django-rest-framework. That way, we can
|
||||||
|
|
@ -15,8 +14,6 @@ interface:
|
||||||
The permission classes are then just fancy wrappers of these basic checks of
|
The permission classes are then just fancy wrappers of these basic checks of
|
||||||
which it can hold multiple.
|
which it can hold multiple.
|
||||||
"""
|
"""
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
@ -25,8 +22,7 @@ from django.db.utils import DEFAULT_DB_ALIAS
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import router
|
from django.db import router
|
||||||
from django.utils import six
|
from django.utils.encoding import force_str
|
||||||
from django.utils.encoding import python_2_unicode_compatible, force_text
|
|
||||||
|
|
||||||
logger = logging.getLogger('djadmin2')
|
logger = logging.getLogger('djadmin2')
|
||||||
|
|
||||||
|
|
@ -35,7 +31,7 @@ def is_authenticated(request, view, obj=None):
|
||||||
'''
|
'''
|
||||||
Checks if the current user is authenticated.
|
Checks if the current user is authenticated.
|
||||||
'''
|
'''
|
||||||
return request.user.is_authenticated()
|
return request.user.is_authenticated
|
||||||
|
|
||||||
|
|
||||||
def is_staff(request, view, obj=None):
|
def is_staff(request, view, obj=None):
|
||||||
|
|
@ -95,7 +91,7 @@ def model_permission(permission):
|
||||||
return has_permission
|
return has_permission
|
||||||
|
|
||||||
|
|
||||||
class BasePermission(object):
|
class BasePermission:
|
||||||
'''
|
'''
|
||||||
Provides a base class with a common API. It implements a compatible
|
Provides a base class with a common API. It implements a compatible
|
||||||
interface to django-rest-framework permission backends.
|
interface to django-rest-framework permission backends.
|
||||||
|
|
@ -191,8 +187,7 @@ class ModelDeletePermission(BasePermission):
|
||||||
permissions = (model_permission('{app_label}.delete_{model_name}'),)
|
permissions = (model_permission('{app_label}.delete_{model_name}'),)
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
class TemplatePermissionChecker:
|
||||||
class TemplatePermissionChecker(object):
|
|
||||||
'''
|
'''
|
||||||
Can be used in the template like:
|
Can be used in the template like:
|
||||||
|
|
||||||
|
|
@ -259,7 +254,7 @@ class TemplatePermissionChecker(object):
|
||||||
needs an interface beeing implemented like suggested in:
|
needs an interface beeing implemented like suggested in:
|
||||||
https://github.com/twoscoops/django-admin2/issues/142
|
https://github.com/twoscoops/django-admin2/issues/142
|
||||||
'''
|
'''
|
||||||
_has_named_permission_regex = re.compile('^has_(?P<name>\w+)_permission$')
|
_has_named_permission_regex = re.compile('^has_(?P<name>\\w+)_permission$')
|
||||||
|
|
||||||
view_name_mapping = {
|
view_name_mapping = {
|
||||||
'view': 'detail_view',
|
'view': 'detail_view',
|
||||||
|
|
@ -286,7 +281,7 @@ class TemplatePermissionChecker(object):
|
||||||
Return a clone of the permission wrapper with a new model_admin bind
|
Return a clone of the permission wrapper with a new model_admin bind
|
||||||
to it.
|
to it.
|
||||||
'''
|
'''
|
||||||
if isinstance(admin, six.string_types):
|
if isinstance(admin, str):
|
||||||
try:
|
try:
|
||||||
admin = self._model_admin.admin.get_admin_by_name(admin)
|
admin = self._model_admin.admin.get_admin_by_name(admin)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
|
@ -300,7 +295,7 @@ class TemplatePermissionChecker(object):
|
||||||
'''
|
'''
|
||||||
Return a clone of the permission wrapper with a new view bind to it.
|
Return a clone of the permission wrapper with a new view bind to it.
|
||||||
'''
|
'''
|
||||||
if isinstance(view, six.string_types):
|
if isinstance(view, str):
|
||||||
if view not in self.view_name_mapping:
|
if view not in self.view_name_mapping:
|
||||||
return ''
|
return ''
|
||||||
view_name = self.view_name_mapping[view]
|
view_name = self.view_name_mapping[view]
|
||||||
|
|
@ -365,7 +360,7 @@ class TemplatePermissionChecker(object):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self._view is None:
|
if self._view is None:
|
||||||
return ''
|
return ''
|
||||||
return force_text(bool(self))
|
return force_str(bool(self))
|
||||||
|
|
||||||
|
|
||||||
def create_view_permissions(app_config, verbosity=2, interactive=True, using=DEFAULT_DB_ALIAS, **kwargs): # noqa
|
def create_view_permissions(app_config, verbosity=2, interactive=True, using=DEFAULT_DB_ALIAS, **kwargs): # noqa
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,14 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
There are currently a few renderers that come directly with django-admin2. They
|
There are currently a few renderers that come directly with django-admin2. They
|
||||||
are used by default for some field types.
|
are used by default for some field types.
|
||||||
"""
|
"""
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
|
||||||
|
|
||||||
import os.path
|
import os.path
|
||||||
from datetime import date, time, datetime
|
from datetime import date, time, datetime
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.utils import formats, timezone
|
from django.utils import formats, timezone
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_str
|
||||||
|
|
||||||
from djadmin2 import settings
|
from djadmin2 import settings
|
||||||
|
|
||||||
|
|
@ -65,7 +62,7 @@ def title_renderer(value, field):
|
||||||
:rtype: unicode or str
|
:rtype: unicode or str
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return force_text(value).title()
|
return force_str(value).title()
|
||||||
|
|
||||||
|
|
||||||
def number_renderer(value, field):
|
def number_renderer(value, field):
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,8 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
|
||||||
|
|
||||||
from numbers import Number
|
from numbers import Number
|
||||||
from datetime import date, time, datetime
|
from datetime import date, time, datetime
|
||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
from django.db.models.fields import FieldDoesNotExist
|
from django.core.exceptions import FieldDoesNotExist
|
||||||
|
|
||||||
from .. import utils, renderers, models, settings
|
from .. import utils, renderers, models, settings
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "djadmin2theme_bootstrap3/base.html" %}
|
{% extends "djadmin2theme_bootstrap3/base.html" %}
|
||||||
{% load i18n staticfiles admin2_tags %}
|
{% load i18n static admin2_tags %}
|
||||||
|
|
||||||
{% block navbar %}{% endblock navbar %}
|
{% block navbar %}{% endblock navbar %}
|
||||||
{% block breacrumbs %}{% endblock breacrumbs %}
|
{% block breacrumbs %}{% endblock breacrumbs %}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ from ..actions import get_description
|
||||||
from .models import Thing
|
from .models import Thing
|
||||||
|
|
||||||
|
|
||||||
class TestAction(object):
|
class TestAction:
|
||||||
description = "Test Action Class"
|
description = "Test Action Class"
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -23,19 +23,19 @@ class ActionTest(TestCase):
|
||||||
TestAction,
|
TestAction,
|
||||||
test_function,
|
test_function,
|
||||||
])
|
])
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
get_description(
|
get_description(
|
||||||
self.admin2.registry[Thing].list_actions[0]
|
self.admin2.registry[Thing].list_actions[0]
|
||||||
),
|
),
|
||||||
'Delete selected items'
|
'Delete selected items'
|
||||||
)
|
)
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
get_description(
|
get_description(
|
||||||
self.admin2.registry[Thing].list_actions[1]
|
self.admin2.registry[Thing].list_actions[1]
|
||||||
),
|
),
|
||||||
'Test Action Class'
|
'Test Action Class'
|
||||||
)
|
)
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
get_description(
|
get_description(
|
||||||
self.admin2.registry[Thing].list_actions[2]
|
self.admin2.registry[Thing].list_actions[2]
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -22,65 +22,65 @@ class TagsTests(TestCase):
|
||||||
self.instance = TagsTestsModel()
|
self.instance = TagsTestsModel()
|
||||||
|
|
||||||
def test_admin2_urlname(self):
|
def test_admin2_urlname(self):
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
"admin2:None_None_index",
|
"admin2:None_None_index",
|
||||||
admin2_tags.admin2_urlname(IndexView, "index")
|
admin2_tags.admin2_urlname(IndexView, "index")
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_model_verbose_name_as_model_class(self):
|
def test_model_verbose_name_as_model_class(self):
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
TagsTestsModel._meta.verbose_name,
|
TagsTestsModel._meta.verbose_name,
|
||||||
admin2_tags.model_verbose_name(TagsTestsModel)
|
admin2_tags.model_verbose_name(TagsTestsModel)
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_model_verbose_name_as_model_instance(self):
|
def test_model_verbose_name_as_model_instance(self):
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
self.instance._meta.verbose_name,
|
self.instance._meta.verbose_name,
|
||||||
admin2_tags.model_verbose_name(self.instance)
|
admin2_tags.model_verbose_name(self.instance)
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_model_verbose_name_plural_as_model_class(self):
|
def test_model_verbose_name_plural_as_model_class(self):
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
TagsTestsModel._meta.verbose_name_plural,
|
TagsTestsModel._meta.verbose_name_plural,
|
||||||
admin2_tags.model_verbose_name_plural(TagsTestsModel)
|
admin2_tags.model_verbose_name_plural(TagsTestsModel)
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_model_verbose_name_plural_as_model_instance(self):
|
def test_model_verbose_name_plural_as_model_instance(self):
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
self.instance._meta.verbose_name_plural,
|
self.instance._meta.verbose_name_plural,
|
||||||
admin2_tags.model_verbose_name_plural(self.instance)
|
admin2_tags.model_verbose_name_plural(self.instance)
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_model_field_verbose_name_autogenerated(self):
|
def test_model_field_verbose_name_autogenerated(self):
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
'field1',
|
'field1',
|
||||||
admin2_tags.model_attr_verbose_name(self.instance, 'field1')
|
admin2_tags.model_attr_verbose_name(self.instance, 'field1')
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_model_field_verbose_name_overridden(self):
|
def test_model_field_verbose_name_overridden(self):
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
'second field',
|
'second field',
|
||||||
admin2_tags.model_attr_verbose_name(self.instance, 'field2')
|
admin2_tags.model_attr_verbose_name(self.instance, 'field2')
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_model_method_verbose_name(self):
|
def test_model_method_verbose_name(self):
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
'Published recently?',
|
'Published recently?',
|
||||||
admin2_tags.model_attr_verbose_name(self.instance, 'was_published_recently')
|
admin2_tags.model_attr_verbose_name(self.instance, 'was_published_recently')
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_formset_visible_fieldlist(self):
|
def test_formset_visible_fieldlist(self):
|
||||||
formset = TagsTestFormSet()
|
formset = TagsTestFormSet()
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
admin2_tags.formset_visible_fieldlist(formset),
|
admin2_tags.formset_visible_fieldlist(formset),
|
||||||
[u'Visible 1', u'Visible 2']
|
['Visible 1', 'Visible 2']
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_verbose_name_for(self):
|
def test_verbose_name_for(self):
|
||||||
app_verbose_names = {
|
app_verbose_names = {
|
||||||
u'app_one_label': 'App One Verbose Name',
|
'app_one_label': 'App One Verbose Name',
|
||||||
}
|
}
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
"App One Verbose Name",
|
"App One Verbose Name",
|
||||||
admin2_tags.verbose_name_for(app_verbose_names, 'app_one_label')
|
admin2_tags.verbose_name_for(app_verbose_names, 'app_one_label')
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
|
||||||
from djadmin2.site import djadmin2_site
|
from djadmin2.site import djadmin2_site
|
||||||
from ..admin2 import UserAdmin2
|
from ..admin2 import UserAdmin2
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ class Admin2Test(TestCase):
|
||||||
|
|
||||||
def test_register_app_verbose_name(self):
|
def test_register_app_verbose_name(self):
|
||||||
self.admin2.register_app_verbose_name(APP_LABEL, APP_VERBOSE_NAME)
|
self.admin2.register_app_verbose_name(APP_LABEL, APP_VERBOSE_NAME)
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
self.admin2.app_verbose_names[APP_LABEL],
|
self.admin2.app_verbose_names[APP_LABEL],
|
||||||
APP_VERBOSE_NAME
|
APP_VERBOSE_NAME
|
||||||
)
|
)
|
||||||
|
|
@ -61,7 +61,7 @@ class Admin2Test(TestCase):
|
||||||
|
|
||||||
def test_get_urls(self):
|
def test_get_urls(self):
|
||||||
self.admin2.register(SmallThing)
|
self.admin2.register(SmallThing)
|
||||||
self.assertEquals(8, len(self.admin2.get_urls()))
|
self.assertEqual(8, len(self.admin2.get_urls()))
|
||||||
|
|
||||||
def test_default_entries(self):
|
def test_default_entries(self):
|
||||||
expected_default_models = (User, Group, Site)
|
expected_default_models = (User, Group, Site)
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
|
||||||
|
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.utils import six
|
|
||||||
from django.utils.translation import activate
|
from django.utils.translation import activate
|
||||||
|
|
||||||
from .. import renderers
|
from .. import renderers
|
||||||
|
|
@ -106,10 +102,7 @@ class NumberRendererTest(TestCase):
|
||||||
|
|
||||||
def testEndlessFloat(self):
|
def testEndlessFloat(self):
|
||||||
out = self.renderer(1.0 / 3, None)
|
out = self.renderer(1.0 / 3, None)
|
||||||
if six.PY2:
|
self.assertEqual('0.3333333333333333', out)
|
||||||
self.assertEqual('0.333333333333', out)
|
|
||||||
else:
|
|
||||||
self.assertEqual('0.3333333333333333', out)
|
|
||||||
|
|
||||||
def testPlainDecimal(self):
|
def testPlainDecimal(self):
|
||||||
number = '0.123456789123456789123456789'
|
number = '0.123456789123456789123456789'
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ from ..core import Admin2
|
||||||
from .models import BigThing
|
from .models import BigThing
|
||||||
|
|
||||||
|
|
||||||
class ModelAdmin(object):
|
class ModelAdmin:
|
||||||
model_admin_attributes = ['a', 'b', 'c']
|
model_admin_attributes = ['a', 'b', 'c']
|
||||||
a = 1 # covered
|
a = 1 # covered
|
||||||
b = 2 # covered
|
b = 2 # covered
|
||||||
|
|
@ -31,9 +31,9 @@ class ImmutableAdminFactoryTests(TestCase):
|
||||||
del self.immutable_admin.a
|
del self.immutable_admin.a
|
||||||
|
|
||||||
def test_attributes(self):
|
def test_attributes(self):
|
||||||
self.assertEquals(self.immutable_admin.a, 1)
|
self.assertEqual(self.immutable_admin.a, 1)
|
||||||
self.assertEquals(self.immutable_admin.b, 2)
|
self.assertEqual(self.immutable_admin.b, 2)
|
||||||
self.assertEquals(self.immutable_admin.c, 3)
|
self.assertEqual(self.immutable_admin.c, 3)
|
||||||
with self.assertRaises(AttributeError):
|
with self.assertRaises(AttributeError):
|
||||||
# 'ImmutableAdmin' object has no attribute 'd'
|
# 'ImmutableAdmin' object has no attribute 'd'
|
||||||
self.immutable_admin.d
|
self.immutable_admin.d
|
||||||
|
|
@ -73,7 +73,7 @@ class ModelAdminTest(TestCase):
|
||||||
admin_instance.get_urls()
|
admin_instance.get_urls()
|
||||||
|
|
||||||
except TypeError as e:
|
except TypeError as e:
|
||||||
message = u"Cannot instantiate admin view " \
|
message = "Cannot instantiate admin view " \
|
||||||
'"ModelAdmin2.None". The error that got raised was: ' \
|
'"ModelAdmin2.None". The error that got raised was: ' \
|
||||||
"'NoneType' object has no attribute 'as_view'"
|
"'NoneType' object has no attribute 'as_view'"
|
||||||
self.assertEqual(e.args[0], message)
|
self.assertEqual(e.args[0], message)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.utils import six
|
|
||||||
|
|
||||||
from .. import utils
|
from .. import utils
|
||||||
from ..views import IndexView
|
from ..views import IndexView
|
||||||
|
|
@ -12,14 +11,14 @@ class UtilsTest(TestCase):
|
||||||
self.instance = UtilsTestModel()
|
self.instance = UtilsTestModel()
|
||||||
|
|
||||||
def test_as_model_class(self):
|
def test_as_model_class(self):
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
UtilsTestModel._meta,
|
UtilsTestModel._meta,
|
||||||
utils.model_options(UtilsTestModel)
|
utils.model_options(UtilsTestModel)
|
||||||
)
|
)
|
||||||
UtilsTestModel._meta.verbose_name = "Utils Test Model is singular"
|
UtilsTestModel._meta.verbose_name = "Utils Test Model is singular"
|
||||||
UtilsTestModel._meta.verbose_name_plural = "Utils Test Model are " +\
|
UtilsTestModel._meta.verbose_name_plural = "Utils Test Model are " +\
|
||||||
" plural"
|
" plural"
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
UtilsTestModel._meta,
|
UtilsTestModel._meta,
|
||||||
utils.model_options(UtilsTestModel)
|
utils.model_options(UtilsTestModel)
|
||||||
)
|
)
|
||||||
|
|
@ -27,14 +26,14 @@ class UtilsTest(TestCase):
|
||||||
UtilsTestModel._meta.verbose_name_plural = "Utils Test Models"
|
UtilsTestModel._meta.verbose_name_plural = "Utils Test Models"
|
||||||
|
|
||||||
def test_as_model_instance(self):
|
def test_as_model_instance(self):
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
self.instance._meta,
|
self.instance._meta,
|
||||||
utils.model_options(self.instance)
|
utils.model_options(self.instance)
|
||||||
)
|
)
|
||||||
self.instance._meta.verbose_name = "Utils Test Model is singular"
|
self.instance._meta.verbose_name = "Utils Test Model is singular"
|
||||||
self.instance._meta.verbose_name_plural = "Utils Test Model are " +\
|
self.instance._meta.verbose_name_plural = "Utils Test Model are " +\
|
||||||
" plural"
|
" plural"
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
self.instance._meta,
|
self.instance._meta,
|
||||||
utils.model_options(self.instance)
|
utils.model_options(self.instance)
|
||||||
)
|
)
|
||||||
|
|
@ -42,117 +41,111 @@ class UtilsTest(TestCase):
|
||||||
self.instance._meta.verbose_name_plural = "Utils Test Models"
|
self.instance._meta.verbose_name_plural = "Utils Test Models"
|
||||||
|
|
||||||
def test_admin2_urlname(self):
|
def test_admin2_urlname(self):
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
"admin2:None_None_index",
|
"admin2:None_None_index",
|
||||||
utils.admin2_urlname(IndexView, "index")
|
utils.admin2_urlname(IndexView, "index")
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_model_app_label_as_model_class(self):
|
def test_model_app_label_as_model_class(self):
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
UtilsTestModel._meta.app_label,
|
UtilsTestModel._meta.app_label,
|
||||||
utils.model_app_label(UtilsTestModel)
|
utils.model_app_label(UtilsTestModel)
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_model_app_label_as_model_instance(self):
|
def test_model_app_label_as_model_instance(self):
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
self.instance._meta.app_label,
|
self.instance._meta.app_label,
|
||||||
utils.model_app_label(UtilsTestModel)
|
utils.model_app_label(UtilsTestModel)
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_model_verbose_name_as_model_class(self):
|
def test_model_verbose_name_as_model_class(self):
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
UtilsTestModel._meta.verbose_name,
|
UtilsTestModel._meta.verbose_name,
|
||||||
utils.model_verbose_name(UtilsTestModel)
|
utils.model_verbose_name(UtilsTestModel)
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_model_verbose_name_as_model_instance(self):
|
def test_model_verbose_name_as_model_instance(self):
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
self.instance._meta.verbose_name,
|
self.instance._meta.verbose_name,
|
||||||
utils.model_verbose_name(self.instance)
|
utils.model_verbose_name(self.instance)
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_model_verbose_name_plural_as_model_class(self):
|
def test_model_verbose_name_plural_as_model_class(self):
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
UtilsTestModel._meta.verbose_name_plural,
|
UtilsTestModel._meta.verbose_name_plural,
|
||||||
utils.model_verbose_name_plural(UtilsTestModel)
|
utils.model_verbose_name_plural(UtilsTestModel)
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_model_verbose_name_plural_as_model_instance(self):
|
def test_model_verbose_name_plural_as_model_instance(self):
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
self.instance._meta.verbose_name_plural,
|
self.instance._meta.verbose_name_plural,
|
||||||
utils.model_verbose_name_plural(self.instance)
|
utils.model_verbose_name_plural(self.instance)
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_model_field_verbose_name_autogenerated(self):
|
def test_model_field_verbose_name_autogenerated(self):
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
'field1',
|
'field1',
|
||||||
utils.model_field_verbose_name(self.instance, 'field1')
|
utils.model_field_verbose_name(self.instance, 'field1')
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_model_field_verbose_name_overridden(self):
|
def test_model_field_verbose_name_overridden(self):
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
'second field',
|
'second field',
|
||||||
utils.model_field_verbose_name(self.instance, 'field2')
|
utils.model_field_verbose_name(self.instance, 'field2')
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_model_method_verbose_name(self):
|
def test_model_method_verbose_name(self):
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
'Published recently?',
|
'Published recently?',
|
||||||
utils.model_method_verbose_name(self.instance, 'was_published_recently')
|
utils.model_method_verbose_name(self.instance, 'was_published_recently')
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_model_method_verbose_name_fallback(self):
|
def test_model_method_verbose_name_fallback(self):
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
'simple_method',
|
'simple_method',
|
||||||
utils.model_method_verbose_name(self.instance, 'simple_method')
|
utils.model_method_verbose_name(self.instance, 'simple_method')
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_app_label_as_model_class(self):
|
def test_app_label_as_model_class(self):
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
UtilsTestModel._meta.app_label,
|
UtilsTestModel._meta.app_label,
|
||||||
utils.model_app_label(UtilsTestModel)
|
utils.model_app_label(UtilsTestModel)
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_app_label_as_model_instance(self):
|
def test_app_label_as_model_instance(self):
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
self.instance._meta.app_label,
|
self.instance._meta.app_label,
|
||||||
utils.model_app_label(self.instance)
|
utils.model_app_label(self.instance)
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_attr_callable(self):
|
def test_get_attr_callable(self):
|
||||||
class Klass(object):
|
class Klass:
|
||||||
def hello(self):
|
def hello(self):
|
||||||
return "hello"
|
return "hello"
|
||||||
|
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
utils.get_attr(Klass(), "hello"),
|
utils.get_attr(Klass(), "hello"),
|
||||||
"hello"
|
"hello"
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_attr_str(self):
|
def test_get_attr_str(self):
|
||||||
class Klass(object):
|
class Klass:
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "str"
|
return "str"
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return "unicode"
|
return "unicode"
|
||||||
|
|
||||||
if six.PY2:
|
self.assertEqual(
|
||||||
self.assertEquals(
|
utils.get_attr(Klass(), "__str__"),
|
||||||
utils.get_attr(Klass(), "__str__"),
|
"str"
|
||||||
"unicode"
|
)
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.assertEquals(
|
|
||||||
utils.get_attr(Klass(), "__str__"),
|
|
||||||
"str"
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_get_attr(self):
|
def test_get_attr(self):
|
||||||
class Klass(object):
|
class Klass:
|
||||||
attr = "value"
|
attr = "value"
|
||||||
|
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
utils.get_attr(Klass(), "attr"),
|
utils.get_attr(Klass(), "attr"),
|
||||||
"value"
|
"value"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.test import TestCase, override_settings
|
from django.test import TestCase, override_settings
|
||||||
from django.utils.encoding import force_text
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from django.utils.encoding import force_str
|
||||||
|
|
||||||
|
|
||||||
from .. import views
|
from .. import views
|
||||||
|
|
||||||
|
|
@ -11,13 +13,13 @@ class AdminViewTest(TestCase):
|
||||||
self.admin_view = views.AdminView(r'^$', views.ModelListView, name='admin-view')
|
self.admin_view = views.AdminView(r'^$', views.ModelListView, name='admin-view')
|
||||||
|
|
||||||
def test_url(self):
|
def test_url(self):
|
||||||
self.assertEquals(self.admin_view.url, r'^$')
|
self.assertEqual(self.admin_view.url, r'^$')
|
||||||
|
|
||||||
def test_view(self):
|
def test_view(self):
|
||||||
self.assertEquals(self.admin_view.view, views.ModelListView)
|
self.assertEqual(self.admin_view.view, views.ModelListView)
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
self.assertEquals(self.admin_view.name, 'admin-view')
|
self.assertEqual(self.admin_view.name, 'admin-view')
|
||||||
|
|
||||||
|
|
||||||
@override_settings(ROOT_URLCONF='djadmin2.tests.urls')
|
@override_settings(ROOT_URLCONF='djadmin2.tests.urls')
|
||||||
|
|
@ -25,4 +27,4 @@ class CustomLoginViewTest(TestCase):
|
||||||
|
|
||||||
def test_view_ok(self):
|
def test_view_ok(self):
|
||||||
response = self.client.get(reverse("admin2:dashboard"))
|
response = self.client.get(reverse("admin2:dashboard"))
|
||||||
self.assertInHTML('<h3 class="panel-title">Custom login view</h3>', force_text(response.content))
|
self.assertInHTML('<h3 class="panel-title">Custom login view</h3>', force_str(response.content))
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls import include, url
|
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
|
from django.urls import re_path
|
||||||
|
|
||||||
from djadmin2.site import djadmin2_site
|
from djadmin2.site import djadmin2_site
|
||||||
|
|
||||||
|
|
@ -12,9 +10,10 @@ from djadmin2.views import LoginView
|
||||||
class CustomLoginView(LoginView):
|
class CustomLoginView(LoginView):
|
||||||
default_template_name = "custom_login_template.html"
|
default_template_name = "custom_login_template.html"
|
||||||
|
|
||||||
|
|
||||||
djadmin2_site.login_view = CustomLoginView
|
djadmin2_site.login_view = CustomLoginView
|
||||||
djadmin2_site.autodiscover()
|
djadmin2_site.autodiscover()
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^admin2/', include(djadmin2_site.urls)),
|
re_path(r'^admin2/', djadmin2_site.urls),
|
||||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "djadmin2theme_bootstrap3/base.html" %}
|
{% extends "djadmin2theme_bootstrap3/base.html" %}
|
||||||
{% load i18n staticfiles admin2_tags %}
|
{% load i18n static admin2_tags %}
|
||||||
|
|
||||||
{% block navbar %}{% endblock navbar %}
|
{% block navbar %}{% endblock navbar %}
|
||||||
{% block breacrumbs %}{% endblock breacrumbs %}
|
{% block breacrumbs %}{% endblock breacrumbs %}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{% load i18n %}{% load staticfiles %}<!DOCTYPE html>
|
{% load i18n %}{% load static %}<!DOCTYPE html>
|
||||||
<html lang="{{ request.LANGUAGE_CODE }}">
|
<html lang="{{ request.LANGUAGE_CODE }}">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "djadmin2theme_bootstrap3/base.html" %}
|
{% extends "djadmin2theme_bootstrap3/base.html" %}
|
||||||
{% load i18n staticfiles admin2_tags %}
|
{% load i18n static admin2_tags %}
|
||||||
|
|
||||||
{% block title %}{% blocktrans with model_name=model_name %}Select {{ model_name }} to change{% endblocktrans %}{% endblock title %}
|
{% block title %}{% blocktrans with model_name=model_name %}Select {{ model_name }} to change{% endblocktrans %}{% endblock title %}
|
||||||
|
|
||||||
|
|
@ -49,7 +49,7 @@
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% for link, date in dates %}
|
{% for link, date in dates %}
|
||||||
<li class="{% ifequal active_day date %}active{% endifequal %}">
|
<li class="{% if active_day == date %}active{% endif %}">
|
||||||
<a href="{{ link|safe }}">{{ date }}</a>
|
<a href="{{ link|safe }}">{{ date }}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,11 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
import extra_views
|
import extra_views
|
||||||
from django.conf.urls import url
|
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.forms import modelform_factory
|
from django.forms import modelform_factory
|
||||||
from django.utils.six import with_metaclass
|
from django.urls import re_path, reverse
|
||||||
|
|
||||||
from . import actions
|
from . import actions
|
||||||
from . import apiviews
|
from . import apiviews
|
||||||
|
|
@ -19,14 +14,12 @@ from . import utils
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger('djadmin2')
|
logger = logging.getLogger("djadmin2")
|
||||||
|
|
||||||
|
|
||||||
class ModelAdminBase2(type):
|
class ModelAdminBase2(type):
|
||||||
|
|
||||||
def __new__(cls, name, bases, attrs):
|
def __new__(cls, name, bases, attrs):
|
||||||
new_class = super(ModelAdminBase2, cls).__new__(cls, name,
|
new_class = super().__new__(cls, name, bases, attrs)
|
||||||
bases, attrs)
|
|
||||||
view_list = []
|
view_list = []
|
||||||
for key, value in attrs.items():
|
for key, value in attrs.items():
|
||||||
if isinstance(value, views.AdminView):
|
if isinstance(value, views.AdminView):
|
||||||
|
|
@ -34,12 +27,12 @@ class ModelAdminBase2(type):
|
||||||
value.name = key
|
value.name = key
|
||||||
view_list.append(value)
|
view_list.append(value)
|
||||||
|
|
||||||
view_list.extend(getattr(new_class, 'views', []))
|
view_list.extend(getattr(new_class, "views", []))
|
||||||
new_class.views = view_list
|
new_class.views = view_list
|
||||||
return new_class
|
return new_class
|
||||||
|
|
||||||
|
|
||||||
class ModelAdmin2(with_metaclass(ModelAdminBase2)):
|
class ModelAdmin2(metaclass=ModelAdminBase2):
|
||||||
"""
|
"""
|
||||||
Adding new ModelAdmin2 attributes:
|
Adding new ModelAdmin2 attributes:
|
||||||
|
|
||||||
|
|
@ -58,9 +51,10 @@ class ModelAdmin2(with_metaclass(ModelAdminBase2)):
|
||||||
This prevents us from easily implementing methods/setters which
|
This prevents us from easily implementing methods/setters which
|
||||||
bypass the blocking features of the ImmutableAdmin.
|
bypass the blocking features of the ImmutableAdmin.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
actions_selection_counter = True
|
actions_selection_counter = True
|
||||||
date_hierarchy = False
|
date_hierarchy = False
|
||||||
list_display = ('__str__',)
|
list_display = ("__str__",)
|
||||||
list_display_links = ()
|
list_display_links = ()
|
||||||
list_filter = ()
|
list_filter = ()
|
||||||
list_select_related = False
|
list_select_related = False
|
||||||
|
|
@ -117,12 +111,20 @@ class ModelAdmin2(with_metaclass(ModelAdminBase2)):
|
||||||
inlines = []
|
inlines = []
|
||||||
|
|
||||||
# Views
|
# Views
|
||||||
index_view = views.AdminView(r'^$', views.ModelListView, name='index')
|
index_view = views.AdminView(r"^$", views.ModelListView, name="index")
|
||||||
create_view = views.AdminView(r'^create/$', views.ModelAddFormView, name='create')
|
create_view = views.AdminView(r"^create/$", views.ModelAddFormView, name="create")
|
||||||
update_view = views.AdminView(r'^(?P<pk>[0-9]+)/$', views.ModelEditFormView, name='update')
|
update_view = views.AdminView(
|
||||||
detail_view = views.AdminView(r'^(?P<pk>[0-9]+)/update/$', views.ModelDetailView, name='detail')
|
r"^(?P<pk>[0-9]+)/$", views.ModelEditFormView, name="update"
|
||||||
delete_view = views.AdminView(r'^(?P<pk>[0-9]+)/delete/$', views.ModelDeleteView, name='delete')
|
)
|
||||||
history_view = views.AdminView(r'^(?P<pk>[0-9]+)/history/$', views.ModelHistoryView, name='history')
|
detail_view = views.AdminView(
|
||||||
|
r"^(?P<pk>[0-9]+)/update/$", views.ModelDetailView, name="detail"
|
||||||
|
)
|
||||||
|
delete_view = views.AdminView(
|
||||||
|
r"^(?P<pk>[0-9]+)/delete/$", views.ModelDeleteView, name="delete"
|
||||||
|
)
|
||||||
|
history_view = views.AdminView(
|
||||||
|
r"^(?P<pk>[0-9]+)/history/$", views.ModelHistoryView, name="history"
|
||||||
|
)
|
||||||
views = []
|
views = []
|
||||||
|
|
||||||
# API configuration
|
# API configuration
|
||||||
|
|
@ -141,7 +143,7 @@ class ModelAdmin2(with_metaclass(ModelAdminBase2)):
|
||||||
self.model_name = model_options.object_name.lower()
|
self.model_name = model_options.object_name.lower()
|
||||||
|
|
||||||
if self.name is None:
|
if self.name is None:
|
||||||
self.name = '{}_{}'.format(self.app_label, self.model_name)
|
self.name = "{}_{}".format(self.app_label, self.model_name)
|
||||||
|
|
||||||
if self.verbose_name is None:
|
if self.verbose_name is None:
|
||||||
self.verbose_name = model_options.verbose_name
|
self.verbose_name = model_options.verbose_name
|
||||||
|
|
@ -150,60 +152,73 @@ class ModelAdmin2(with_metaclass(ModelAdminBase2)):
|
||||||
|
|
||||||
def get_default_view_kwargs(self):
|
def get_default_view_kwargs(self):
|
||||||
return {
|
return {
|
||||||
'app_label': self.app_label,
|
"app_label": self.app_label,
|
||||||
'model': self.model,
|
"model": self.model,
|
||||||
'model_name': self.model_name,
|
"model_name": self.model_name,
|
||||||
'model_admin': immutable_admin_factory(self),
|
"model_admin": immutable_admin_factory(self),
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_index_kwargs(self):
|
def get_index_kwargs(self):
|
||||||
kwargs = self.get_default_view_kwargs()
|
kwargs = self.get_default_view_kwargs()
|
||||||
kwargs.update({
|
kwargs.update(
|
||||||
'paginate_by': self.list_per_page,
|
{
|
||||||
})
|
"paginate_by": self.list_per_page,
|
||||||
|
}
|
||||||
|
)
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
def get_default_api_view_kwargs(self):
|
def get_default_api_view_kwargs(self):
|
||||||
kwargs = self.get_default_view_kwargs()
|
kwargs = self.get_default_view_kwargs()
|
||||||
kwargs.update({
|
kwargs.update(
|
||||||
'serializer_class': self.api_serializer_class,
|
{
|
||||||
})
|
"serializer_class": self.api_serializer_class,
|
||||||
|
}
|
||||||
|
)
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
def get_prefixed_view_name(self, view_name):
|
def get_prefixed_view_name(self, view_name):
|
||||||
return '{}_{}'.format(self.name, view_name)
|
return "{}_{}".format(self.name, view_name)
|
||||||
|
|
||||||
def get_create_kwargs(self):
|
def get_create_kwargs(self):
|
||||||
kwargs = self.get_default_view_kwargs()
|
kwargs = self.get_default_view_kwargs()
|
||||||
kwargs.update({
|
kwargs.update(
|
||||||
'inlines': self.inlines,
|
{
|
||||||
'form_class': (self.create_form_class if
|
"inlines": self.inlines,
|
||||||
self.create_form_class else self.form_class),
|
"form_class": (
|
||||||
})
|
self.create_form_class
|
||||||
|
if self.create_form_class
|
||||||
|
else self.form_class
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
def get_update_kwargs(self):
|
def get_update_kwargs(self):
|
||||||
kwargs = self.get_default_view_kwargs()
|
kwargs = self.get_default_view_kwargs()
|
||||||
form_class = (self.update_form_class if
|
form_class = (
|
||||||
self.update_form_class else self.form_class)
|
self.update_form_class if self.update_form_class else self.form_class
|
||||||
|
)
|
||||||
if form_class is None:
|
if form_class is None:
|
||||||
form_class = modelform_factory(self.model, fields='__all__')
|
form_class = modelform_factory(self.model, fields="__all__")
|
||||||
kwargs.update({
|
kwargs.update(
|
||||||
'inlines': self.inlines,
|
{
|
||||||
'form_class': form_class,
|
"inlines": self.inlines,
|
||||||
})
|
"form_class": form_class,
|
||||||
|
}
|
||||||
|
)
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
def get_index_url(self):
|
def get_index_url(self):
|
||||||
return reverse('admin2:{}'.format(
|
return reverse("admin2:{}".format(self.get_prefixed_view_name("index")))
|
||||||
self.get_prefixed_view_name('index')))
|
|
||||||
|
|
||||||
def get_api_list_kwargs(self):
|
def get_api_list_kwargs(self):
|
||||||
kwargs = self.get_default_api_view_kwargs()
|
kwargs = self.get_default_api_view_kwargs()
|
||||||
kwargs.update({
|
kwargs.update(
|
||||||
'queryset': self.model.objects.all(),
|
{
|
||||||
# 'paginate_by': self.list_per_page,
|
"queryset": self.model.objects.all(),
|
||||||
})
|
# 'paginate_by': self.list_per_page,
|
||||||
|
}
|
||||||
|
)
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
def get_api_detail_kwargs(self):
|
def get_api_detail_kwargs(self):
|
||||||
|
|
@ -222,34 +237,35 @@ class ModelAdmin2(with_metaclass(ModelAdminBase2)):
|
||||||
trace = sys.exc_info()[2]
|
trace = sys.exc_info()[2]
|
||||||
new_exception = TypeError(
|
new_exception = TypeError(
|
||||||
'Cannot instantiate admin view "{}.{}". '
|
'Cannot instantiate admin view "{}.{}". '
|
||||||
'The error that got raised was: {}'.format(
|
"The error that got raised was: {}".format(
|
||||||
self.__class__.__name__, admin_view.name, e))
|
self.__class__.__name__, admin_view.name, e
|
||||||
|
)
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
raise new_exception.with_traceback(trace)
|
raise new_exception.with_traceback(trace)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise (new_exception, None, trace)
|
raise (new_exception, None, trace)
|
||||||
|
|
||||||
pattern_list.append(
|
pattern_list.append(
|
||||||
url(
|
re_path(
|
||||||
regex=admin_view.url,
|
admin_view.url,
|
||||||
view=view_instance,
|
view=view_instance,
|
||||||
name=self.get_prefixed_view_name(admin_view.name)
|
name=self.get_prefixed_view_name(admin_view.name),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return pattern_list
|
return pattern_list
|
||||||
|
|
||||||
def get_api_urls(self):
|
def get_api_urls(self):
|
||||||
return [
|
return [
|
||||||
url(
|
re_path(
|
||||||
regex=r'^$',
|
r"^$",
|
||||||
view=self.api_list_view.as_view(**self.get_api_list_kwargs()),
|
view=self.api_list_view.as_view(**self.get_api_list_kwargs()),
|
||||||
name=self.get_prefixed_view_name('api_list'),
|
name=self.get_prefixed_view_name("api_list"),
|
||||||
),
|
),
|
||||||
url(
|
re_path(
|
||||||
regex=r'^(?P<pk>[0-9]+)/$',
|
r"^(?P<pk>[0-9]+)/$",
|
||||||
view=self.api_detail_view.as_view(
|
view=self.api_detail_view.as_view(**self.get_api_detail_kwargs()),
|
||||||
**self.get_api_detail_kwargs()),
|
name=self.get_prefixed_view_name("api_detail"),
|
||||||
name=self.get_prefixed_view_name('api_detail'),
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -266,12 +282,12 @@ class ModelAdmin2(with_metaclass(ModelAdminBase2)):
|
||||||
actions_dict = {}
|
actions_dict = {}
|
||||||
|
|
||||||
for cls in type(self).mro()[::-1]:
|
for cls in type(self).mro()[::-1]:
|
||||||
class_actions = getattr(cls, 'list_actions', [])
|
class_actions = getattr(cls, "list_actions", [])
|
||||||
for action in class_actions:
|
for action in class_actions:
|
||||||
actions_dict[action.__name__] = {
|
actions_dict[action.__name__] = {
|
||||||
'name': action.__name__,
|
"name": action.__name__,
|
||||||
'description': actions.get_description(action),
|
"description": actions.get_description(action),
|
||||||
'action_callable': action
|
"action_callable": action,
|
||||||
}
|
}
|
||||||
return actions_dict
|
return actions_dict
|
||||||
|
|
||||||
|
|
@ -279,19 +295,21 @@ class ModelAdmin2(with_metaclass(ModelAdminBase2)):
|
||||||
return self.ordering
|
return self.ordering
|
||||||
|
|
||||||
|
|
||||||
class Admin2Inline(extra_views.InlineFormSet):
|
class Admin2Inline(extra_views.InlineFormSetFactory):
|
||||||
"""
|
"""
|
||||||
A simple extension of django-extra-view's InlineFormSet that
|
A simple extension of django-extra-view's InlineFormSet that
|
||||||
adds some useful functionality.
|
adds some useful functionality.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
template = None
|
template = None
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
def construct_formset(self):
|
def construct_formset(self):
|
||||||
"""
|
"""
|
||||||
Overrides construct_formset to attach the model class as
|
Overrides construct_formset to attach the model class as
|
||||||
an attribute of the returned formset instance.
|
an attribute of the returned formset instance.
|
||||||
"""
|
"""
|
||||||
formset = super(Admin2Inline, self).construct_formset()
|
formset = super().construct_formset()
|
||||||
formset.model = self.inline_model
|
formset.model = self.inline_model
|
||||||
formset.template = self.template
|
formset.template = self.template
|
||||||
return formset
|
return formset
|
||||||
|
|
@ -299,12 +317,14 @@ class Admin2Inline(extra_views.InlineFormSet):
|
||||||
|
|
||||||
class Admin2TabularInline(Admin2Inline):
|
class Admin2TabularInline(Admin2Inline):
|
||||||
template = os.path.join(
|
template = os.path.join(
|
||||||
settings.ADMIN2_THEME_DIRECTORY, 'edit_inlines/tabular.html')
|
settings.ADMIN2_THEME_DIRECTORY, "edit_inlines/tabular.html"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Admin2StackedInline(Admin2Inline):
|
class Admin2StackedInline(Admin2Inline):
|
||||||
template = os.path.join(
|
template = os.path.join(
|
||||||
settings.ADMIN2_THEME_DIRECTORY, 'edit_inlines/stacked.html')
|
settings.ADMIN2_THEME_DIRECTORY, "edit_inlines/stacked.html"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def immutable_admin_factory(model_admin):
|
def immutable_admin_factory(model_admin):
|
||||||
|
|
@ -318,8 +338,7 @@ def immutable_admin_factory(model_admin):
|
||||||
the result, but hopefully developers attempting that
|
the result, but hopefully developers attempting that
|
||||||
'workaround/hack' will read our documentation.
|
'workaround/hack' will read our documentation.
|
||||||
"""
|
"""
|
||||||
ImmutableAdmin = namedtuple('ImmutableAdmin',
|
ImmutableAdmin = namedtuple("ImmutableAdmin", model_admin.model_admin_attributes)
|
||||||
model_admin.model_admin_attributes,
|
return ImmutableAdmin(
|
||||||
verbose=False)
|
*[getattr(model_admin, x) for x in model_admin.model_admin_attributes]
|
||||||
return ImmutableAdmin(*[getattr(
|
)
|
||||||
model_admin, x) for x in model_admin.model_admin_attributes])
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
|
||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
|
from django.core.exceptions import FieldDoesNotExist
|
||||||
|
from django.db.models.constants import LOOKUP_SEP
|
||||||
from django.db.models.deletion import Collector, ProtectedError
|
from django.db.models.deletion import Collector, ProtectedError
|
||||||
from django.db.models.sql.constants import QUERY_TERMS
|
from django.utils.encoding import force_bytes, force_str
|
||||||
from django.utils import six
|
|
||||||
from django.utils.encoding import force_bytes, force_text
|
|
||||||
|
|
||||||
|
|
||||||
def lookup_needs_distinct(opts, lookup_path):
|
def lookup_needs_distinct(opts, lookup_path):
|
||||||
|
|
@ -19,20 +16,24 @@ def lookup_needs_distinct(opts, lookup_path):
|
||||||
https://github.com/django/django/blob/1.9.6/django/contrib/admin/utils.py#L22
|
https://github.com/django/django/blob/1.9.6/django/contrib/admin/utils.py#L22
|
||||||
"""
|
"""
|
||||||
|
|
||||||
lookup_fields = lookup_path.split('__')
|
lookup_fields = lookup_path.split(LOOKUP_SEP)
|
||||||
# Remove the last item of the lookup path if it is a query term
|
# Go through the fields (following all relations) and look for an m2m.
|
||||||
if lookup_fields[-1] in QUERY_TERMS:
|
|
||||||
lookup_fields = lookup_fields[:-1]
|
|
||||||
# Now go through the fields (following all relations) and look for an m2m
|
|
||||||
for field_name in lookup_fields:
|
for field_name in lookup_fields:
|
||||||
field = opts.get_field(field_name)
|
if field_name == 'pk':
|
||||||
if hasattr(field, 'get_path_info'):
|
field_name = opts.pk.name
|
||||||
# This field is a relation, update opts to follow the relation
|
try:
|
||||||
path_info = field.get_path_info()
|
field = opts.get_field(field_name)
|
||||||
opts = path_info[-1].to_opts
|
except FieldDoesNotExist:
|
||||||
if any(path.m2m for path in path_info):
|
# Ignore query lookups.
|
||||||
# This field is a m2m relation so we know we need to call distinct
|
continue
|
||||||
return True
|
else:
|
||||||
|
if hasattr(field, 'get_path_info'):
|
||||||
|
# This field is a relation; update opts to follow the relation.
|
||||||
|
path_info = field.get_path_info()
|
||||||
|
opts = path_info[-1].to_opts
|
||||||
|
if any(path.m2m for path in path_info):
|
||||||
|
# This field is a m2m relation so distinct must be called.
|
||||||
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -118,7 +119,7 @@ class NestedObjects(Collector):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(NestedObjects, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.edges = {} # {from_instance: [to_instances]}
|
self.edges = {} # {from_instance: [to_instances]}
|
||||||
self.protected = set()
|
self.protected = set()
|
||||||
self.model_objs = defaultdict(set)
|
self.model_objs = defaultdict(set)
|
||||||
|
|
@ -138,13 +139,21 @@ class NestedObjects(Collector):
|
||||||
self.add_edge(None, obj)
|
self.add_edge(None, obj)
|
||||||
self.model_objs[obj._meta.model].add(obj)
|
self.model_objs[obj._meta.model].add(obj)
|
||||||
try:
|
try:
|
||||||
return super(NestedObjects, self).collect(objs, source_attr=source_attr, **kwargs)
|
return super().collect(objs, source_attr=source_attr, **kwargs)
|
||||||
except ProtectedError as e:
|
except ProtectedError as e:
|
||||||
self.protected.update(e.protected_objects)
|
self.protected.update(e.protected_objects)
|
||||||
|
|
||||||
def related_objects(self, related, objs):
|
def related_objects(self, *args):
|
||||||
qs = super(NestedObjects, self).related_objects(related, objs)
|
# Django >= 3.1
|
||||||
return qs.select_related(related.field.name)
|
if len(args) == 3:
|
||||||
|
related_model, related_fields, objs = args
|
||||||
|
qs = super().related_objects(related_model, related_fields, objs)
|
||||||
|
return qs.select_related(*[related_field.name for related_field in related_fields])
|
||||||
|
# Django < 3.1
|
||||||
|
elif len(args) == 2:
|
||||||
|
related, objs = args
|
||||||
|
qs = super(NestedObjects, self).related_objects(related, objs)
|
||||||
|
return qs.select_related(related.field.name)
|
||||||
|
|
||||||
def _nested(self, obj, seen, format_callback):
|
def _nested(self, obj, seen, format_callback):
|
||||||
if obj in seen:
|
if obj in seen:
|
||||||
|
|
@ -191,7 +200,7 @@ def quote(s):
|
||||||
|
|
||||||
https://github.com/django/django/blob/1.9.6/django/contrib/admin/utils.py#L66-L73
|
https://github.com/django/django/blob/1.9.6/django/contrib/admin/utils.py#L66-L73
|
||||||
"""
|
"""
|
||||||
if not isinstance(s, six.string_types):
|
if not isinstance(s, str):
|
||||||
return s
|
return s
|
||||||
res = list(s)
|
res = list(s)
|
||||||
for i in range(len(res)):
|
for i in range(len(res)):
|
||||||
|
|
@ -202,7 +211,4 @@ def quote(s):
|
||||||
|
|
||||||
|
|
||||||
def type_str(text):
|
def type_str(text):
|
||||||
if six.PY2:
|
return force_str(text)
|
||||||
return force_bytes(text)
|
|
||||||
else:
|
|
||||||
return force_text(text)
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,13 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from django.contrib.auth.views import redirect_to_login
|
from django.contrib.auth.views import redirect_to_login
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.core.urlresolvers import reverse, reverse_lazy
|
|
||||||
from django.forms.models import modelform_factory
|
from django.forms.models import modelform_factory
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.utils.encoding import force_text
|
from django.urls import reverse, reverse_lazy
|
||||||
|
from django.utils.encoding import force_str
|
||||||
from django.utils.text import get_text_list
|
from django.utils.text import get_text_list
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
# braces 1.3 views exported AccessMixin
|
# braces 1.3 views exported AccessMixin
|
||||||
# in braces 1.4 this was moved views._access and not exported in views
|
# in braces 1.4 this was moved views._access and not exported in views
|
||||||
|
|
@ -34,7 +31,7 @@ class PermissionMixin(AccessMixin):
|
||||||
self.permissions = [
|
self.permissions = [
|
||||||
permission_class()
|
permission_class()
|
||||||
for permission_class in self.permission_classes]
|
for permission_class in self.permission_classes]
|
||||||
super(PermissionMixin, self).__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
def has_permission(self, obj=None):
|
def has_permission(self, obj=None):
|
||||||
'''
|
'''
|
||||||
|
|
@ -58,10 +55,10 @@ class PermissionMixin(AccessMixin):
|
||||||
request.get_full_path(),
|
request.get_full_path(),
|
||||||
self.get_login_url(),
|
self.get_login_url(),
|
||||||
self.get_redirect_field_name())
|
self.get_redirect_field_name())
|
||||||
return super(PermissionMixin, self).dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(PermissionMixin, self).get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
permission_checker = permissions.TemplatePermissionChecker(
|
permission_checker = permissions.TemplatePermissionChecker(
|
||||||
self.request, self.model_admin)
|
self.request, self.model_admin)
|
||||||
context.update({
|
context.update({
|
||||||
|
|
@ -84,6 +81,9 @@ class Admin2Mixin(PermissionMixin):
|
||||||
return [os.path.join(
|
return [os.path.join(
|
||||||
settings.ADMIN2_THEME_DIRECTORY, self.default_template_name)]
|
settings.ADMIN2_THEME_DIRECTORY, self.default_template_name)]
|
||||||
|
|
||||||
|
def get_templates(self):
|
||||||
|
return os.path.join(settings.ADMIN2_THEME_DIRECTORY, self.default_template_name)
|
||||||
|
|
||||||
def get_model(self):
|
def get_model(self):
|
||||||
return self.model
|
return self.model
|
||||||
|
|
||||||
|
|
@ -111,14 +111,14 @@ class Admin2Mixin(PermissionMixin):
|
||||||
}
|
}
|
||||||
return self.login_view().dispatch(request, extra_context=extra, *args, **kwargs)
|
return self.login_view().dispatch(request, extra_context=extra, *args, **kwargs)
|
||||||
|
|
||||||
return super(Admin2Mixin, self).dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class Admin2ModelMixin(Admin2Mixin):
|
class Admin2ModelMixin(Admin2Mixin):
|
||||||
model_admin = None
|
model_admin = None
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(Admin2ModelMixin, self).get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
model = self.get_model()
|
model = self.get_model()
|
||||||
model_meta = model_options(model)
|
model_meta = model_options(model)
|
||||||
app_verbose_names = self.model_admin.admin.app_verbose_names
|
app_verbose_names = self.model_admin.admin.app_verbose_names
|
||||||
|
|
@ -142,7 +142,7 @@ class Admin2ModelMixin(Admin2Mixin):
|
||||||
return modelform_factory(self.get_model(), fields='__all__')
|
return modelform_factory(self.get_model(), fields='__all__')
|
||||||
|
|
||||||
|
|
||||||
class Admin2ModelFormMixin(object):
|
class Admin2ModelFormMixin:
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
if '_continue' in self.request.POST:
|
if '_continue' in self.request.POST:
|
||||||
view_name = admin2_urlname(self, 'update')
|
view_name = admin2_urlname(self, 'update')
|
||||||
|
|
@ -167,19 +167,19 @@ class Admin2ModelFormMixin(object):
|
||||||
for added_object in formset.new_objects:
|
for added_object in formset.new_objects:
|
||||||
change_message.append(
|
change_message.append(
|
||||||
_('Added {0} "{1}".'.format(
|
_('Added {0} "{1}".'.format(
|
||||||
force_text(added_object._meta.verbose_name),
|
force_str(added_object._meta.verbose_name),
|
||||||
force_text(added_object))))
|
force_str(added_object))))
|
||||||
for changed_object, changed_fields in formset.changed_objects:
|
for changed_object, changed_fields in formset.changed_objects:
|
||||||
change_message.append(
|
change_message.append(
|
||||||
_('Changed {0} for {1} "{2}".'.format(
|
_('Changed {0} for {1} "{2}".'.format(
|
||||||
get_text_list(changed_fields, _('and')),
|
get_text_list(changed_fields, _('and')),
|
||||||
force_text(changed_object._meta.verbose_name),
|
force_str(changed_object._meta.verbose_name),
|
||||||
force_text(changed_object))))
|
force_str(changed_object))))
|
||||||
for deleted_object in formset.deleted_objects:
|
for deleted_object in formset.deleted_objects:
|
||||||
change_message.append(
|
change_message.append(
|
||||||
_('Deleted {0} "{1}".'.format(
|
_('Deleted {0} "{1}".'.format(
|
||||||
force_text(deleted_object._meta.verbose_name),
|
force_str(deleted_object._meta.verbose_name),
|
||||||
force_text(deleted_object))))
|
force_str(deleted_object))))
|
||||||
|
|
||||||
change_message = ' '.join(change_message)
|
change_message = ' '.join(change_message)
|
||||||
return change_message or _('No fields changed.')
|
return change_message or _('No fields changed.')
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,24 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
|
||||||
|
|
||||||
import operator
|
import operator
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
|
|
||||||
import extra_views
|
import extra_views
|
||||||
|
from django.core.exceptions import FieldDoesNotExist
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth import update_session_auth_hash
|
from django.contrib.auth import (logout as auth_logout,
|
||||||
|
update_session_auth_hash)
|
||||||
from django.contrib.auth.forms import (PasswordChangeForm,
|
from django.contrib.auth.forms import (PasswordChangeForm,
|
||||||
AdminPasswordChangeForm)
|
AdminPasswordChangeForm)
|
||||||
from django.contrib.auth.views import (logout as auth_logout,
|
from django.contrib.auth.views import LoginView as DjangoLoginView
|
||||||
login as auth_login)
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.urlresolvers import reverse, reverse_lazy
|
|
||||||
from django.db import models, router
|
from django.db import models, router
|
||||||
from django.db.models.fields import FieldDoesNotExist
|
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_str
|
||||||
from django.utils.text import capfirst
|
from django.utils.text import capfirst
|
||||||
from django.utils.translation import ugettext_lazy
|
from django.utils.translation import gettext_lazy
|
||||||
|
from django.urls import reverse, reverse_lazy
|
||||||
|
|
||||||
from django.views import generic
|
from django.views import generic
|
||||||
|
|
||||||
from . import permissions, utils
|
from . import permissions, utils
|
||||||
|
|
@ -30,7 +28,7 @@ from .models import LogEntry
|
||||||
from .viewmixins import Admin2Mixin, Admin2ModelMixin, Admin2ModelFormMixin
|
from .viewmixins import Admin2Mixin, Admin2ModelMixin, Admin2ModelFormMixin
|
||||||
|
|
||||||
|
|
||||||
class AdminView(object):
|
class AdminView:
|
||||||
|
|
||||||
def __init__(self, url, view, name=None):
|
def __init__(self, url, view, name=None):
|
||||||
self.url = url
|
self.url = url
|
||||||
|
|
@ -63,7 +61,7 @@ class IndexView(Admin2Mixin, generic.TemplateView):
|
||||||
app_verbose_names = None
|
app_verbose_names = None
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
data = super(IndexView, self).get_context_data(**kwargs)
|
data = super().get_context_data(**kwargs)
|
||||||
data.update({
|
data.update({
|
||||||
'apps': self.apps,
|
'apps': self.apps,
|
||||||
'app_verbose_names': self.app_verbose_names,
|
'app_verbose_names': self.app_verbose_names,
|
||||||
|
|
@ -90,7 +88,7 @@ class AppIndexView(Admin2Mixin, generic.TemplateView):
|
||||||
app_verbose_names = None
|
app_verbose_names = None
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
data = super(AppIndexView, self).get_context_data(**kwargs)
|
data = super().get_context_data(**kwargs)
|
||||||
app_label = self.kwargs['app_label']
|
app_label = self.kwargs['app_label']
|
||||||
registry = self.apps[app_label]
|
registry = self.apps[app_label]
|
||||||
data.update({
|
data.update({
|
||||||
|
|
@ -115,6 +113,7 @@ class ModelListView(Admin2ModelMixin, generic.ListView):
|
||||||
app name.
|
app name.
|
||||||
"""
|
"""
|
||||||
default_template_name = "model_list.html"
|
default_template_name = "model_list.html"
|
||||||
|
paginate_by = 10
|
||||||
permission_classes = (
|
permission_classes = (
|
||||||
permissions.IsStaffPermission,
|
permissions.IsStaffPermission,
|
||||||
permissions.ModelViewPermission)
|
permissions.ModelViewPermission)
|
||||||
|
|
@ -173,7 +172,7 @@ class ModelListView(Admin2ModelMixin, generic.ListView):
|
||||||
return queryset, use_distinct
|
return queryset, use_distinct
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
queryset = super(ModelListView, self).get_queryset()
|
queryset = super().get_queryset()
|
||||||
search_term = self.request.GET.get('q', None)
|
search_term = self.request.GET.get('q', None)
|
||||||
search_use_distinct = False
|
search_use_distinct = False
|
||||||
if self.model_admin.search_fields and search_term:
|
if self.model_admin.search_fields and search_term:
|
||||||
|
|
@ -247,7 +246,7 @@ class ModelListView(Admin2ModelMixin, generic.ListView):
|
||||||
return self._date_filter
|
return self._date_filter
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(ModelListView, self).get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context['model'] = self.get_model()
|
context['model'] = self.get_model()
|
||||||
context['actions'] = self.get_actions().values()
|
context['actions'] = self.get_actions().values()
|
||||||
context['search_fields'] = self.get_search_fields()
|
context['search_fields'] = self.get_search_fields()
|
||||||
|
|
@ -283,7 +282,7 @@ class ModelListView(Admin2ModelMixin, generic.ListView):
|
||||||
elif year:
|
elif year:
|
||||||
context["previous_date"] = {
|
context["previous_date"] = {
|
||||||
"link": "?",
|
"link": "?",
|
||||||
"text": ugettext_lazy("‹ All dates"),
|
"text": gettext_lazy("‹ All dates"),
|
||||||
}
|
}
|
||||||
|
|
||||||
context["dates"] = self._format_months(self.get_queryset())
|
context["dates"] = self._format_months(self.get_queryset())
|
||||||
|
|
@ -387,7 +386,7 @@ class ModelEditFormView(Admin2ModelMixin, Admin2ModelFormMixin,
|
||||||
context = super(ModelEditFormView, self).get_context_data(**kwargs)
|
context = super(ModelEditFormView, self).get_context_data(**kwargs)
|
||||||
context['model'] = self.get_model()
|
context['model'] = self.get_model()
|
||||||
context['action'] = "Change"
|
context['action'] = "Change"
|
||||||
context['action_name'] = ugettext_lazy("Change")
|
context['action_name'] = gettext_lazy("Change")
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def forms_valid(self, form, inlines):
|
def forms_valid(self, form, inlines):
|
||||||
|
|
@ -420,14 +419,14 @@ class ModelAddFormView(Admin2ModelMixin, Admin2ModelFormMixin,
|
||||||
permissions.ModelAddPermission)
|
permissions.ModelAddPermission)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(ModelAddFormView, self).get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context['model'] = self.get_model()
|
context['model'] = self.get_model()
|
||||||
context['action'] = "Add"
|
context['action'] = "Add"
|
||||||
context['action_name'] = ugettext_lazy("Add")
|
context['action_name'] = gettext_lazy("Add")
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def forms_valid(self, form, inlines):
|
def forms_valid(self, form, inlines):
|
||||||
response = super(ModelAddFormView, self).forms_valid(form, inlines)
|
response = super().forms_valid(form, inlines)
|
||||||
LogEntry.objects.log_action(
|
LogEntry.objects.log_action(
|
||||||
self.request.user.id,
|
self.request.user.id,
|
||||||
self.object,
|
self.object,
|
||||||
|
|
@ -456,12 +455,12 @@ class ModelDeleteView(Admin2ModelMixin, generic.DeleteView):
|
||||||
permissions.ModelDeletePermission)
|
permissions.ModelDeletePermission)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(ModelDeleteView, self).get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
def _format_callback(obj):
|
def _format_callback(obj):
|
||||||
opts = utils.model_options(obj)
|
opts = utils.model_options(obj)
|
||||||
return '%s: %s' % (force_text(capfirst(opts.verbose_name)),
|
return '%s: %s' % (force_str(capfirst(opts.verbose_name)),
|
||||||
force_text(obj))
|
force_str(obj))
|
||||||
|
|
||||||
using = router.db_for_write(self.get_object()._meta.model)
|
using = router.db_for_write(self.get_object()._meta.model)
|
||||||
collector = utils.NestedObjects(using=using)
|
collector = utils.NestedObjects(using=using)
|
||||||
|
|
@ -477,7 +476,7 @@ class ModelDeleteView(Admin2ModelMixin, generic.DeleteView):
|
||||||
self.get_object(),
|
self.get_object(),
|
||||||
LogEntry.DELETION,
|
LogEntry.DELETION,
|
||||||
'Object deleted.')
|
'Object deleted.')
|
||||||
return super(ModelDeleteView, self).delete(request, *args, **kwargs)
|
return super().delete(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class ModelHistoryView(Admin2ModelMixin, generic.ListView):
|
class ModelHistoryView(Admin2ModelMixin, generic.ListView):
|
||||||
|
|
@ -493,13 +492,14 @@ class ModelHistoryView(Admin2ModelMixin, generic.ListView):
|
||||||
app name.
|
app name.
|
||||||
"""
|
"""
|
||||||
default_template_name = "model_history.html"
|
default_template_name = "model_history.html"
|
||||||
|
paginate_by = 10
|
||||||
permission_classes = (
|
permission_classes = (
|
||||||
permissions.IsStaffPermission,
|
permissions.IsStaffPermission,
|
||||||
permissions.ModelChangePermission
|
permissions.ModelChangePermission
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(ModelHistoryView, self).get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context['model'] = self.get_model()
|
context['model'] = self.get_model()
|
||||||
context['object'] = self.get_object()
|
context['object'] = self.get_object()
|
||||||
return context
|
return context
|
||||||
|
|
@ -536,7 +536,7 @@ class PasswordChangeView(Admin2Mixin, generic.UpdateView):
|
||||||
def get_form_class(self):
|
def get_form_class(self):
|
||||||
if self.request.user == self.get_object():
|
if self.request.user == self.get_object():
|
||||||
return self.admin_form_class
|
return self.admin_form_class
|
||||||
return super(PasswordChangeView, self).get_form_class()
|
return super().get_form_class()
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
|
@ -564,10 +564,7 @@ class LoginView(Admin2Mixin, generic.TemplateView):
|
||||||
authentication_form = AdminAuthenticationForm
|
authentication_form = AdminAuthenticationForm
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
return auth_login(request,
|
return DjangoLoginView.as_view(template_name=self.get_templates(), authentication_form=self.authentication_form, *args, **kwargs)(request)
|
||||||
authentication_form=self.authentication_form,
|
|
||||||
template_name=self.get_template_names(),
|
|
||||||
*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class LogoutView(Admin2Mixin, generic.TemplateView):
|
class LogoutView(Admin2Mixin, generic.TemplateView):
|
||||||
|
|
@ -579,5 +576,6 @@ class LogoutView(Admin2Mixin, generic.TemplateView):
|
||||||
default_template_name = 'auth/logout.html'
|
default_template_name = 'auth/logout.html'
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
return auth_logout(request, template_name=self.get_template_names(),
|
auth_logout(request)
|
||||||
*args, **kwargs)
|
context = self.get_context_data(**kwargs)
|
||||||
|
return self.render_to_response(context)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Taken from https://github.com/django/django/blob/master/docs/_ext/djangodocs.py
|
# Taken from https://github.com/django/django/blob/main/docs/_ext/djangodocs.py
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from sphinx import addnodes
|
from sphinx import addnodes
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# django-admin2 documentation build configuration file, created by
|
# django-admin2 documentation build configuration file, created by
|
||||||
# sphinx-quickstart on Sat May 18 12:59:02 2013.
|
# sphinx-quickstart on Sat May 18 12:59:02 2013.
|
||||||
#
|
#
|
||||||
|
|
|
||||||
|
|
@ -109,8 +109,7 @@ Now when you go to your fork on GitHub, you will see this branch listed under
|
||||||
the "Source" tab where it says "Switch Branches". Go ahead and select your
|
the "Source" tab where it says "Switch Branches". Go ahead and select your
|
||||||
topic branch from this list, and then click the "Pull request" button.
|
topic branch from this list, and then click the "Pull request" button.
|
||||||
|
|
||||||
Your pull request should be applied to the **develop** branch of django-admin2.
|
Your pull request should be applied to the **main** branch of django-admin2.
|
||||||
Be sure to change from the default of ``master`` to ``develop``.
|
|
||||||
|
|
||||||
Next, you can add a comment about your branch. If this in response to
|
Next, you can add a comment about your branch. If this in response to
|
||||||
a submitted issue, it is good to put a link to that issue in this initial
|
a submitted issue, it is good to put a link to that issue in this initial
|
||||||
|
|
@ -132,8 +131,8 @@ Pull upstream changes into your fork regularly
|
||||||
|
|
||||||
To pull in upstream changes::
|
To pull in upstream changes::
|
||||||
|
|
||||||
git remote add upstream https://github.com/twoscoops/django-admin2.git
|
git remote add upstream https://github.com/jazzband/django-admin2.git
|
||||||
git pull upstream develop
|
git pull upstream main
|
||||||
|
|
||||||
For more info, see http://help.github.com/fork-a-repo/
|
For more info, see http://help.github.com/fork-a-repo/
|
||||||
|
|
||||||
|
|
@ -145,7 +144,7 @@ Advanced git users: Pull with rebase
|
||||||
|
|
||||||
This will pull and then reapply your work on top of the upcoming changes::
|
This will pull and then reapply your work on top of the upcoming changes::
|
||||||
|
|
||||||
git pull --rebase upstream develop
|
git pull --rebase upstream main
|
||||||
|
|
||||||
It saves you from an extra merge, keeping the history cleaner, but it's potentially dangerous because you're rewriting history. For more info, see http://gitready.com/advanced/2009/02/11/pull-with-rebase.html
|
It saves you from an extra merge, keeping the history cleaner, but it's potentially dangerous because you're rewriting history. For more info, see http://gitready.com/advanced/2009/02/11/pull-with-rebase.html
|
||||||
|
|
||||||
|
|
@ -262,8 +261,8 @@ How pull requests are checked, tested, and done
|
||||||
|
|
||||||
First we pull the code into a local branch::
|
First we pull the code into a local branch::
|
||||||
|
|
||||||
git checkout develop
|
git checkout main
|
||||||
git checkout -b <submitter-github-name>-<submitter-branch> develop
|
git checkout -b <submitter-github-name>-<submitter-branch> main
|
||||||
git pull git://github.com/<submitter-github-name>/django-admin2.git <submitter-branch> <branch-name>
|
git pull git://github.com/<submitter-github-name>/django-admin2.git <submitter-branch> <branch-name>
|
||||||
|
|
||||||
Then we run the tests::
|
Then we run the tests::
|
||||||
|
|
@ -279,11 +278,11 @@ We do the following:
|
||||||
|
|
||||||
We finish with a merge and push to GitHub::
|
We finish with a merge and push to GitHub::
|
||||||
|
|
||||||
git checkout develop
|
git checkout main
|
||||||
git merge <branch-name>
|
git merge <branch-name>
|
||||||
git push origin develop
|
git push origin main
|
||||||
|
|
||||||
.. _installation: install.html
|
.. _installation: install.html
|
||||||
.. _GitHub project: https://github.com/twoscoops/django-admin2
|
.. _GitHub project: https://github.com/jazzband/django-admin2
|
||||||
.. _issue tracker: https://github.com/twoscoops/django-admin2/issues
|
.. _issue tracker: https://github.com/jazzband/django-admin2/issues
|
||||||
.. _pydanny: http://pydanny.com
|
.. _pydanny: http://pydanny.com
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@
|
||||||
Welcome to django-admin2's documentation!
|
Welcome to django-admin2's documentation!
|
||||||
=========================================
|
=========================================
|
||||||
|
|
||||||
.. image:: https://travis-ci.org/jazzband/django-admin2.png
|
.. image:: https://github.com/jazzband/django-admin2/workflows/Test/badge.svg
|
||||||
:alt: Build Status
|
:target: https://github.com/jazzband/django-admin2/actions
|
||||||
:target: https://travis-ci.org/jazzband/django-admin2
|
:alt: GitHub Actions
|
||||||
|
|
||||||
**Warning:** This project is currently in an **alpha** state and currently not meant for real projects.
|
**Warning:** This project is currently in an **alpha** state and currently not meant for real projects.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ Marking strings for translation
|
||||||
|
|
||||||
**Python code**
|
**Python code**
|
||||||
|
|
||||||
Make sure to use ugettext or ugettext_lazy on strings that will be shown to the users,
|
Make sure to use gettext or gettext_lazy on strings that will be shown to the users,
|
||||||
with string interpolation ( "%(name_of_variable)s" instead of "%s" ) where needed.
|
with string interpolation ( "%(name_of_variable)s" instead of "%s" ) where needed.
|
||||||
|
|
||||||
Remember that all languages do not use the same word order, so try to provide flexible strings to translate !
|
Remember that all languages do not use the same word order, so try to provide flexible strings to translate !
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,6 @@ Then enter the following information (you will probably want to change the highl
|
||||||
|
|
||||||
|
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
import re
|
import re
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,23 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.utils.translation import ugettext_lazy, pgettext_lazy
|
from django.utils.translation import gettext_lazy, pgettext_lazy
|
||||||
|
|
||||||
from djadmin2 import permissions
|
from djadmin2 import permissions
|
||||||
from djadmin2.actions import BaseListAction
|
from djadmin2.actions import BaseListAction
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class CustomPublishAction(BaseListAction):
|
class CustomPublishAction(BaseListAction):
|
||||||
|
|
||||||
permission_classes = BaseListAction.permission_classes + (
|
permission_classes = BaseListAction.permission_classes + (
|
||||||
permissions.ModelChangePermission,
|
permissions.ModelChangePermission,
|
||||||
)
|
)
|
||||||
|
|
||||||
description = ugettext_lazy('Publish selected items')
|
description = gettext_lazy('Publish selected items')
|
||||||
success_message = pgettext_lazy('singular form',
|
success_message = pgettext_lazy(
|
||||||
'Successfully published %(count)s %(items)s')
|
'singular form',
|
||||||
success_message_plural = pgettext_lazy('plural form',
|
'Successfully published %(count)s %(items)s')
|
||||||
'Successfully published %(count)s %(items)s')
|
success_message_plural = pgettext_lazy(
|
||||||
|
'plural form',
|
||||||
|
'Successfully published %(count)s %(items)s')
|
||||||
|
|
||||||
default_template_name = "actions/publish_selected_items.html"
|
default_template_name = "actions/publish_selected_items.html"
|
||||||
|
|
||||||
|
|
@ -32,7 +30,7 @@ class PublishAllItemsAction(BaseListAction):
|
||||||
permissions.ModelChangePermission,
|
permissions.ModelChangePermission,
|
||||||
)
|
)
|
||||||
|
|
||||||
description = ugettext_lazy('Publish all items')
|
description = gettext_lazy('Publish all items')
|
||||||
success_message = pgettext_lazy(
|
success_message = pgettext_lazy(
|
||||||
'singular form',
|
'singular form',
|
||||||
'Successfully published %(count)s %(items)s',
|
'Successfully published %(count)s %(items)s',
|
||||||
|
|
@ -52,10 +50,12 @@ class PublishAllItemsAction(BaseListAction):
|
||||||
|
|
||||||
def unpublish_items(request, queryset):
|
def unpublish_items(request, queryset):
|
||||||
queryset.update(published=False)
|
queryset.update(published=False)
|
||||||
messages.add_message(request, messages.INFO, ugettext_lazy(u'Items unpublished'))
|
messages.add_message(request, messages.INFO,
|
||||||
|
gettext_lazy(u'Items unpublished'))
|
||||||
|
|
||||||
|
|
||||||
# Translators : action description
|
# Translators : action description
|
||||||
unpublish_items.description = ugettext_lazy('Unpublish selected items')
|
unpublish_items.description = gettext_lazy('Unpublish selected items')
|
||||||
|
|
||||||
|
|
||||||
def unpublish_all_items(request, queryset):
|
def unpublish_all_items(request, queryset):
|
||||||
|
|
@ -63,8 +63,9 @@ def unpublish_all_items(request, queryset):
|
||||||
messages.add_message(
|
messages.add_message(
|
||||||
request,
|
request,
|
||||||
messages.INFO,
|
messages.INFO,
|
||||||
ugettext_lazy('Items unpublished'),
|
gettext_lazy('Items unpublished'),
|
||||||
)
|
)
|
||||||
|
|
||||||
unpublish_all_items.description = ugettext_lazy('Unpublish all items')
|
|
||||||
|
unpublish_all_items.description = gettext_lazy('Unpublish all items')
|
||||||
unpublish_all_items.only_selected = False
|
unpublish_all_items.only_selected = False
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from .models import Post, Comment
|
from .models import Post, Comment
|
||||||
|
|
@ -16,5 +13,6 @@ class PostAdmin(admin.ModelAdmin):
|
||||||
list_filter = ['published', 'title']
|
list_filter = ['published', 'title']
|
||||||
date_hierarchy = "published_date"
|
date_hierarchy = "published_date"
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Post, PostAdmin)
|
admin.site.register(Post, PostAdmin)
|
||||||
admin.site.register(Comment)
|
admin.site.register(Comment)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy
|
from django.utils.translation import gettext_lazy
|
||||||
|
|
||||||
from djadmin2 import renderers
|
from djadmin2 import renderers
|
||||||
from djadmin2.actions import DeleteSelectedAction
|
from djadmin2.actions import DeleteSelectedAction
|
||||||
|
|
@ -32,7 +30,7 @@ class PostAdmin(ModelAdmin2):
|
||||||
}
|
}
|
||||||
save_on_top = True
|
save_on_top = True
|
||||||
date_hierarchy = "published_date"
|
date_hierarchy = "published_date"
|
||||||
ordering = ["-published_date", "title",]
|
ordering = ["-published_date", "title", ]
|
||||||
|
|
||||||
|
|
||||||
class CommentAdmin(ModelAdmin2):
|
class CommentAdmin(ModelAdmin2):
|
||||||
|
|
@ -42,13 +40,13 @@ class CommentAdmin(ModelAdmin2):
|
||||||
actions_on_bottom = True
|
actions_on_bottom = True
|
||||||
actions_selection_counter = False
|
actions_selection_counter = False
|
||||||
|
|
||||||
|
|
||||||
# Register the blog app with a verbose name
|
# Register the blog app with a verbose name
|
||||||
djadmin2_site.register_app_verbose_name(
|
djadmin2_site.register_app_verbose_name(
|
||||||
'blog',
|
'blog',
|
||||||
ugettext_lazy('My Blog')
|
gettext_lazy('My Blog')
|
||||||
)
|
)
|
||||||
|
|
||||||
# Register each model with the admin
|
# Register each model with the admin
|
||||||
djadmin2_site.register(Post, PostAdmin)
|
djadmin2_site.register(Post, PostAdmin)
|
||||||
djadmin2_site.register(Comment, CommentAdmin)
|
djadmin2_site.register(Comment, CommentAdmin)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
@ -27,7 +24,8 @@ class Migration(migrations.Migration):
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
|
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
|
||||||
('num', models.PositiveSmallIntegerField()),
|
('num', models.PositiveSmallIntegerField()),
|
||||||
('parent', models.ForeignKey(to='blog.Count', null=True)),
|
('parent', models.ForeignKey(
|
||||||
|
to='blog.Count', null=True, on_delete=models.CASCADE)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
|
|
@ -49,7 +47,9 @@ class Migration(migrations.Migration):
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
|
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
|
||||||
('name', models.CharField(max_length=255)),
|
('name', models.CharField(max_length=255)),
|
||||||
('event', models.OneToOneField(to='blog.Event')),
|
('event', models.OneToOneField(
|
||||||
|
to='blog.Event',
|
||||||
|
on_delete=models.CASCADE)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'awesome guest',
|
'verbose_name': 'awesome guest',
|
||||||
|
|
@ -59,7 +59,10 @@ class Migration(migrations.Migration):
|
||||||
name='Location',
|
name='Location',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
|
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
|
||||||
('event', models.OneToOneField(verbose_name='awesome event', to='blog.Event')),
|
('event', models.OneToOneField(
|
||||||
|
verbose_name='awesome event',
|
||||||
|
to='blog.Event',
|
||||||
|
on_delete=models.CASCADE)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
|
|
@ -79,6 +82,10 @@ class Migration(migrations.Migration):
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='comment',
|
model_name='comment',
|
||||||
name='post',
|
name='post',
|
||||||
field=models.ForeignKey(related_name='comments', verbose_name='post', to='blog.Post'),
|
field=models.ForeignKey(
|
||||||
|
related_name='comments',
|
||||||
|
verbose_name='post',
|
||||||
|
to='blog.Post',
|
||||||
|
on_delete=models.CASCADE),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils import six
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class Post(models.Model):
|
class Post(models.Model):
|
||||||
title = models.CharField(max_length=255, verbose_name=_('title'))
|
title = models.CharField(max_length=255, verbose_name=_('title'))
|
||||||
body = models.TextField(verbose_name=_('body'))
|
body = models.TextField(verbose_name=_('body'))
|
||||||
|
|
@ -22,10 +16,10 @@ class Post(models.Model):
|
||||||
verbose_name_plural = _('posts')
|
verbose_name_plural = _('posts')
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class Comment(models.Model):
|
class Comment(models.Model):
|
||||||
post = models.ForeignKey(
|
post = models.ForeignKey(
|
||||||
Post, verbose_name=_('post'), related_name="comments")
|
Post, verbose_name=_('post'), related_name="comments",
|
||||||
|
on_delete=models.CASCADE)
|
||||||
body = models.TextField(verbose_name=_('body'))
|
body = models.TextField(verbose_name=_('body'))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|
@ -38,13 +32,12 @@ class Comment(models.Model):
|
||||||
|
|
||||||
# Models needed for testing NestedObjects
|
# Models needed for testing NestedObjects
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class Count(models.Model):
|
class Count(models.Model):
|
||||||
num = models.PositiveSmallIntegerField()
|
num = models.PositiveSmallIntegerField()
|
||||||
parent = models.ForeignKey('self', null=True)
|
parent = models.ForeignKey('self', null=True, on_delete=models.CASCADE)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return six.text_type(self.num)
|
return str(self.num)
|
||||||
|
|
||||||
|
|
||||||
class Event(models.Model):
|
class Event(models.Model):
|
||||||
|
|
@ -52,11 +45,14 @@ class Event(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class Location(models.Model):
|
class Location(models.Model):
|
||||||
event = models.OneToOneField(Event, verbose_name='awesome event')
|
event = models.OneToOneField(
|
||||||
|
Event, verbose_name='awesome event',
|
||||||
|
on_delete=models.CASCADE
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Guest(models.Model):
|
class Guest(models.Model):
|
||||||
event = models.OneToOneField(Event)
|
event = models.OneToOneField(Event, on_delete=models.CASCADE)
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{% load i18n %}{% load staticfiles %}<!DOCTYPE html>
|
{% load i18n %}{% load static %}<!DOCTYPE html>
|
||||||
<html lang="{{ request.LANGUAGE_CODE }}">
|
<html lang="{{ request.LANGUAGE_CODE }}">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load staticfiles %}
|
{% load static %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load i18n staticfiles %}
|
{% load i18n static %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load staticfiles i18n %}
|
{% load static i18n %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from django.contrib.auth.models import AnonymousUser, User
|
from django.contrib.auth.models import AnonymousUser, User
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
from django.utils.encoding import force_text
|
from django.urls import reverse
|
||||||
|
from django.utils.encoding import force_str
|
||||||
|
|
||||||
from djadmin2 import apiviews
|
from djadmin2 import apiviews
|
||||||
from djadmin2.site import djadmin2_site
|
from djadmin2.site import djadmin2_site
|
||||||
|
|
@ -32,14 +30,16 @@ class IndexAPIViewTest(APITestCase):
|
||||||
def test_response_ok(self):
|
def test_response_ok(self):
|
||||||
request = self.factory.get(reverse('admin2:api_index'))
|
request = self.factory.get(reverse('admin2:api_index'))
|
||||||
request.user = self.user
|
request.user = self.user
|
||||||
view = apiviews.IndexAPIView.as_view(**djadmin2_site.get_api_index_kwargs())
|
view = apiviews.IndexAPIView.as_view(
|
||||||
|
**djadmin2_site.get_api_index_kwargs())
|
||||||
response = view(request)
|
response = view(request)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
def test_view_permission(self):
|
def test_view_permission(self):
|
||||||
request = self.factory.get(reverse('admin2:api_index'))
|
request = self.factory.get(reverse('admin2:api_index'))
|
||||||
request.user = AnonymousUser()
|
request.user = AnonymousUser()
|
||||||
view = apiviews.IndexAPIView.as_view(**djadmin2_site.get_api_index_kwargs())
|
view = apiviews.IndexAPIView.as_view(
|
||||||
|
**djadmin2_site.get_api_index_kwargs())
|
||||||
self.assertRaises(PermissionDenied, view, request)
|
self.assertRaises(PermissionDenied, view, request)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -72,7 +72,7 @@ class ListCreateAPIViewTest(APITestCase):
|
||||||
response.render()
|
response.render()
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertIn('"__unicode__":"Foo"', force_text(response.content))
|
self.assertIn('"__unicode__":"Foo"', force_str(response.content))
|
||||||
|
|
||||||
def test_pagination(self):
|
def test_pagination(self):
|
||||||
request = self.factory.get(reverse('admin2:blog_post_api_list'))
|
request = self.factory.get(reverse('admin2:blog_post_api_list'))
|
||||||
|
|
@ -84,7 +84,7 @@ class ListCreateAPIViewTest(APITestCase):
|
||||||
response.render()
|
response.render()
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
data = json.loads(force_text(response.content))
|
data = json.loads(force_str(response.content))
|
||||||
self.assertEqual(data['count'], 0)
|
self.assertEqual(data['count'], 0)
|
||||||
# next and previous fields exist, but are null because we have no
|
# next and previous fields exist, but are null because we have no
|
||||||
# content
|
# content
|
||||||
|
|
@ -99,7 +99,7 @@ class RetrieveUpdateDestroyAPIViewTest(APITestCase):
|
||||||
post = Post.objects.create(title='Foo', body='Bar')
|
post = Post.objects.create(title='Foo', body='Bar')
|
||||||
request = self.factory.get(
|
request = self.factory.get(
|
||||||
reverse('admin2:blog_post_api_detail',
|
reverse('admin2:blog_post_api_detail',
|
||||||
kwargs={'pk': post.pk}))
|
kwargs={'pk': post.pk}))
|
||||||
request.user = self.user
|
request.user = self.user
|
||||||
model_admin = self.get_model_admin(Post)
|
model_admin = self.get_model_admin(Post)
|
||||||
view = apiviews.RetrieveUpdateDestroyAPIView.as_view(
|
view = apiviews.RetrieveUpdateDestroyAPIView.as_view(
|
||||||
|
|
@ -111,7 +111,7 @@ class RetrieveUpdateDestroyAPIViewTest(APITestCase):
|
||||||
post = Post.objects.create(title='Foo', body='Bar')
|
post = Post.objects.create(title='Foo', body='Bar')
|
||||||
request = self.factory.get(
|
request = self.factory.get(
|
||||||
reverse('admin2:blog_post_api_detail',
|
reverse('admin2:blog_post_api_detail',
|
||||||
kwargs={'pk': post.pk}))
|
kwargs={'pk': post.pk}))
|
||||||
request.user = AnonymousUser()
|
request.user = AnonymousUser()
|
||||||
model_admin = self.get_model_admin(Post)
|
model_admin = self.get_model_admin(Post)
|
||||||
view = apiviews.RetrieveUpdateDestroyAPIView.as_view(
|
view = apiviews.RetrieveUpdateDestroyAPIView.as_view(
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
from django.contrib.auth.models import Group, User
|
from django.contrib.auth.models import Group, User
|
||||||
from django.core.urlresolvers import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from .test_apiviews import APITestCase
|
from .test_apiviews import APITestCase
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# vim:fenc=utf-8
|
|
||||||
|
|
||||||
import django_filters
|
import django_filters
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
from djadmin2 import filters as djadmin2_filters
|
from djadmin2 import filters as djadmin2_filters
|
||||||
from djadmin2.types import ModelAdmin2
|
from djadmin2.types import ModelAdmin2
|
||||||
|
|
@ -20,10 +17,9 @@ class ListFilterBuilderTest(TestCase):
|
||||||
class PostAdminSimple(ModelAdmin2):
|
class PostAdminSimple(ModelAdmin2):
|
||||||
list_filter = ['published', ]
|
list_filter = ['published', ]
|
||||||
|
|
||||||
|
|
||||||
class PostAdminWithFilterInstances(ModelAdmin2):
|
class PostAdminWithFilterInstances(ModelAdmin2):
|
||||||
list_filter = [
|
list_filter = [
|
||||||
django_filters.BooleanFilter(name='published'),
|
django_filters.BooleanFilter(field_name='published'),
|
||||||
]
|
]
|
||||||
|
|
||||||
class FS(django_filters.FilterSet):
|
class FS(django_filters.FilterSet):
|
||||||
|
|
@ -31,7 +27,6 @@ class ListFilterBuilderTest(TestCase):
|
||||||
model = Post
|
model = Post
|
||||||
fields = ['published']
|
fields = ['published']
|
||||||
|
|
||||||
|
|
||||||
class PostAdminWithFilterSetInst(ModelAdmin2):
|
class PostAdminWithFilterSetInst(ModelAdmin2):
|
||||||
list_filter = FS
|
list_filter = FS
|
||||||
|
|
||||||
|
|
@ -46,19 +41,11 @@ class ListFilterBuilderTest(TestCase):
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
issubclass(list_filter_inst.__class__, django_filters.FilterSet)
|
issubclass(list_filter_inst.__class__, django_filters.FilterSet)
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
|
||||||
list_filter_inst.filters['published'].widget,
|
|
||||||
djadmin2_filters.NullBooleanLinksWidget,
|
|
||||||
)
|
|
||||||
list_filter_inst = djadmin2_filters.build_list_filter(
|
list_filter_inst = djadmin2_filters.build_list_filter(
|
||||||
request,
|
request,
|
||||||
PostAdminWithFilterInstances,
|
PostAdminWithFilterInstances,
|
||||||
Post.objects.all(),
|
Post.objects.all(),
|
||||||
)
|
)
|
||||||
self.assertNotEqual(
|
|
||||||
list_filter_inst.filters['published'].widget,
|
|
||||||
djadmin2_filters.NullBooleanLinksWidget,
|
|
||||||
)
|
|
||||||
list_filter_inst = djadmin2_filters.build_list_filter(
|
list_filter_inst = djadmin2_filters.build_list_filter(
|
||||||
request,
|
request,
|
||||||
PostAdminWithFilterSetInst,
|
PostAdminWithFilterSetInst,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.forms import modelform_factory
|
from django.forms import modelform_factory
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
from blog.models import Post
|
from blog.models import Post
|
||||||
from django.contrib.auth.models import User, Permission
|
from django.contrib.auth.models import User, Permission
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.template import Template, Context
|
from django.template import Template, Context
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
from djadmin2.permissions import TemplatePermissionChecker
|
from djadmin2.permissions import TemplatePermissionChecker
|
||||||
from djadmin2.site import djadmin2_site
|
from djadmin2.site import djadmin2_site
|
||||||
|
|
@ -71,11 +71,14 @@ class TemplatePermissionTest(TestCase):
|
||||||
|
|
||||||
result = self.render('{{ permissions.has_add_permission }}', context)
|
result = self.render('{{ permissions.has_add_permission }}', context)
|
||||||
self.assertEqual(result, 'True')
|
self.assertEqual(result, 'True')
|
||||||
result = self.render('{{ permissions.blog_post.has_add_permission }}', context)
|
result = self.render(
|
||||||
|
'{{ permissions.blog_post.has_add_permission }}', context)
|
||||||
self.assertEqual(result, 'True')
|
self.assertEqual(result, 'True')
|
||||||
result = self.render('{{ permissions.blog_post.has_change_permission }}', context)
|
result = self.render(
|
||||||
|
'{{ permissions.blog_post.has_change_permission }}', context)
|
||||||
self.assertEqual(result, 'False')
|
self.assertEqual(result, 'False')
|
||||||
result = self.render('{{ permissions.auth_user.has_delete_permission }}', context)
|
result = self.render(
|
||||||
|
'{{ permissions.auth_user.has_delete_permission }}', context)
|
||||||
self.assertEqual(result, 'False')
|
self.assertEqual(result, 'False')
|
||||||
|
|
||||||
result = self.render(
|
result = self.render(
|
||||||
|
|
@ -112,7 +115,7 @@ class TemplatePermissionTest(TestCase):
|
||||||
'{% load admin2_tags %}'
|
'{% load admin2_tags %}'
|
||||||
'{{ permissions.has_add_permission }}'
|
'{{ permissions.has_add_permission }}'
|
||||||
'{% with permissions|for_admin:post_admin as permissions %}'
|
'{% with permissions|for_admin:post_admin as permissions %}'
|
||||||
'{{ permissions.has_add_permission }}'
|
'{{ permissions.has_add_permission }}'
|
||||||
'{% endwith %}',
|
'{% endwith %}',
|
||||||
context)
|
context)
|
||||||
self.assertEqual(result, 'FalseFalse')
|
self.assertEqual(result, 'FalseFalse')
|
||||||
|
|
@ -131,7 +134,7 @@ class TemplatePermissionTest(TestCase):
|
||||||
'{% load admin2_tags %}'
|
'{% load admin2_tags %}'
|
||||||
'{{ permissions.has_add_permission }}'
|
'{{ permissions.has_add_permission }}'
|
||||||
'{% with permissions|for_admin:post_admin as permissions %}'
|
'{% with permissions|for_admin:post_admin as permissions %}'
|
||||||
'{{ permissions.has_add_permission }}'
|
'{{ permissions.has_add_permission }}'
|
||||||
'{% endwith %}'
|
'{% endwith %}'
|
||||||
'{{ permissions.blog_post.has_add_permission }}',
|
'{{ permissions.blog_post.has_add_permission }}',
|
||||||
context)
|
context)
|
||||||
|
|
@ -141,7 +144,7 @@ class TemplatePermissionTest(TestCase):
|
||||||
result = self.render(
|
result = self.render(
|
||||||
'{% load admin2_tags %}'
|
'{% load admin2_tags %}'
|
||||||
'{% with permissions|for_admin:"blog_post" as permissions %}'
|
'{% with permissions|for_admin:"blog_post" as permissions %}'
|
||||||
'{{ permissions.has_add_permission }}'
|
'{{ permissions.has_add_permission }}'
|
||||||
'{% endwith %}',
|
'{% endwith %}',
|
||||||
context)
|
context)
|
||||||
self.assertEqual(result, 'True')
|
self.assertEqual(result, 'True')
|
||||||
|
|
@ -150,7 +153,7 @@ class TemplatePermissionTest(TestCase):
|
||||||
result = self.render(
|
result = self.render(
|
||||||
'{% load admin2_tags %}'
|
'{% load admin2_tags %}'
|
||||||
'{% with permissions|for_admin:"invalid_admin_name" as permissions %}'
|
'{% with permissions|for_admin:"invalid_admin_name" as permissions %}'
|
||||||
'{{ permissions.has_add_permission }}'
|
'{{ permissions.has_add_permission }}'
|
||||||
'{% endwith %}',
|
'{% endwith %}',
|
||||||
context)
|
context)
|
||||||
self.assertEqual(result, '')
|
self.assertEqual(result, '')
|
||||||
|
|
@ -186,8 +189,8 @@ class TemplatePermissionTest(TestCase):
|
||||||
# user add permission
|
# user add permission
|
||||||
'{{ permissions.has_add_permission }}'
|
'{{ permissions.has_add_permission }}'
|
||||||
'{% with permissions|for_admin:"blog_post"|for_view:"add" as post_add_perm %}'
|
'{% with permissions|for_admin:"blog_post"|for_view:"add" as post_add_perm %}'
|
||||||
# post add permission
|
# post add permission
|
||||||
'{{ post_add_perm }}'
|
'{{ post_add_perm }}'
|
||||||
'{% endwith %}',
|
'{% endwith %}',
|
||||||
context)
|
context)
|
||||||
self.assertEqual(result, 'FalseFalse')
|
self.assertEqual(result, 'FalseFalse')
|
||||||
|
|
@ -212,8 +215,8 @@ class TemplatePermissionTest(TestCase):
|
||||||
# user add permission
|
# user add permission
|
||||||
'{{ permissions.has_add_permission }}'
|
'{{ permissions.has_add_permission }}'
|
||||||
'{% with permissions|for_admin:"blog_post"|for_view:"add" as post_add_perm %}'
|
'{% with permissions|for_admin:"blog_post"|for_view:"add" as post_add_perm %}'
|
||||||
# post add permission
|
# post add permission
|
||||||
'{{ post_add_perm }}'
|
'{{ post_add_perm }}'
|
||||||
'{% endwith %}'
|
'{% endwith %}'
|
||||||
# user change permission
|
# user change permission
|
||||||
'{{ permissions|for_view:"change" }}',
|
'{{ permissions|for_view:"change" }}',
|
||||||
|
|
@ -224,13 +227,13 @@ class TemplatePermissionTest(TestCase):
|
||||||
result = self.render(
|
result = self.render(
|
||||||
'{% load admin2_tags %}'
|
'{% load admin2_tags %}'
|
||||||
'{% with permissions|for_view:"change" as user_change_perm %}'
|
'{% with permissions|for_view:"change" as user_change_perm %}'
|
||||||
'1{{ user_change_perm }}'
|
'1{{ user_change_perm }}'
|
||||||
'2{{ user_change_perm|for_view:"add" }}'
|
'2{{ user_change_perm|for_view:"add" }}'
|
||||||
# this shouldn't return True or False but '' since the
|
# this shouldn't return True or False but '' since the
|
||||||
# previously bound change view doesn't belong to the newly
|
# previously bound change view doesn't belong to the newly
|
||||||
# bound blog_post admin
|
# bound blog_post admin
|
||||||
'3{{ user_change_perm|for_admin:"blog_post" }}'
|
'3{{ user_change_perm|for_admin:"blog_post" }}'
|
||||||
'4{{ user_change_perm|for_admin:"blog_post"|for_view:"add" }}'
|
'4{{ user_change_perm|for_admin:"blog_post"|for_view:"add" }}'
|
||||||
'{% endwith %}',
|
'{% endwith %}',
|
||||||
context)
|
context)
|
||||||
self.assertEqual(result, '1True2False34True')
|
self.assertEqual(result, '1True2False34True')
|
||||||
|
|
@ -282,7 +285,7 @@ class TemplatePermissionTest(TestCase):
|
||||||
'{% load admin2_tags %}'
|
'{% load admin2_tags %}'
|
||||||
'{{ permissions.has_add_permission }}'
|
'{{ permissions.has_add_permission }}'
|
||||||
'{% with permissions|for_object:post as permissions %}'
|
'{% with permissions|for_object:post as permissions %}'
|
||||||
'{{ permissions.has_add_permission }}'
|
'{{ permissions.has_add_permission }}'
|
||||||
'{% endwith %}',
|
'{% endwith %}',
|
||||||
context)
|
context)
|
||||||
self.assertEqual(result, 'TrueFalse')
|
self.assertEqual(result, 'TrueFalse')
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,10 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.contrib.auth.models import Group
|
from django.contrib.auth.models import Group
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.test import TestCase, Client
|
from django.test import TestCase, Client
|
||||||
from django.utils.encoding import force_text
|
from django.urls import reverse
|
||||||
|
from django.utils.encoding import force_str
|
||||||
|
|
||||||
from ..models import Post, Comment
|
from ..models import Post, Comment
|
||||||
|
|
||||||
|
|
@ -140,7 +137,7 @@ class PostListTest(BaseIntegrationTest):
|
||||||
def test_actions_displayed(self):
|
def test_actions_displayed(self):
|
||||||
response = self.client.get(reverse("admin2:blog_post_index"))
|
response = self.client.get(reverse("admin2:blog_post_index"))
|
||||||
self.assertInHTML(
|
self.assertInHTML(
|
||||||
'<a tabindex="-1" href="#" data-name="action" data-value="DeleteSelectedAction">Delete selected items</a>', force_text(response.content))
|
'<a tabindex="-1" href="#" data-name="action" data-value="DeleteSelectedAction">Delete selected items</a>', force_str(response.content))
|
||||||
|
|
||||||
def test_actions_displayed_twice(self):
|
def test_actions_displayed_twice(self):
|
||||||
# If actions_on_top and actions_on_bottom are both set
|
# If actions_on_top and actions_on_bottom are both set
|
||||||
|
|
@ -155,7 +152,7 @@ class PostListTest(BaseIntegrationTest):
|
||||||
response = self.client.post(reverse("admin2:blog_post_index"), params)
|
response = self.client.post(reverse("admin2:blog_post_index"), params)
|
||||||
# caution : uses pluralization
|
# caution : uses pluralization
|
||||||
self.assertInHTML(
|
self.assertInHTML(
|
||||||
'<p>Are you sure you want to delete the selected post? The following item will be deleted:</p>', force_text(response.content))
|
'<p>Are you sure you want to delete the selected post? The following item will be deleted:</p>', force_str(response.content))
|
||||||
|
|
||||||
def test_delete_selected_post_confirmation(self):
|
def test_delete_selected_post_confirmation(self):
|
||||||
post = Post.objects.create(title="A Post Title", body="body")
|
post = Post.objects.create(title="A Post Title", body="body")
|
||||||
|
|
@ -331,7 +328,7 @@ class PostListTestCustomAction(BaseIntegrationTest):
|
||||||
def test_publish_action_displayed_in_list(self):
|
def test_publish_action_displayed_in_list(self):
|
||||||
response = self.client.get(reverse("admin2:blog_post_index"))
|
response = self.client.get(reverse("admin2:blog_post_index"))
|
||||||
self.assertInHTML(
|
self.assertInHTML(
|
||||||
'<a tabindex="-1" href="#" data-name="action" data-value="CustomPublishAction">Publish selected items</a>', force_text(response.content))
|
'<a tabindex="-1" href="#" data-name="action" data-value="CustomPublishAction">Publish selected items</a>', force_str(response.content))
|
||||||
|
|
||||||
def test_publish_selected_items(self):
|
def test_publish_selected_items(self):
|
||||||
post = Post.objects.create(title="A Post Title",
|
post = Post.objects.create(title="A Post Title",
|
||||||
|
|
@ -350,7 +347,7 @@ class PostListTestCustomAction(BaseIntegrationTest):
|
||||||
def test_unpublish_action_displayed_in_list(self):
|
def test_unpublish_action_displayed_in_list(self):
|
||||||
response = self.client.get(reverse("admin2:blog_post_index"))
|
response = self.client.get(reverse("admin2:blog_post_index"))
|
||||||
self.assertInHTML(
|
self.assertInHTML(
|
||||||
'<a tabindex="-1" href="#" data-name="action" data-value="unpublish_items">Unpublish selected items</a>', force_text(response.content))
|
'<a tabindex="-1" href="#" data-name="action" data-value="unpublish_items">Unpublish selected items</a>', force_str(response.content))
|
||||||
|
|
||||||
def test_unpublish_selected_items(self):
|
def test_unpublish_selected_items(self):
|
||||||
post = Post.objects.create(title="A Post Title",
|
post = Post.objects.create(title="A Post Title",
|
||||||
|
|
@ -380,7 +377,7 @@ class PostCreateViewTest(BaseIntegrationTest):
|
||||||
def test_view_ok(self):
|
def test_view_ok(self):
|
||||||
response = self.client.get(reverse("admin2:blog_post_create"))
|
response = self.client.get(reverse("admin2:blog_post_create"))
|
||||||
self.assertNotIn(
|
self.assertNotIn(
|
||||||
'''enctype="multipart/form-data"''', force_text(response.content))
|
'''enctype="multipart/form-data"''', force_str(response.content))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
def test_create_post(self):
|
def test_create_post(self):
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
#from django.shortcuts import render
|
|
||||||
from django.views.generic import ListView, DetailView
|
from django.views.generic import ListView, DetailView
|
||||||
|
|
||||||
from .models import Post
|
from .models import Post
|
||||||
|
|
@ -7,6 +6,7 @@ from .models import Post
|
||||||
class BlogListView(ListView):
|
class BlogListView(ListView):
|
||||||
model = Post
|
model = Post
|
||||||
template_name = 'blog_list.html'
|
template_name = 'blog_list.html'
|
||||||
|
paginate_by = 10
|
||||||
|
|
||||||
|
|
||||||
class BlogDetailView(DetailView):
|
class BlogDetailView(DetailView):
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -15,6 +15,8 @@ import os
|
||||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
os.environ['DJANGO_SETTINGS_MODULE'] = 'example.settings'
|
||||||
|
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
# Quick-start development settings - unsuitable for production
|
||||||
# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/
|
# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/
|
||||||
|
|
@ -40,6 +42,7 @@ INSTALLED_APPS = [
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
# Uncomment the next line to enable admin documentation:
|
# Uncomment the next line to enable admin documentation:
|
||||||
# 'django.contrib.admindocs',
|
# 'django.contrib.admindocs',
|
||||||
|
'django_filters',
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
'djadmin2',
|
'djadmin2',
|
||||||
'djadmin2.tests',
|
'djadmin2.tests',
|
||||||
|
|
@ -49,14 +52,13 @@ INSTALLED_APPS = [
|
||||||
'polls'
|
'polls'
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE_CLASSES = [
|
MIDDLEWARE = [
|
||||||
'django.middleware.security.SecurityMiddleware',
|
'django.middleware.security.SecurityMiddleware',
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
'django.middleware.locale.LocaleMiddleware',
|
'django.middleware.locale.LocaleMiddleware',
|
||||||
'django.middleware.common.CommonMiddleware',
|
'django.middleware.common.CommonMiddleware',
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
|
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
]
|
]
|
||||||
|
|
@ -66,7 +68,7 @@ ROOT_URLCONF = 'example.urls'
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
'DIRS': [],
|
'DIRS': [os.path.join(BASE_DIR, 'templates')],
|
||||||
'APP_DIRS': True,
|
'APP_DIRS': True,
|
||||||
'OPTIONS': {
|
'OPTIONS': {
|
||||||
'context_processors': [
|
'context_processors': [
|
||||||
|
|
@ -145,6 +147,13 @@ MEDIA_URL = "/media/"
|
||||||
|
|
||||||
REST_FRAMEWORK = {
|
REST_FRAMEWORK = {
|
||||||
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
|
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
|
||||||
'PAGE_SIZE': 10
|
'PAGE_SIZE': 10,
|
||||||
|
|
||||||
|
'DEFAULT_PERMISSION_CLASSES': [
|
||||||
|
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
|
||||||
|
],
|
||||||
|
'DEFAULT_FILTER_BACKENDS': (
|
||||||
|
'django_filters.rest_framework.DjangoFilterBackend',)
|
||||||
}
|
}
|
||||||
ADMIN2_THEME_DIRECTORY = "djadmin2theme_bootstrap3"
|
|
||||||
|
ADMIN2_THEME_DIRECTORY = "djadmin2theme_bootstrap3"
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,26 @@
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from blog.views import BlogListView, BlogDetailView
|
from blog.views import BlogListView, BlogDetailView
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls import include, url
|
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from django.urls import re_path
|
||||||
|
|
||||||
from djadmin2.site import djadmin2_site
|
from djadmin2.site import djadmin2_site
|
||||||
|
|
||||||
admin.autodiscover()
|
|
||||||
djadmin2_site.autodiscover()
|
djadmin2_site.autodiscover()
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^admin2/', include(djadmin2_site.urls)),
|
re_path(r"^admin2/", djadmin2_site.urls),
|
||||||
url(r'^admin/', include(admin.site.urls)),
|
re_path(r"^admin/", admin.site.urls),
|
||||||
url(r'^blog/', BlogListView.as_view(template_name="blog/blog_list.html"), name='blog_list'),
|
re_path(
|
||||||
url(r'^blog/detail(?P<pk>\d+)/$', BlogDetailView.as_view(template_name="blog/blog_detail.html"), name='blog_detail'),
|
r"^blog/",
|
||||||
url(r'^$', BlogListView.as_view(template_name="blog/home.html"), name='home'),
|
BlogListView.as_view(template_name="blog/blog_list.html"),
|
||||||
|
name="blog_list",
|
||||||
|
),
|
||||||
|
re_path(
|
||||||
|
r"^blog/detail(?P<pk>\d+)/$",
|
||||||
|
BlogDetailView.as_view(template_name="blog/blog_detail.html"),
|
||||||
|
name="blog_detail",
|
||||||
|
),
|
||||||
|
re_path(r"^$", BlogListView.as_view(template_name="blog/home.html"), name="home"),
|
||||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from .models import CaptionedFile, UncaptionedFile
|
from .models import CaptionedFile, UncaptionedFile
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
|
||||||
|
|
||||||
from djadmin2.site import djadmin2_site
|
from djadmin2.site import djadmin2_site
|
||||||
from .models import CaptionedFile, UncaptionedFile
|
from .models import CaptionedFile, UncaptionedFile
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class CaptionedFile(models.Model):
|
class CaptionedFile(models.Model):
|
||||||
caption = models.CharField(max_length=200, verbose_name=_('caption'))
|
caption = models.CharField(max_length=200, verbose_name=_('caption'))
|
||||||
publication = models.FileField(upload_to='captioned-files', verbose_name=_('Uploaded File'))
|
publication = models.FileField(upload_to='captioned-files', verbose_name=_('Uploaded File'))
|
||||||
|
|
@ -19,7 +14,6 @@ class CaptionedFile(models.Model):
|
||||||
verbose_name_plural = _('Captioned Files')
|
verbose_name_plural = _('Captioned Files')
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class UncaptionedFile(models.Model):
|
class UncaptionedFile(models.Model):
|
||||||
publication = models.FileField(upload_to='uncaptioned-files', verbose_name=_('Uploaded File'))
|
publication = models.FileField(upload_to='uncaptioned-files', verbose_name=_('Uploaded File'))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
from os import path
|
from os import path
|
||||||
|
|
||||||
|
from django.contrib.auth.models import User
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
from django.urls import reverse
|
||||||
from files.models import CaptionedFile
|
from files.models import CaptionedFile
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
from os import path
|
from os import path
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.core.urlresolvers import reverse
|
from django.urls import reverse
|
||||||
from django.test import TestCase, Client
|
from django.test import TestCase, Client
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_str
|
||||||
|
|
||||||
from ..models import CaptionedFile
|
from ..models import CaptionedFile
|
||||||
|
|
||||||
|
|
@ -45,7 +45,7 @@ class CaptionedFileListTest(BaseIntegrationTest):
|
||||||
def test_actions_displayed(self):
|
def test_actions_displayed(self):
|
||||||
response = self.client.get(reverse("admin2:files_captionedfile_index"))
|
response = self.client.get(reverse("admin2:files_captionedfile_index"))
|
||||||
self.assertInHTML(
|
self.assertInHTML(
|
||||||
'<a tabindex="-1" href="#" data-name="action" data-value="DeleteSelectedAction">Delete selected items</a>', force_text(response.content))
|
'<a tabindex="-1" href="#" data-name="action" data-value="DeleteSelectedAction">Delete selected items</a>', force_str(response.content))
|
||||||
|
|
||||||
def test_delete_selected_captioned_file(self):
|
def test_delete_selected_captioned_file(self):
|
||||||
captioned_file = CaptionedFile.objects.create(
|
captioned_file = CaptionedFile.objects.create(
|
||||||
|
|
@ -55,7 +55,7 @@ class CaptionedFileListTest(BaseIntegrationTest):
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("admin2:files_captionedfile_index"), params)
|
reverse("admin2:files_captionedfile_index"), params)
|
||||||
self.assertInHTML(
|
self.assertInHTML(
|
||||||
'<p>Are you sure you want to delete the selected Captioned File? The following item will be deleted:</p>', force_text(response.content))
|
'<p>Are you sure you want to delete the selected Captioned File? The following item will be deleted:</p>', force_str(response.content))
|
||||||
|
|
||||||
def test_delete_selected_captioned_file_confirmation(self):
|
def test_delete_selected_captioned_file_confirmation(self):
|
||||||
captioned_file = CaptionedFile.objects.create(
|
captioned_file = CaptionedFile.objects.create(
|
||||||
|
|
@ -93,7 +93,7 @@ class CaptionedFileCreateViewTest(BaseIntegrationTest):
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
reverse("admin2:files_captionedfile_create"))
|
reverse("admin2:files_captionedfile_create"))
|
||||||
self.assertIn(
|
self.assertIn(
|
||||||
'enctype="multipart/form-data"', force_text(response.content))
|
'enctype="multipart/form-data"', force_str(response.content))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
def test_create_captioned_file(self):
|
def test_create_captioned_file(self):
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python3
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from .models import Poll, Choice
|
from .models import Poll, Choice
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
|
||||||
|
|
||||||
from djadmin2.site import djadmin2_site
|
from djadmin2.site import djadmin2_site
|
||||||
from djadmin2.types import Admin2TabularInline, ModelAdmin2
|
from djadmin2.types import Admin2TabularInline, ModelAdmin2
|
||||||
from .models import Poll, Choice
|
from .models import Poll, Choice
|
||||||
|
|
@ -8,7 +5,7 @@ from .models import Poll, Choice
|
||||||
|
|
||||||
class ChoiceInline(Admin2TabularInline):
|
class ChoiceInline(Admin2TabularInline):
|
||||||
model = Choice
|
model = Choice
|
||||||
extra = 3
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
class PollAdmin(ModelAdmin2):
|
class PollAdmin(ModelAdmin2):
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -37,6 +34,9 @@ class Migration(migrations.Migration):
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='choice',
|
model_name='choice',
|
||||||
name='poll',
|
name='poll',
|
||||||
field=models.ForeignKey(verbose_name='poll', to='polls.Poll'),
|
field=models.ForeignKey(
|
||||||
|
verbose_name='poll',
|
||||||
|
to='polls.Poll',
|
||||||
|
on_delete=models.CASCADE),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,10 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class Poll(models.Model):
|
class Poll(models.Model):
|
||||||
question = models.CharField(max_length=200, verbose_name=_('question'))
|
question = models.CharField(max_length=200, verbose_name=_('question'))
|
||||||
pub_date = models.DateTimeField(verbose_name=_('date published'))
|
pub_date = models.DateTimeField(verbose_name=_('date published'))
|
||||||
|
|
@ -28,9 +23,12 @@ class Poll(models.Model):
|
||||||
verbose_name_plural = _('polls')
|
verbose_name_plural = _('polls')
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class Choice(models.Model):
|
class Choice(models.Model):
|
||||||
poll = models.ForeignKey(Poll, verbose_name=_('poll'))
|
poll = models.ForeignKey(
|
||||||
|
Poll,
|
||||||
|
verbose_name=_('poll'),
|
||||||
|
on_delete=models.CASCADE
|
||||||
|
)
|
||||||
choice_text = models.CharField(
|
choice_text = models.CharField(
|
||||||
max_length=200, verbose_name=_('choice text'))
|
max_length=200, verbose_name=_('choice text'))
|
||||||
votes = models.IntegerField(default=0, verbose_name=_('votes'))
|
votes = models.IntegerField(default=0, verbose_name=_('votes'))
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.test import TestCase, Client
|
from django.test import TestCase, Client
|
||||||
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_str
|
||||||
|
|
||||||
from ..models import Poll
|
from ..models import Poll
|
||||||
|
|
||||||
|
|
@ -40,7 +40,7 @@ class PollListTest(BaseIntegrationTest):
|
||||||
def test_actions_displayed(self):
|
def test_actions_displayed(self):
|
||||||
response = self.client.get(reverse("admin2:polls_poll_index"))
|
response = self.client.get(reverse("admin2:polls_poll_index"))
|
||||||
self.assertInHTML(
|
self.assertInHTML(
|
||||||
'<a tabindex="-1" href="#" data-name="action" data-value="DeleteSelectedAction">Delete selected items</a>', force_text(response.content))
|
'<a tabindex="-1" href="#" data-name="action" data-value="DeleteSelectedAction">Delete selected items</a>', force_str(response.content))
|
||||||
|
|
||||||
def test_delete_selected_poll(self):
|
def test_delete_selected_poll(self):
|
||||||
poll = Poll.objects.create(
|
poll = Poll.objects.create(
|
||||||
|
|
@ -49,7 +49,7 @@ class PollListTest(BaseIntegrationTest):
|
||||||
'selected_model_pk': str(poll.pk)}
|
'selected_model_pk': str(poll.pk)}
|
||||||
response = self.client.post(reverse("admin2:polls_poll_index"), params)
|
response = self.client.post(reverse("admin2:polls_poll_index"), params)
|
||||||
self.assertInHTML(
|
self.assertInHTML(
|
||||||
'<p>Are you sure you want to delete the selected poll? The following item will be deleted:</p>', force_text(response.content))
|
'<p>Are you sure you want to delete the selected poll? The following item will be deleted:</p>', force_str(response.content))
|
||||||
|
|
||||||
def test_delete_selected_poll_confirmation(self):
|
def test_delete_selected_poll_confirmation(self):
|
||||||
poll = Poll.objects.create(
|
poll = Poll.objects.create(
|
||||||
|
|
|
||||||
3
fabfile.py
vendored
3
fabfile.py
vendored
|
|
@ -1,6 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import print_function, division, absolute_import, unicode_literals
|
|
||||||
|
|
||||||
from fabric.api import local, lcd
|
from fabric.api import local, lcd
|
||||||
from fabric.contrib.console import confirm
|
from fabric.contrib.console import confirm
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
django-extra-views<=0.7.1
|
django-extra-views
|
||||||
django-braces>=1.3.0
|
django-braces
|
||||||
djangorestframework<=3.3.3
|
djangorestframework
|
||||||
django-filter>=0.15.3
|
django-filter
|
||||||
django-debug-toolbar>=1.5
|
django-debug-toolbar
|
||||||
future>=0.15.2
|
pytz
|
||||||
pytz>=2016.4
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
-rrequirements.txt
|
-rrequirements.txt
|
||||||
flake8==2.5.4
|
flake8>=2.5.4
|
||||||
pytest
|
pytest
|
||||||
pytest-django
|
pytest-django
|
||||||
|
pytest-cov
|
||||||
|
|
|
||||||
22
setup.py
22
setup.py
|
|
@ -1,5 +1,4 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
from setuptools.command.test import test as TestCommand
|
from setuptools.command.test import test as TestCommand
|
||||||
|
|
@ -81,6 +80,7 @@ if sys.argv[-1] == 'publish':
|
||||||
LONG_DESCRIPTION = remove_screenshots(open('README.rst').read())
|
LONG_DESCRIPTION = remove_screenshots(open('README.rst').read())
|
||||||
HISTORY = open('HISTORY.rst').read()
|
HISTORY = open('HISTORY.rst').read()
|
||||||
|
|
||||||
|
|
||||||
class PyTest(TestCommand):
|
class PyTest(TestCommand):
|
||||||
user_options = [('pytest-args=', 'a', "Arguments to pass to py.test")]
|
user_options = [('pytest-args=', 'a', "Arguments to pass to py.test")]
|
||||||
|
|
||||||
|
|
@ -100,6 +100,7 @@ class PyTest(TestCommand):
|
||||||
errno = pytest.main(self.pytest_args)
|
errno = pytest.main(self.pytest_args)
|
||||||
sys.exit(errno)
|
sys.exit(errno)
|
||||||
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='django-admin2',
|
name='django-admin2',
|
||||||
version=version,
|
version=version,
|
||||||
|
|
@ -112,8 +113,10 @@ setup(
|
||||||
"License :: OSI Approved :: BSD License",
|
"License :: OSI Approved :: BSD License",
|
||||||
"Operating System :: OS Independent",
|
"Operating System :: OS Independent",
|
||||||
"Programming Language :: JavaScript",
|
"Programming Language :: JavaScript",
|
||||||
"Programming Language :: Python :: 2.7",
|
"Programming Language :: Python :: 3.5",
|
||||||
"Programming Language :: Python :: 3.3",
|
"Programming Language :: Python :: 3.6",
|
||||||
|
"Programming Language :: Python :: 3.7",
|
||||||
|
"Programming Language :: Python :: 3.8",
|
||||||
"Topic :: Internet :: WWW/HTTP",
|
"Topic :: Internet :: WWW/HTTP",
|
||||||
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
|
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
|
||||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||||
|
|
@ -127,13 +130,12 @@ setup(
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
#test_suite='runtests.runtests',
|
#test_suite='runtests.runtests',
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'django>=1.8.0',
|
'django>=2.2',
|
||||||
'django-extra-views<=0.7.1',
|
'django-extra-views>=0.12.0',
|
||||||
'django-braces>=1.3.0',
|
'django-braces>=1.3.0',
|
||||||
'djangorestframework<=3.3.3',
|
'djangorestframework>=3.11.1',
|
||||||
'django-filter>=0.15.3',
|
'django-filter>=2.4.0',
|
||||||
'pytz==2016.4',
|
'pytz>=2016.4',
|
||||||
'future>=0.15.2',
|
|
||||||
],
|
],
|
||||||
extras_require={
|
extras_require={
|
||||||
'testing': ['pytest', 'pytest-django', 'pytest-ipdb'],
|
'testing': ['pytest', 'pytest-django', 'pytest-ipdb'],
|
||||||
|
|
|
||||||
28
tox.ini
28
tox.ini
|
|
@ -6,21 +6,29 @@ exclude = migrations/*,docs/*
|
||||||
|
|
||||||
[tox]
|
[tox]
|
||||||
envlist =
|
envlist =
|
||||||
py27-{1.8,1.9,1.10,master},
|
py35-{2.2},
|
||||||
py33-{1.8},
|
py36-{2.2,3.1},
|
||||||
py34-{1.8,1.9,1.10,master},
|
py37-{2.2,3.1,3.2},
|
||||||
py35-{1.8,1.9,1.10,master},
|
py38-{2.2,3.1,3.2,main},
|
||||||
|
|
||||||
|
[gh-actions]
|
||||||
|
python =
|
||||||
|
3.5: py35
|
||||||
|
3.6: py36
|
||||||
|
3.7: py37
|
||||||
|
3.8: py38
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
commands =
|
commands =
|
||||||
flake8 djadmin2
|
pytest --cov-append --cov djadmin2 --cov-report=xml []
|
||||||
py.test []
|
|
||||||
deps =
|
deps =
|
||||||
-rrequirements_test.txt
|
-rrequirements_test.txt
|
||||||
1.8: Django>=1.8,<1.9
|
3.1: Django>=3.1,<3.2
|
||||||
1.9: Django>=1.9,<1.10
|
3.2: Django>=3.2,<4.0
|
||||||
1.10: Django>=1.10,<1.11
|
2.2: Django>=2.2,<2.3
|
||||||
master: https://github.com/django/django/tarball/master
|
main: https://github.com/django/django/tarball/main
|
||||||
setenv=
|
setenv=
|
||||||
DJANGO_SETTINGS_MODULE = example.settings
|
DJANGO_SETTINGS_MODULE = example.settings
|
||||||
PYTHONPATH = {toxinidir}/example:{toxinidir}
|
PYTHONPATH = {toxinidir}/example:{toxinidir}
|
||||||
|
PYTHONDONTWRITEBYTECODE=1
|
||||||
|
PYTHONWARNINGS=once
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue