mirror of
https://github.com/jazzband/django-admin2.git
synced 2026-05-15 18:53:18 +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
|
||||
.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
|
||||
------------
|
||||
|
||||
* Asif Saif Uddin (@auvipy)
|
||||
* Daniel Greenfeld (@pydanny / <pydanny@gmail.com>)
|
||||
|
||||
Translation Managers
|
||||
|
|
@ -11,6 +12,7 @@ Translation Managers
|
|||
|
||||
* Henri Colas (@NotSqrt)
|
||||
* Danilo Bargen (@dbrgn)
|
||||
* Asif Saif Uddin (@auvipy)
|
||||
|
||||
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
|
||||
:target: https://jazzband.co/
|
||||
:alt: Jazzband
|
||||
.. image:: https://travis-ci.org/jazzband/django-admin2.png
|
||||
:alt: Build Status
|
||||
:target: https://travis-ci.org/jazzband/django-admin2
|
||||
.. image:: https://coveralls.io/repos/github/jazzband/django-admin2/badge.svg?branch=develop
|
||||
:alt: Coverage Status
|
||||
:target: https://coveralls.io/github/jazzband/django-admin2?branch=develop
|
||||
.. 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
|
||||
.. image:: https://github.com/jazzband/django-admin2/workflows/Test/badge.svg
|
||||
:target: https://github.com/jazzband/django-admin2/actions
|
||||
:alt: GitHub Actions
|
||||
.. image:: https://codecov.io/gh/jazzband/django-admin2/branch/main/graph/badge.svg?token=PcC594rhI4
|
||||
:target: https://codecov.io/gh/jazzband/django-admin2
|
||||
:alt: Code coverage
|
||||
|
||||
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
|
||||
|
|
@ -32,23 +30,23 @@ Features
|
|||
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
|
||||
:alt: Site administration
|
||||
: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
|
||||
:alt: Select user
|
||||
: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
|
||||
============
|
||||
|
||||
* Django 1.7+
|
||||
* Python 2.7+ or Python 3.3+
|
||||
* Django 2.2+
|
||||
* Python 3.5+
|
||||
* django-braces_
|
||||
* django-extra-views_
|
||||
* django-rest-framework_
|
||||
|
|
|
|||
|
|
@ -1,8 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import division, absolute_import, unicode_literals
|
||||
|
||||
|
||||
__version__ = '0.7.0'
|
||||
__version__ = '0.7.1'
|
||||
|
||||
__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.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.translation import ugettext as _
|
||||
from django.utils.translation import ugettext_lazy, ungettext, pgettext_lazy
|
||||
from django.utils.translation import gettext as _
|
||||
from django.utils.translation import gettext_lazy, ngettext, pgettext_lazy
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
from . import permissions, utils
|
||||
|
|
@ -26,7 +23,7 @@ class BaseListAction(Admin2ModelMixin, TemplateView):
|
|||
|
||||
permission_classes = (permissions.IsStaffPermission,)
|
||||
|
||||
empty_message = ugettext_lazy(
|
||||
empty_message = gettext_lazy(
|
||||
'Items must be selected in order to perform actions '
|
||||
'on them. No items have been changed.'
|
||||
)
|
||||
|
|
@ -50,9 +47,9 @@ class BaseListAction(Admin2ModelMixin, TemplateView):
|
|||
objects_name = options.verbose_name
|
||||
else:
|
||||
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):
|
||||
""" Replaced `get_queryset` from `Admin2ModelMixin`"""
|
||||
|
|
@ -88,12 +85,12 @@ class BaseListAction(Admin2ModelMixin, TemplateView):
|
|||
""" Utility method when you want to display nested objects
|
||||
(such as during a bulk update/delete)
|
||||
"""
|
||||
context = super(BaseListAction, self).get_context_data()
|
||||
context = super().get_context_data()
|
||||
|
||||
def _format_callback(obj):
|
||||
opts = utils.model_options(obj)
|
||||
return '%s: %s' % (force_text(capfirst(opts.verbose_name)),
|
||||
force_text(obj))
|
||||
return '%s: %s' % (force_str(capfirst(opts.verbose_name)),
|
||||
force_str(obj))
|
||||
|
||||
using = router.db_for_write(self.model)
|
||||
|
||||
|
|
@ -111,7 +108,7 @@ class BaseListAction(Admin2ModelMixin, TemplateView):
|
|||
|
||||
def get(self, request):
|
||||
if self.item_count > 0:
|
||||
return super(BaseListAction, self).get(request)
|
||||
return super().get(request)
|
||||
|
||||
message = _(self.empty_message)
|
||||
messages.add_message(request, messages.INFO, message)
|
||||
|
|
@ -122,7 +119,7 @@ class BaseListAction(Admin2ModelMixin, TemplateView):
|
|||
if self.process_queryset() is None:
|
||||
|
||||
# objects_name should already be pluralized, see __init__
|
||||
message = ungettext(
|
||||
message = ngettext(
|
||||
self.success_message,
|
||||
self.success_message_plural,
|
||||
self.item_count
|
||||
|
|
@ -146,7 +143,7 @@ class DeleteSelectedAction(BaseListAction):
|
|||
|
||||
default_template_name = "actions/delete_selected_confirmation.html"
|
||||
|
||||
description = ugettext_lazy("Delete selected items")
|
||||
description = gettext_lazy("Delete selected items")
|
||||
|
||||
success_message = pgettext_lazy(
|
||||
'singular form',
|
||||
|
|
@ -163,7 +160,7 @@ class DeleteSelectedAction(BaseListAction):
|
|||
|
||||
def post(self, request):
|
||||
if request.POST.get('confirmed'):
|
||||
super(DeleteSelectedAction, self).post(request)
|
||||
super().post(request)
|
||||
else:
|
||||
# The user has not confirmed that they want to delete the
|
||||
# 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.contrib.auth.models import Group, User
|
||||
from django.contrib.sites.models import Site
|
||||
|
|
@ -17,6 +14,7 @@ class GroupSerializer(Admin2APISerializer):
|
|||
|
||||
class Meta:
|
||||
model = Group
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
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 rest_framework import fields, generics, serializers
|
||||
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 . import utils
|
||||
|
|
@ -20,7 +17,7 @@ class Admin2APISerializer(serializers.HyperlinkedModelSerializer):
|
|||
__unicode__ = fields.ReadOnlyField(source='__str__')
|
||||
|
||||
def get_extra_kwargs(self):
|
||||
extra_kwargs = super(Admin2APISerializer, self).get_extra_kwargs()
|
||||
extra_kwargs = super().get_extra_kwargs()
|
||||
extra_kwargs.update({
|
||||
'url': {'view_name': self._get_default_view_name(self.Meta.model)}
|
||||
})
|
||||
|
|
@ -56,9 +53,10 @@ class Admin2APIMixin(Admin2Mixin):
|
|||
|
||||
class Meta:
|
||||
model = model_class
|
||||
fields = '__all__'
|
||||
|
||||
return ModelAPISerilizer
|
||||
return super(Admin2APIMixin, self).get_serializer_class()
|
||||
return super().get_serializer_class()
|
||||
|
||||
|
||||
class IndexAPIView(Admin2APIMixin, APIView):
|
||||
|
|
@ -75,7 +73,7 @@ class IndexAPIView(Admin2APIMixin, APIView):
|
|||
'app_label': model_options.app_label,
|
||||
'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,
|
||||
request=self.request,
|
||||
format=self.kwargs.get('format'))
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
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
|
||||
|
||||
|
|
@ -10,5 +10,7 @@ class Djadmin2Config(AppConfig):
|
|||
verbose_name = _("Django Admin2")
|
||||
|
||||
def ready(self):
|
||||
post_migrate.connect(create_view_permissions,
|
||||
dispatch_uid="django-admin2.djadmin2.permissions.create_view_permissions")
|
||||
post_migrate.connect(
|
||||
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 create_new_multiwidget
|
||||
|
||||
|
||||
# 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
|
||||
# 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
|
||||
Issue #99.
|
||||
"""
|
||||
from __future__ import division, absolute_import, unicode_literals
|
||||
|
||||
from importlib import import_module
|
||||
|
||||
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 . import apiviews
|
||||
|
|
@ -17,7 +14,7 @@ from . import utils
|
|||
from . import views
|
||||
|
||||
|
||||
class Admin2(object):
|
||||
class Admin2:
|
||||
"""
|
||||
The base Admin2 object.
|
||||
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
|
||||
admin site.
|
||||
"""
|
||||
|
||||
index_view = views.IndexView
|
||||
login_view = views.LoginView
|
||||
app_index_view = views.AppIndexView
|
||||
api_index_view = apiviews.IndexAPIView
|
||||
|
||||
def __init__(self, name='admin2'):
|
||||
def __init__(self, name="admin2"):
|
||||
self.registry = {}
|
||||
self.apps = {}
|
||||
self.app_verbose_names = {}
|
||||
|
|
@ -51,7 +49,8 @@ class Admin2(object):
|
|||
"""
|
||||
if model in self.registry:
|
||||
raise ImproperlyConfigured(
|
||||
'%s is already registered in django-admin2' % model)
|
||||
"%s is already registered in django-admin2" % model
|
||||
)
|
||||
if not model_admin:
|
||||
model_admin = types.ModelAdmin2
|
||||
self.registry[model] = model_admin(model, admin=self, **kwargs)
|
||||
|
|
@ -74,7 +73,8 @@ class Admin2(object):
|
|||
del self.registry[model]
|
||||
except KeyError:
|
||||
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
|
||||
# Get the app label
|
||||
|
|
@ -96,7 +96,8 @@ class Admin2(object):
|
|||
"""
|
||||
if app_label in self.app_verbose_names:
|
||||
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
|
||||
|
||||
|
|
@ -112,7 +113,8 @@ class Admin2(object):
|
|||
del self.app_verbose_names[app_label]
|
||||
except KeyError:
|
||||
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):
|
||||
"""
|
||||
|
|
@ -123,7 +125,7 @@ class Admin2(object):
|
|||
try:
|
||||
import_module("%s.admin2" % app_name)
|
||||
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
|
||||
raise e
|
||||
|
||||
|
|
@ -135,71 +137,74 @@ class Admin2(object):
|
|||
for object_admin in self.registry.values():
|
||||
if object_admin.name == name:
|
||||
return object_admin
|
||||
raise ValueError(
|
||||
u'No object admin found with name {}'.format(repr(name)))
|
||||
raise ValueError("No object admin found with name {}".format(repr(name)))
|
||||
|
||||
def get_index_kwargs(self):
|
||||
return {
|
||||
'registry': self.registry,
|
||||
'app_verbose_names': self.app_verbose_names,
|
||||
'apps': self.apps,
|
||||
'login_view': self.login_view,
|
||||
"registry": self.registry,
|
||||
"app_verbose_names": self.app_verbose_names,
|
||||
"apps": self.apps,
|
||||
"login_view": self.login_view,
|
||||
}
|
||||
|
||||
def get_app_index_kwargs(self):
|
||||
return {
|
||||
'registry': self.registry,
|
||||
'app_verbose_names': self.app_verbose_names,
|
||||
'apps': self.apps,
|
||||
"registry": self.registry,
|
||||
"app_verbose_names": self.app_verbose_names,
|
||||
"apps": self.apps,
|
||||
}
|
||||
|
||||
def get_api_index_kwargs(self):
|
||||
return {
|
||||
'registry': self.registry,
|
||||
'app_verbose_names': self.app_verbose_names,
|
||||
'apps': self.apps,
|
||||
"registry": self.registry,
|
||||
"app_verbose_names": self.app_verbose_names,
|
||||
"apps": self.apps,
|
||||
}
|
||||
|
||||
def get_urls(self):
|
||||
urlpatterns = [
|
||||
url(regex=r'^$',
|
||||
re_path(
|
||||
r"^$",
|
||||
view=self.index_view.as_view(**self.get_index_kwargs()),
|
||||
name='dashboard'
|
||||
),
|
||||
url(regex='^auth/user/(?P<pk>\d+)/update/password/$',
|
||||
name="dashboard",
|
||||
),
|
||||
re_path(
|
||||
r"^auth/user/(?P<pk>\d+)/update/password/$",
|
||||
view=views.PasswordChangeView.as_view(),
|
||||
name='password_change'
|
||||
),
|
||||
url(regex='^password_change_done/$',
|
||||
name="password_change",
|
||||
),
|
||||
re_path(
|
||||
"^password_change_done/$",
|
||||
view=views.PasswordChangeDoneView.as_view(),
|
||||
name='password_change_done'
|
||||
),
|
||||
url(regex='^logout/$',
|
||||
view=views.LogoutView.as_view(),
|
||||
name='logout'
|
||||
),
|
||||
url(regex=r'^(?P<app_label>\w+)/$',
|
||||
view=self.app_index_view.as_view(
|
||||
**self.get_app_index_kwargs()),
|
||||
name='app_index'
|
||||
),
|
||||
url(regex=r'^api/v0/$',
|
||||
view=self.api_index_view.as_view(
|
||||
**self.get_api_index_kwargs()),
|
||||
name='api_index'
|
||||
),
|
||||
name="password_change_done",
|
||||
),
|
||||
re_path("^logout/$", view=views.LogoutView.as_view(), name="logout"),
|
||||
re_path(
|
||||
r"^(?P<app_label>\w+)/$",
|
||||
view=self.app_index_view.as_view(**self.get_app_index_kwargs()),
|
||||
name="app_index",
|
||||
),
|
||||
re_path(
|
||||
r"^api/v0/$",
|
||||
view=self.api_index_view.as_view(**self.get_api_index_kwargs()),
|
||||
name="api_index",
|
||||
),
|
||||
]
|
||||
for model, model_admin in self.registry.items():
|
||||
model_options = utils.model_options(model)
|
||||
urlpatterns += [
|
||||
url('^{}/{}/'.format(
|
||||
model_options.app_label,
|
||||
model_options.object_name.lower()),
|
||||
include(model_admin.urls)),
|
||||
url('^api/v0/{}/{}/'.format(
|
||||
model_options.app_label,
|
||||
model_options.object_name.lower()),
|
||||
include(model_admin.api_urls)),
|
||||
re_path(
|
||||
"^{}/{}/".format(
|
||||
model_options.app_label, model_options.object_name.lower()
|
||||
),
|
||||
model_admin.urls,
|
||||
),
|
||||
re_path(
|
||||
"^api/v0/{}/{}/".format(
|
||||
model_options.app_label, model_options.object_name.lower()
|
||||
),
|
||||
model_admin.api_urls,
|
||||
),
|
||||
]
|
||||
return urlpatterns
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,14 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import division, absolute_import, unicode_literals
|
||||
|
||||
import collections
|
||||
import collections.abc
|
||||
from itertools import chain
|
||||
|
||||
import django_filters
|
||||
from django import forms
|
||||
from django.forms import widgets as django_widgets
|
||||
from django.forms.utils import flatatt
|
||||
from django.utils import six
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.encoding import force_str
|
||||
from django.utils.html import format_html
|
||||
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
|
||||
|
||||
|
|
@ -24,15 +20,16 @@ class NumericDateFilter(django_filters.DateFilter):
|
|||
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
def render(self, name, value, attrs=None, choices=()):
|
||||
links = []
|
||||
for choice_value, choice_label in chain(self.choices, choices):
|
||||
links.append(format_html(
|
||||
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))
|
||||
|
||||
|
|
@ -44,11 +41,12 @@ class NullBooleanLinksWidget(
|
|||
def __init__(self, attrs=None, choices=()):
|
||||
super(ChoicesAsLinksWidget, self).__init__(attrs)
|
||||
self.choices = [
|
||||
('1', ugettext_lazy('Unknown')),
|
||||
('2', ugettext_lazy('Yes')),
|
||||
('3', ugettext_lazy('No')),
|
||||
('1', gettext_lazy('Unknown')),
|
||||
('2', gettext_lazy('Yes')),
|
||||
('3', gettext_lazy('No')),
|
||||
]
|
||||
|
||||
|
||||
#: Maps `django_filter`'s field filters types to our
|
||||
#: custom form widget.
|
||||
FILTER_TYPE_TO_WIDGET = {
|
||||
|
|
@ -68,7 +66,7 @@ def build_list_filter(request, model_admin, queryset):
|
|||
`request.GET` and `queryset`.
|
||||
"""
|
||||
# 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(
|
||||
request.GET,
|
||||
queryset=queryset,
|
||||
|
|
@ -76,7 +74,7 @@ def build_list_filter(request, model_admin, queryset):
|
|||
# otherwise build :mod:`django_filters.FilterSet`
|
||||
filters = []
|
||||
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(
|
||||
queryset.model,
|
||||
field_filter,
|
||||
|
|
@ -85,7 +83,7 @@ def build_list_filter(request, model_admin, queryset):
|
|||
filters.append(field_filter)
|
||||
filterset_dict = {}
|
||||
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())
|
||||
filterset_dict['Meta'] = type(
|
||||
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"):
|
||||
filterset_dict = {
|
||||
"year": NumericDateFilter(
|
||||
name=field_name,
|
||||
lookup_type="year",
|
||||
field_name=field_name,
|
||||
lookup_expr="year",
|
||||
),
|
||||
"month": NumericDateFilter(
|
||||
name=field_name,
|
||||
lookup_type="month",
|
||||
field_name=field_name,
|
||||
lookup_expr="month",
|
||||
),
|
||||
"day": NumericDateFilter(
|
||||
name=field_name,
|
||||
lookup_type="day",
|
||||
field_name=field_name,
|
||||
lookup_expr="day",
|
||||
)
|
||||
}
|
||||
filterset_dict["Meta"] = type(
|
||||
type_str('Meta'),
|
||||
(object, ),
|
||||
{"model": queryset.model, "fields": [field_name]},
|
||||
)
|
||||
|
||||
return type(
|
||||
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,),
|
||||
field_name,
|
||||
)
|
||||
print("EXTRA!!!!")
|
||||
print(filter_.extra)
|
||||
filter_.widget = FILTER_TYPE_TO_WIDGET.get(
|
||||
filter_.__class__,
|
||||
filter_.widget,
|
||||
filter_.__class__
|
||||
)
|
||||
return filter_
|
||||
|
|
|
|||
|
|
@ -1,14 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import division, absolute_import, unicode_literals
|
||||
|
||||
from django import forms
|
||||
from django.contrib.auth import authenticate
|
||||
from django.contrib.auth.forms import AuthenticationForm
|
||||
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
|
||||
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
|
||||
|
|
@ -59,7 +56,7 @@ class Admin2UserCreationForm(UserCreationForm):
|
|||
class Admin2UserChangeForm(UserChangeForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Admin2UserChangeForm, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
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())
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
from django.conf import settings
|
||||
|
||||
|
|
@ -22,8 +19,16 @@ class Migration(migrations.Migration):
|
|||
('object_repr', models.CharField(max_length=200, verbose_name='object repr')),
|
||||
('action_flag', models.PositiveSmallIntegerField(verbose_name='action flag')),
|
||||
('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')),
|
||||
('user', models.ForeignKey(related_name='log_entries', to=settings.AUTH_USER_MODEL)),
|
||||
('content_type', models.ForeignKey(
|
||||
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={
|
||||
'verbose_name': 'log entry',
|
||||
|
|
|
|||
|
|
@ -1,14 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
""" Boilerplate for now, will serve a purpose soon! """
|
||||
from __future__ import division, absolute_import, unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import models
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
from django.utils.encoding import smart_text
|
||||
from django.utils.translation import ugettext, ugettext_lazy as _
|
||||
from django.utils.encoding import force_str
|
||||
from django.utils.translation import gettext, gettext_lazy as _
|
||||
|
||||
from .utils import quote
|
||||
|
||||
|
|
@ -17,12 +12,11 @@ class LogEntryManager(models.Manager):
|
|||
def log_action(self, user_id, obj, action_flag, change_message=''):
|
||||
content_type_id = ContentType.objects.get_for_model(obj).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)
|
||||
e.save()
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class LogEntry(models.Model):
|
||||
ADDITION = 1
|
||||
CHANGE = 2
|
||||
|
|
@ -30,9 +24,11 @@ class LogEntry(models.Model):
|
|||
|
||||
action_time = models.DateTimeField(_('action time'), auto_now=True)
|
||||
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,
|
||||
related_name='log_entries')
|
||||
related_name='log_entries',
|
||||
on_delete=models.CASCADE)
|
||||
object_id = models.TextField(_('object id'), blank=True, null=True)
|
||||
object_repr = models.CharField(_('object repr'), max_length=200)
|
||||
action_flag = models.PositiveSmallIntegerField(_('action flag'))
|
||||
|
|
@ -46,22 +42,22 @@ class LogEntry(models.Model):
|
|||
ordering = ('-action_time',)
|
||||
|
||||
def __repr__(self):
|
||||
return smart_text(self.action_time)
|
||||
return force_str(self.action_time)
|
||||
|
||||
def __str__(self):
|
||||
if self.action_flag == self.ADDITION:
|
||||
return ugettext('Added "%(object)s".') % {
|
||||
return gettext('Added "%(object)s".') % {
|
||||
'object': self.object_repr}
|
||||
elif self.action_flag == self.CHANGE:
|
||||
return ugettext('Changed "%(object)s" - %(changes)s') % {
|
||||
return gettext('Changed "%(object)s" - %(changes)s') % {
|
||||
'object': self.object_repr,
|
||||
'changes': self.change_message,
|
||||
}
|
||||
elif self.action_flag == self.DELETION:
|
||||
return ugettext('Deleted "%(object)s."') % {
|
||||
return gettext('Deleted "%(object)s."') % {
|
||||
'object': self.object_repr}
|
||||
|
||||
return ugettext('LogEntry Object')
|
||||
return gettext('LogEntry Object')
|
||||
|
||||
def is_addition(self):
|
||||
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
|
||||
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
|
||||
which it can hold multiple.
|
||||
"""
|
||||
from __future__ import division, absolute_import, unicode_literals
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
|
|
@ -25,8 +22,7 @@ from django.db.utils import DEFAULT_DB_ALIAS
|
|||
from django.apps import apps
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import router
|
||||
from django.utils import six
|
||||
from django.utils.encoding import python_2_unicode_compatible, force_text
|
||||
from django.utils.encoding import force_str
|
||||
|
||||
logger = logging.getLogger('djadmin2')
|
||||
|
||||
|
|
@ -35,7 +31,7 @@ def is_authenticated(request, view, obj=None):
|
|||
'''
|
||||
Checks if the current user is authenticated.
|
||||
'''
|
||||
return request.user.is_authenticated()
|
||||
return request.user.is_authenticated
|
||||
|
||||
|
||||
def is_staff(request, view, obj=None):
|
||||
|
|
@ -95,7 +91,7 @@ def model_permission(permission):
|
|||
return has_permission
|
||||
|
||||
|
||||
class BasePermission(object):
|
||||
class BasePermission:
|
||||
'''
|
||||
Provides a base class with a common API. It implements a compatible
|
||||
interface to django-rest-framework permission backends.
|
||||
|
|
@ -191,8 +187,7 @@ class ModelDeletePermission(BasePermission):
|
|||
permissions = (model_permission('{app_label}.delete_{model_name}'),)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class TemplatePermissionChecker(object):
|
||||
class TemplatePermissionChecker:
|
||||
'''
|
||||
Can be used in the template like:
|
||||
|
||||
|
|
@ -259,7 +254,7 @@ class TemplatePermissionChecker(object):
|
|||
needs an interface beeing implemented like suggested in:
|
||||
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': 'detail_view',
|
||||
|
|
@ -286,7 +281,7 @@ class TemplatePermissionChecker(object):
|
|||
Return a clone of the permission wrapper with a new model_admin bind
|
||||
to it.
|
||||
'''
|
||||
if isinstance(admin, six.string_types):
|
||||
if isinstance(admin, str):
|
||||
try:
|
||||
admin = self._model_admin.admin.get_admin_by_name(admin)
|
||||
except ValueError:
|
||||
|
|
@ -300,7 +295,7 @@ class TemplatePermissionChecker(object):
|
|||
'''
|
||||
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:
|
||||
return ''
|
||||
view_name = self.view_name_mapping[view]
|
||||
|
|
@ -365,7 +360,7 @@ class TemplatePermissionChecker(object):
|
|||
def __str__(self):
|
||||
if self._view is None:
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,17 +1,14 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
There are currently a few renderers that come directly with django-admin2. They
|
||||
are used by default for some field types.
|
||||
"""
|
||||
from __future__ import division, absolute_import, unicode_literals
|
||||
|
||||
import os.path
|
||||
from datetime import date, time, datetime
|
||||
|
||||
from django.db import models
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils import formats, timezone
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.encoding import force_str
|
||||
|
||||
from djadmin2 import settings
|
||||
|
||||
|
|
@ -65,7 +62,7 @@ def title_renderer(value, field):
|
|||
:rtype: unicode or str
|
||||
|
||||
"""
|
||||
return force_text(value).title()
|
||||
return force_str(value).title()
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import division, absolute_import, unicode_literals
|
||||
|
||||
from numbers import Number
|
||||
from datetime import date, time, datetime
|
||||
|
||||
from django import template
|
||||
from django.db.models.fields import FieldDoesNotExist
|
||||
from django.core.exceptions import FieldDoesNotExist
|
||||
|
||||
from .. import utils, renderers, models, settings
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{% extends "djadmin2theme_bootstrap3/base.html" %}
|
||||
{% load i18n staticfiles admin2_tags %}
|
||||
{% load i18n static admin2_tags %}
|
||||
|
||||
{% block navbar %}{% endblock navbar %}
|
||||
{% block breacrumbs %}{% endblock breacrumbs %}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from ..actions import get_description
|
|||
from .models import Thing
|
||||
|
||||
|
||||
class TestAction(object):
|
||||
class TestAction:
|
||||
description = "Test Action Class"
|
||||
|
||||
|
||||
|
|
@ -23,19 +23,19 @@ class ActionTest(TestCase):
|
|||
TestAction,
|
||||
test_function,
|
||||
])
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
get_description(
|
||||
self.admin2.registry[Thing].list_actions[0]
|
||||
),
|
||||
'Delete selected items'
|
||||
)
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
get_description(
|
||||
self.admin2.registry[Thing].list_actions[1]
|
||||
),
|
||||
'Test Action Class'
|
||||
)
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
get_description(
|
||||
self.admin2.registry[Thing].list_actions[2]
|
||||
),
|
||||
|
|
|
|||
|
|
@ -22,65 +22,65 @@ class TagsTests(TestCase):
|
|||
self.instance = TagsTestsModel()
|
||||
|
||||
def test_admin2_urlname(self):
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
"admin2:None_None_index",
|
||||
admin2_tags.admin2_urlname(IndexView, "index")
|
||||
)
|
||||
|
||||
def test_model_verbose_name_as_model_class(self):
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
TagsTestsModel._meta.verbose_name,
|
||||
admin2_tags.model_verbose_name(TagsTestsModel)
|
||||
)
|
||||
|
||||
def test_model_verbose_name_as_model_instance(self):
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
self.instance._meta.verbose_name,
|
||||
admin2_tags.model_verbose_name(self.instance)
|
||||
)
|
||||
|
||||
def test_model_verbose_name_plural_as_model_class(self):
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
TagsTestsModel._meta.verbose_name_plural,
|
||||
admin2_tags.model_verbose_name_plural(TagsTestsModel)
|
||||
)
|
||||
|
||||
def test_model_verbose_name_plural_as_model_instance(self):
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
self.instance._meta.verbose_name_plural,
|
||||
admin2_tags.model_verbose_name_plural(self.instance)
|
||||
)
|
||||
|
||||
def test_model_field_verbose_name_autogenerated(self):
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
'field1',
|
||||
admin2_tags.model_attr_verbose_name(self.instance, 'field1')
|
||||
)
|
||||
|
||||
def test_model_field_verbose_name_overridden(self):
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
'second field',
|
||||
admin2_tags.model_attr_verbose_name(self.instance, 'field2')
|
||||
)
|
||||
|
||||
def test_model_method_verbose_name(self):
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
'Published recently?',
|
||||
admin2_tags.model_attr_verbose_name(self.instance, 'was_published_recently')
|
||||
)
|
||||
|
||||
def test_formset_visible_fieldlist(self):
|
||||
formset = TagsTestFormSet()
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
admin2_tags.formset_visible_fieldlist(formset),
|
||||
[u'Visible 1', u'Visible 2']
|
||||
['Visible 1', 'Visible 2']
|
||||
)
|
||||
|
||||
def test_verbose_name_for(self):
|
||||
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",
|
||||
admin2_tags.verbose_name_for(app_verbose_names, 'app_one_label')
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
from django import forms
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
from django.urls import reverse
|
||||
|
||||
|
||||
from djadmin2.site import djadmin2_site
|
||||
from ..admin2 import UserAdmin2
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class Admin2Test(TestCase):
|
|||
|
||||
def test_register_app_verbose_name(self):
|
||||
self.admin2.register_app_verbose_name(APP_LABEL, APP_VERBOSE_NAME)
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
self.admin2.app_verbose_names[APP_LABEL],
|
||||
APP_VERBOSE_NAME
|
||||
)
|
||||
|
|
@ -61,7 +61,7 @@ class Admin2Test(TestCase):
|
|||
|
||||
def test_get_urls(self):
|
||||
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):
|
||||
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
|
||||
from decimal import Decimal
|
||||
|
||||
from django.test import TestCase
|
||||
from django.utils import six
|
||||
from django.utils.translation import activate
|
||||
|
||||
from .. import renderers
|
||||
|
|
@ -106,10 +102,7 @@ class NumberRendererTest(TestCase):
|
|||
|
||||
def testEndlessFloat(self):
|
||||
out = self.renderer(1.0 / 3, None)
|
||||
if six.PY2:
|
||||
self.assertEqual('0.333333333333', out)
|
||||
else:
|
||||
self.assertEqual('0.3333333333333333', out)
|
||||
self.assertEqual('0.3333333333333333', out)
|
||||
|
||||
def testPlainDecimal(self):
|
||||
number = '0.123456789123456789123456789'
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from ..core import Admin2
|
|||
from .models import BigThing
|
||||
|
||||
|
||||
class ModelAdmin(object):
|
||||
class ModelAdmin:
|
||||
model_admin_attributes = ['a', 'b', 'c']
|
||||
a = 1 # covered
|
||||
b = 2 # covered
|
||||
|
|
@ -31,9 +31,9 @@ class ImmutableAdminFactoryTests(TestCase):
|
|||
del self.immutable_admin.a
|
||||
|
||||
def test_attributes(self):
|
||||
self.assertEquals(self.immutable_admin.a, 1)
|
||||
self.assertEquals(self.immutable_admin.b, 2)
|
||||
self.assertEquals(self.immutable_admin.c, 3)
|
||||
self.assertEqual(self.immutable_admin.a, 1)
|
||||
self.assertEqual(self.immutable_admin.b, 2)
|
||||
self.assertEqual(self.immutable_admin.c, 3)
|
||||
with self.assertRaises(AttributeError):
|
||||
# 'ImmutableAdmin' object has no attribute 'd'
|
||||
self.immutable_admin.d
|
||||
|
|
@ -73,7 +73,7 @@ class ModelAdminTest(TestCase):
|
|||
admin_instance.get_urls()
|
||||
|
||||
except TypeError as e:
|
||||
message = u"Cannot instantiate admin view " \
|
||||
message = "Cannot instantiate admin view " \
|
||||
'"ModelAdmin2.None". The error that got raised was: ' \
|
||||
"'NoneType' object has no attribute 'as_view'"
|
||||
self.assertEqual(e.args[0], message)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
from django.test import TestCase
|
||||
from django.utils import six
|
||||
|
||||
from .. import utils
|
||||
from ..views import IndexView
|
||||
|
|
@ -12,14 +11,14 @@ class UtilsTest(TestCase):
|
|||
self.instance = UtilsTestModel()
|
||||
|
||||
def test_as_model_class(self):
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
UtilsTestModel._meta,
|
||||
utils.model_options(UtilsTestModel)
|
||||
)
|
||||
UtilsTestModel._meta.verbose_name = "Utils Test Model is singular"
|
||||
UtilsTestModel._meta.verbose_name_plural = "Utils Test Model are " +\
|
||||
" plural"
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
UtilsTestModel._meta,
|
||||
utils.model_options(UtilsTestModel)
|
||||
)
|
||||
|
|
@ -27,14 +26,14 @@ class UtilsTest(TestCase):
|
|||
UtilsTestModel._meta.verbose_name_plural = "Utils Test Models"
|
||||
|
||||
def test_as_model_instance(self):
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
self.instance._meta,
|
||||
utils.model_options(self.instance)
|
||||
)
|
||||
self.instance._meta.verbose_name = "Utils Test Model is singular"
|
||||
self.instance._meta.verbose_name_plural = "Utils Test Model are " +\
|
||||
" plural"
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
self.instance._meta,
|
||||
utils.model_options(self.instance)
|
||||
)
|
||||
|
|
@ -42,117 +41,111 @@ class UtilsTest(TestCase):
|
|||
self.instance._meta.verbose_name_plural = "Utils Test Models"
|
||||
|
||||
def test_admin2_urlname(self):
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
"admin2:None_None_index",
|
||||
utils.admin2_urlname(IndexView, "index")
|
||||
)
|
||||
|
||||
def test_model_app_label_as_model_class(self):
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
UtilsTestModel._meta.app_label,
|
||||
utils.model_app_label(UtilsTestModel)
|
||||
)
|
||||
|
||||
def test_model_app_label_as_model_instance(self):
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
self.instance._meta.app_label,
|
||||
utils.model_app_label(UtilsTestModel)
|
||||
)
|
||||
|
||||
def test_model_verbose_name_as_model_class(self):
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
UtilsTestModel._meta.verbose_name,
|
||||
utils.model_verbose_name(UtilsTestModel)
|
||||
)
|
||||
|
||||
def test_model_verbose_name_as_model_instance(self):
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
self.instance._meta.verbose_name,
|
||||
utils.model_verbose_name(self.instance)
|
||||
)
|
||||
|
||||
def test_model_verbose_name_plural_as_model_class(self):
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
UtilsTestModel._meta.verbose_name_plural,
|
||||
utils.model_verbose_name_plural(UtilsTestModel)
|
||||
)
|
||||
|
||||
def test_model_verbose_name_plural_as_model_instance(self):
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
self.instance._meta.verbose_name_plural,
|
||||
utils.model_verbose_name_plural(self.instance)
|
||||
)
|
||||
|
||||
def test_model_field_verbose_name_autogenerated(self):
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
'field1',
|
||||
utils.model_field_verbose_name(self.instance, 'field1')
|
||||
)
|
||||
|
||||
def test_model_field_verbose_name_overridden(self):
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
'second field',
|
||||
utils.model_field_verbose_name(self.instance, 'field2')
|
||||
)
|
||||
|
||||
def test_model_method_verbose_name(self):
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
'Published recently?',
|
||||
utils.model_method_verbose_name(self.instance, 'was_published_recently')
|
||||
)
|
||||
|
||||
def test_model_method_verbose_name_fallback(self):
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
'simple_method',
|
||||
utils.model_method_verbose_name(self.instance, 'simple_method')
|
||||
)
|
||||
|
||||
def test_app_label_as_model_class(self):
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
UtilsTestModel._meta.app_label,
|
||||
utils.model_app_label(UtilsTestModel)
|
||||
)
|
||||
|
||||
def test_app_label_as_model_instance(self):
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
self.instance._meta.app_label,
|
||||
utils.model_app_label(self.instance)
|
||||
)
|
||||
|
||||
def test_get_attr_callable(self):
|
||||
class Klass(object):
|
||||
class Klass:
|
||||
def hello(self):
|
||||
return "hello"
|
||||
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
utils.get_attr(Klass(), "hello"),
|
||||
"hello"
|
||||
)
|
||||
|
||||
def test_get_attr_str(self):
|
||||
class Klass(object):
|
||||
class Klass:
|
||||
def __str__(self):
|
||||
return "str"
|
||||
|
||||
def __unicode__(self):
|
||||
return "unicode"
|
||||
|
||||
if six.PY2:
|
||||
self.assertEquals(
|
||||
utils.get_attr(Klass(), "__str__"),
|
||||
"unicode"
|
||||
)
|
||||
else:
|
||||
self.assertEquals(
|
||||
utils.get_attr(Klass(), "__str__"),
|
||||
"str"
|
||||
)
|
||||
self.assertEqual(
|
||||
utils.get_attr(Klass(), "__str__"),
|
||||
"str"
|
||||
)
|
||||
|
||||
def test_get_attr(self):
|
||||
class Klass(object):
|
||||
class Klass:
|
||||
attr = "value"
|
||||
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
utils.get_attr(Klass(), "attr"),
|
||||
"value"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
from django.core.urlresolvers import reverse
|
||||
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
|
||||
|
||||
|
|
@ -11,13 +13,13 @@ class AdminViewTest(TestCase):
|
|||
self.admin_view = views.AdminView(r'^$', views.ModelListView, name='admin-view')
|
||||
|
||||
def test_url(self):
|
||||
self.assertEquals(self.admin_view.url, r'^$')
|
||||
self.assertEqual(self.admin_view.url, r'^$')
|
||||
|
||||
def test_view(self):
|
||||
self.assertEquals(self.admin_view.view, views.ModelListView)
|
||||
self.assertEqual(self.admin_view.view, views.ModelListView)
|
||||
|
||||
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')
|
||||
|
|
@ -25,4 +27,4 @@ class CustomLoginViewTest(TestCase):
|
|||
|
||||
def test_view_ok(self):
|
||||
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.urls import include, url
|
||||
from django.conf.urls.static import static
|
||||
from django.urls import re_path
|
||||
|
||||
from djadmin2.site import djadmin2_site
|
||||
|
||||
|
|
@ -12,9 +10,10 @@ from djadmin2.views import LoginView
|
|||
class CustomLoginView(LoginView):
|
||||
default_template_name = "custom_login_template.html"
|
||||
|
||||
|
||||
djadmin2_site.login_view = CustomLoginView
|
||||
djadmin2_site.autodiscover()
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^admin2/', include(djadmin2_site.urls)),
|
||||
re_path(r'^admin2/', djadmin2_site.urls),
|
||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{% extends "djadmin2theme_bootstrap3/base.html" %}
|
||||
{% load i18n staticfiles admin2_tags %}
|
||||
{% load i18n static admin2_tags %}
|
||||
|
||||
{% block navbar %}{% endblock navbar %}
|
||||
{% 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 }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{% 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 %}
|
||||
|
||||
|
|
@ -49,7 +49,7 @@
|
|||
</li>
|
||||
{% endif %}
|
||||
{% 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>
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import division, absolute_import, unicode_literals
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from collections import namedtuple
|
||||
|
||||
import extra_views
|
||||
from django.conf.urls import url
|
||||
from django.core.urlresolvers import reverse
|
||||
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 apiviews
|
||||
|
|
@ -19,14 +14,12 @@ from . import utils
|
|||
from . import views
|
||||
|
||||
|
||||
logger = logging.getLogger('djadmin2')
|
||||
logger = logging.getLogger("djadmin2")
|
||||
|
||||
|
||||
class ModelAdminBase2(type):
|
||||
|
||||
def __new__(cls, name, bases, attrs):
|
||||
new_class = super(ModelAdminBase2, cls).__new__(cls, name,
|
||||
bases, attrs)
|
||||
new_class = super().__new__(cls, name, bases, attrs)
|
||||
view_list = []
|
||||
for key, value in attrs.items():
|
||||
if isinstance(value, views.AdminView):
|
||||
|
|
@ -34,12 +27,12 @@ class ModelAdminBase2(type):
|
|||
value.name = key
|
||||
view_list.append(value)
|
||||
|
||||
view_list.extend(getattr(new_class, 'views', []))
|
||||
view_list.extend(getattr(new_class, "views", []))
|
||||
new_class.views = view_list
|
||||
return new_class
|
||||
|
||||
|
||||
class ModelAdmin2(with_metaclass(ModelAdminBase2)):
|
||||
class ModelAdmin2(metaclass=ModelAdminBase2):
|
||||
"""
|
||||
Adding new ModelAdmin2 attributes:
|
||||
|
||||
|
|
@ -58,9 +51,10 @@ class ModelAdmin2(with_metaclass(ModelAdminBase2)):
|
|||
This prevents us from easily implementing methods/setters which
|
||||
bypass the blocking features of the ImmutableAdmin.
|
||||
"""
|
||||
|
||||
actions_selection_counter = True
|
||||
date_hierarchy = False
|
||||
list_display = ('__str__',)
|
||||
list_display = ("__str__",)
|
||||
list_display_links = ()
|
||||
list_filter = ()
|
||||
list_select_related = False
|
||||
|
|
@ -117,12 +111,20 @@ class ModelAdmin2(with_metaclass(ModelAdminBase2)):
|
|||
inlines = []
|
||||
|
||||
# Views
|
||||
index_view = views.AdminView(r'^$', views.ModelListView, name='index')
|
||||
create_view = views.AdminView(r'^create/$', views.ModelAddFormView, name='create')
|
||||
update_view = views.AdminView(r'^(?P<pk>[0-9]+)/$', views.ModelEditFormView, name='update')
|
||||
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')
|
||||
index_view = views.AdminView(r"^$", views.ModelListView, name="index")
|
||||
create_view = views.AdminView(r"^create/$", views.ModelAddFormView, name="create")
|
||||
update_view = views.AdminView(
|
||||
r"^(?P<pk>[0-9]+)/$", views.ModelEditFormView, name="update"
|
||||
)
|
||||
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 = []
|
||||
|
||||
# API configuration
|
||||
|
|
@ -141,7 +143,7 @@ class ModelAdmin2(with_metaclass(ModelAdminBase2)):
|
|||
self.model_name = model_options.object_name.lower()
|
||||
|
||||
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:
|
||||
self.verbose_name = model_options.verbose_name
|
||||
|
|
@ -150,60 +152,73 @@ class ModelAdmin2(with_metaclass(ModelAdminBase2)):
|
|||
|
||||
def get_default_view_kwargs(self):
|
||||
return {
|
||||
'app_label': self.app_label,
|
||||
'model': self.model,
|
||||
'model_name': self.model_name,
|
||||
'model_admin': immutable_admin_factory(self),
|
||||
"app_label": self.app_label,
|
||||
"model": self.model,
|
||||
"model_name": self.model_name,
|
||||
"model_admin": immutable_admin_factory(self),
|
||||
}
|
||||
|
||||
def get_index_kwargs(self):
|
||||
kwargs = self.get_default_view_kwargs()
|
||||
kwargs.update({
|
||||
'paginate_by': self.list_per_page,
|
||||
})
|
||||
kwargs.update(
|
||||
{
|
||||
"paginate_by": self.list_per_page,
|
||||
}
|
||||
)
|
||||
return kwargs
|
||||
|
||||
def get_default_api_view_kwargs(self):
|
||||
kwargs = self.get_default_view_kwargs()
|
||||
kwargs.update({
|
||||
'serializer_class': self.api_serializer_class,
|
||||
})
|
||||
kwargs.update(
|
||||
{
|
||||
"serializer_class": self.api_serializer_class,
|
||||
}
|
||||
)
|
||||
return kwargs
|
||||
|
||||
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):
|
||||
kwargs = self.get_default_view_kwargs()
|
||||
kwargs.update({
|
||||
'inlines': self.inlines,
|
||||
'form_class': (self.create_form_class if
|
||||
self.create_form_class else self.form_class),
|
||||
})
|
||||
kwargs.update(
|
||||
{
|
||||
"inlines": self.inlines,
|
||||
"form_class": (
|
||||
self.create_form_class
|
||||
if self.create_form_class
|
||||
else self.form_class
|
||||
),
|
||||
}
|
||||
)
|
||||
return kwargs
|
||||
|
||||
def get_update_kwargs(self):
|
||||
kwargs = self.get_default_view_kwargs()
|
||||
form_class = (self.update_form_class if
|
||||
self.update_form_class else self.form_class)
|
||||
form_class = (
|
||||
self.update_form_class if self.update_form_class else self.form_class
|
||||
)
|
||||
if form_class is None:
|
||||
form_class = modelform_factory(self.model, fields='__all__')
|
||||
kwargs.update({
|
||||
'inlines': self.inlines,
|
||||
'form_class': form_class,
|
||||
})
|
||||
form_class = modelform_factory(self.model, fields="__all__")
|
||||
kwargs.update(
|
||||
{
|
||||
"inlines": self.inlines,
|
||||
"form_class": form_class,
|
||||
}
|
||||
)
|
||||
return kwargs
|
||||
|
||||
def get_index_url(self):
|
||||
return reverse('admin2:{}'.format(
|
||||
self.get_prefixed_view_name('index')))
|
||||
return reverse("admin2:{}".format(self.get_prefixed_view_name("index")))
|
||||
|
||||
def get_api_list_kwargs(self):
|
||||
kwargs = self.get_default_api_view_kwargs()
|
||||
kwargs.update({
|
||||
'queryset': self.model.objects.all(),
|
||||
# 'paginate_by': self.list_per_page,
|
||||
})
|
||||
kwargs.update(
|
||||
{
|
||||
"queryset": self.model.objects.all(),
|
||||
# 'paginate_by': self.list_per_page,
|
||||
}
|
||||
)
|
||||
return kwargs
|
||||
|
||||
def get_api_detail_kwargs(self):
|
||||
|
|
@ -222,34 +237,35 @@ class ModelAdmin2(with_metaclass(ModelAdminBase2)):
|
|||
trace = sys.exc_info()[2]
|
||||
new_exception = TypeError(
|
||||
'Cannot instantiate admin view "{}.{}". '
|
||||
'The error that got raised was: {}'.format(
|
||||
self.__class__.__name__, admin_view.name, e))
|
||||
"The error that got raised was: {}".format(
|
||||
self.__class__.__name__, admin_view.name, e
|
||||
)
|
||||
)
|
||||
try:
|
||||
raise new_exception.with_traceback(trace)
|
||||
except AttributeError:
|
||||
raise (new_exception, None, trace)
|
||||
|
||||
pattern_list.append(
|
||||
url(
|
||||
regex=admin_view.url,
|
||||
re_path(
|
||||
admin_view.url,
|
||||
view=view_instance,
|
||||
name=self.get_prefixed_view_name(admin_view.name)
|
||||
name=self.get_prefixed_view_name(admin_view.name),
|
||||
)
|
||||
)
|
||||
return pattern_list
|
||||
|
||||
def get_api_urls(self):
|
||||
return [
|
||||
url(
|
||||
regex=r'^$',
|
||||
re_path(
|
||||
r"^$",
|
||||
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(
|
||||
regex=r'^(?P<pk>[0-9]+)/$',
|
||||
view=self.api_detail_view.as_view(
|
||||
**self.get_api_detail_kwargs()),
|
||||
name=self.get_prefixed_view_name('api_detail'),
|
||||
re_path(
|
||||
r"^(?P<pk>[0-9]+)/$",
|
||||
view=self.api_detail_view.as_view(**self.get_api_detail_kwargs()),
|
||||
name=self.get_prefixed_view_name("api_detail"),
|
||||
),
|
||||
]
|
||||
|
||||
|
|
@ -266,12 +282,12 @@ class ModelAdmin2(with_metaclass(ModelAdminBase2)):
|
|||
actions_dict = {}
|
||||
|
||||
for cls in type(self).mro()[::-1]:
|
||||
class_actions = getattr(cls, 'list_actions', [])
|
||||
class_actions = getattr(cls, "list_actions", [])
|
||||
for action in class_actions:
|
||||
actions_dict[action.__name__] = {
|
||||
'name': action.__name__,
|
||||
'description': actions.get_description(action),
|
||||
'action_callable': action
|
||||
"name": action.__name__,
|
||||
"description": actions.get_description(action),
|
||||
"action_callable": action,
|
||||
}
|
||||
return actions_dict
|
||||
|
||||
|
|
@ -279,19 +295,21 @@ class ModelAdmin2(with_metaclass(ModelAdminBase2)):
|
|||
return self.ordering
|
||||
|
||||
|
||||
class Admin2Inline(extra_views.InlineFormSet):
|
||||
class Admin2Inline(extra_views.InlineFormSetFactory):
|
||||
"""
|
||||
A simple extension of django-extra-view's InlineFormSet that
|
||||
adds some useful functionality.
|
||||
"""
|
||||
|
||||
template = None
|
||||
fields = "__all__"
|
||||
|
||||
def construct_formset(self):
|
||||
"""
|
||||
Overrides construct_formset to attach the model class as
|
||||
an attribute of the returned formset instance.
|
||||
"""
|
||||
formset = super(Admin2Inline, self).construct_formset()
|
||||
formset = super().construct_formset()
|
||||
formset.model = self.inline_model
|
||||
formset.template = self.template
|
||||
return formset
|
||||
|
|
@ -299,12 +317,14 @@ class Admin2Inline(extra_views.InlineFormSet):
|
|||
|
||||
class Admin2TabularInline(Admin2Inline):
|
||||
template = os.path.join(
|
||||
settings.ADMIN2_THEME_DIRECTORY, 'edit_inlines/tabular.html')
|
||||
settings.ADMIN2_THEME_DIRECTORY, "edit_inlines/tabular.html"
|
||||
)
|
||||
|
||||
|
||||
class Admin2StackedInline(Admin2Inline):
|
||||
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):
|
||||
|
|
@ -318,8 +338,7 @@ def immutable_admin_factory(model_admin):
|
|||
the result, but hopefully developers attempting that
|
||||
'workaround/hack' will read our documentation.
|
||||
"""
|
||||
ImmutableAdmin = namedtuple('ImmutableAdmin',
|
||||
model_admin.model_admin_attributes,
|
||||
verbose=False)
|
||||
return ImmutableAdmin(*[getattr(
|
||||
model_admin, x) for x in model_admin.model_admin_attributes])
|
||||
ImmutableAdmin = namedtuple("ImmutableAdmin", 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 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.sql.constants import QUERY_TERMS
|
||||
from django.utils import six
|
||||
from django.utils.encoding import force_bytes, force_text
|
||||
from django.utils.encoding import force_bytes, force_str
|
||||
|
||||
|
||||
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
|
||||
"""
|
||||
|
||||
lookup_fields = lookup_path.split('__')
|
||||
# Remove the last item of the lookup path if it is a query term
|
||||
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
|
||||
lookup_fields = lookup_path.split(LOOKUP_SEP)
|
||||
# Go through the fields (following all relations) and look for an m2m.
|
||||
for field_name in lookup_fields:
|
||||
field = opts.get_field(field_name)
|
||||
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 we know we need to call distinct
|
||||
return True
|
||||
if field_name == 'pk':
|
||||
field_name = opts.pk.name
|
||||
try:
|
||||
field = opts.get_field(field_name)
|
||||
except FieldDoesNotExist:
|
||||
# Ignore query lookups.
|
||||
continue
|
||||
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
|
||||
|
||||
|
||||
|
|
@ -118,7 +119,7 @@ class NestedObjects(Collector):
|
|||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(NestedObjects, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.edges = {} # {from_instance: [to_instances]}
|
||||
self.protected = set()
|
||||
self.model_objs = defaultdict(set)
|
||||
|
|
@ -138,13 +139,21 @@ class NestedObjects(Collector):
|
|||
self.add_edge(None, obj)
|
||||
self.model_objs[obj._meta.model].add(obj)
|
||||
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:
|
||||
self.protected.update(e.protected_objects)
|
||||
|
||||
def related_objects(self, related, objs):
|
||||
qs = super(NestedObjects, self).related_objects(related, objs)
|
||||
return qs.select_related(related.field.name)
|
||||
def related_objects(self, *args):
|
||||
# Django >= 3.1
|
||||
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):
|
||||
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
|
||||
"""
|
||||
if not isinstance(s, six.string_types):
|
||||
if not isinstance(s, str):
|
||||
return s
|
||||
res = list(s)
|
||||
for i in range(len(res)):
|
||||
|
|
@ -202,7 +211,4 @@ def quote(s):
|
|||
|
||||
|
||||
def type_str(text):
|
||||
if six.PY2:
|
||||
return force_bytes(text)
|
||||
else:
|
||||
return force_text(text)
|
||||
return force_str(text)
|
||||
|
|
|
|||
|
|
@ -1,16 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import division, absolute_import, unicode_literals
|
||||
|
||||
import os
|
||||
|
||||
from django.contrib.auth.views import redirect_to_login
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.urlresolvers import reverse, reverse_lazy
|
||||
from django.forms.models import modelform_factory
|
||||
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.translation import ugettext as _
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
# braces 1.3 views exported AccessMixin
|
||||
# in braces 1.4 this was moved views._access and not exported in views
|
||||
|
|
@ -34,7 +31,7 @@ class PermissionMixin(AccessMixin):
|
|||
self.permissions = [
|
||||
permission_class()
|
||||
for permission_class in self.permission_classes]
|
||||
super(PermissionMixin, self).__init__(**kwargs)
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def has_permission(self, obj=None):
|
||||
'''
|
||||
|
|
@ -58,10 +55,10 @@ class PermissionMixin(AccessMixin):
|
|||
request.get_full_path(),
|
||||
self.get_login_url(),
|
||||
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):
|
||||
context = super(PermissionMixin, self).get_context_data(**kwargs)
|
||||
context = super().get_context_data(**kwargs)
|
||||
permission_checker = permissions.TemplatePermissionChecker(
|
||||
self.request, self.model_admin)
|
||||
context.update({
|
||||
|
|
@ -84,6 +81,9 @@ class Admin2Mixin(PermissionMixin):
|
|||
return [os.path.join(
|
||||
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):
|
||||
return self.model
|
||||
|
||||
|
|
@ -111,14 +111,14 @@ class Admin2Mixin(PermissionMixin):
|
|||
}
|
||||
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):
|
||||
model_admin = None
|
||||
|
||||
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_meta = model_options(model)
|
||||
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__')
|
||||
|
||||
|
||||
class Admin2ModelFormMixin(object):
|
||||
class Admin2ModelFormMixin:
|
||||
def get_success_url(self):
|
||||
if '_continue' in self.request.POST:
|
||||
view_name = admin2_urlname(self, 'update')
|
||||
|
|
@ -167,19 +167,19 @@ class Admin2ModelFormMixin(object):
|
|||
for added_object in formset.new_objects:
|
||||
change_message.append(
|
||||
_('Added {0} "{1}".'.format(
|
||||
force_text(added_object._meta.verbose_name),
|
||||
force_text(added_object))))
|
||||
force_str(added_object._meta.verbose_name),
|
||||
force_str(added_object))))
|
||||
for changed_object, changed_fields in formset.changed_objects:
|
||||
change_message.append(
|
||||
_('Changed {0} for {1} "{2}".'.format(
|
||||
get_text_list(changed_fields, _('and')),
|
||||
force_text(changed_object._meta.verbose_name),
|
||||
force_text(changed_object))))
|
||||
force_str(changed_object._meta.verbose_name),
|
||||
force_str(changed_object))))
|
||||
for deleted_object in formset.deleted_objects:
|
||||
change_message.append(
|
||||
_('Deleted {0} "{1}".'.format(
|
||||
force_text(deleted_object._meta.verbose_name),
|
||||
force_text(deleted_object))))
|
||||
force_str(deleted_object._meta.verbose_name),
|
||||
force_str(deleted_object))))
|
||||
|
||||
change_message = ' '.join(change_message)
|
||||
return change_message or _('No fields changed.')
|
||||
|
|
|
|||
|
|
@ -1,26 +1,24 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import division, absolute_import, unicode_literals
|
||||
|
||||
import operator
|
||||
from datetime import datetime
|
||||
from functools import reduce
|
||||
|
||||
import extra_views
|
||||
from django.core.exceptions import FieldDoesNotExist
|
||||
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,
|
||||
AdminPasswordChangeForm)
|
||||
from django.contrib.auth.views import (logout as auth_logout,
|
||||
login as auth_login)
|
||||
from django.contrib.auth.views import LoginView as DjangoLoginView
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.urlresolvers import reverse, reverse_lazy
|
||||
from django.db import models, router
|
||||
from django.db.models.fields import FieldDoesNotExist
|
||||
from django.http import HttpResponseRedirect
|
||||
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.translation import ugettext_lazy
|
||||
from django.utils.translation import gettext_lazy
|
||||
from django.urls import reverse, reverse_lazy
|
||||
|
||||
from django.views import generic
|
||||
|
||||
from . import permissions, utils
|
||||
|
|
@ -30,7 +28,7 @@ from .models import LogEntry
|
|||
from .viewmixins import Admin2Mixin, Admin2ModelMixin, Admin2ModelFormMixin
|
||||
|
||||
|
||||
class AdminView(object):
|
||||
class AdminView:
|
||||
|
||||
def __init__(self, url, view, name=None):
|
||||
self.url = url
|
||||
|
|
@ -63,7 +61,7 @@ class IndexView(Admin2Mixin, generic.TemplateView):
|
|||
app_verbose_names = None
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
data = super(IndexView, self).get_context_data(**kwargs)
|
||||
data = super().get_context_data(**kwargs)
|
||||
data.update({
|
||||
'apps': self.apps,
|
||||
'app_verbose_names': self.app_verbose_names,
|
||||
|
|
@ -90,7 +88,7 @@ class AppIndexView(Admin2Mixin, generic.TemplateView):
|
|||
app_verbose_names = None
|
||||
|
||||
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']
|
||||
registry = self.apps[app_label]
|
||||
data.update({
|
||||
|
|
@ -115,6 +113,7 @@ class ModelListView(Admin2ModelMixin, generic.ListView):
|
|||
app name.
|
||||
"""
|
||||
default_template_name = "model_list.html"
|
||||
paginate_by = 10
|
||||
permission_classes = (
|
||||
permissions.IsStaffPermission,
|
||||
permissions.ModelViewPermission)
|
||||
|
|
@ -173,7 +172,7 @@ class ModelListView(Admin2ModelMixin, generic.ListView):
|
|||
return queryset, use_distinct
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super(ModelListView, self).get_queryset()
|
||||
queryset = super().get_queryset()
|
||||
search_term = self.request.GET.get('q', None)
|
||||
search_use_distinct = False
|
||||
if self.model_admin.search_fields and search_term:
|
||||
|
|
@ -247,7 +246,7 @@ class ModelListView(Admin2ModelMixin, generic.ListView):
|
|||
return self._date_filter
|
||||
|
||||
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['actions'] = self.get_actions().values()
|
||||
context['search_fields'] = self.get_search_fields()
|
||||
|
|
@ -283,7 +282,7 @@ class ModelListView(Admin2ModelMixin, generic.ListView):
|
|||
elif year:
|
||||
context["previous_date"] = {
|
||||
"link": "?",
|
||||
"text": ugettext_lazy("‹ All dates"),
|
||||
"text": gettext_lazy("‹ All dates"),
|
||||
}
|
||||
|
||||
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['model'] = self.get_model()
|
||||
context['action'] = "Change"
|
||||
context['action_name'] = ugettext_lazy("Change")
|
||||
context['action_name'] = gettext_lazy("Change")
|
||||
return context
|
||||
|
||||
def forms_valid(self, form, inlines):
|
||||
|
|
@ -420,14 +419,14 @@ class ModelAddFormView(Admin2ModelMixin, Admin2ModelFormMixin,
|
|||
permissions.ModelAddPermission)
|
||||
|
||||
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['action'] = "Add"
|
||||
context['action_name'] = ugettext_lazy("Add")
|
||||
context['action_name'] = gettext_lazy("Add")
|
||||
return context
|
||||
|
||||
def forms_valid(self, form, inlines):
|
||||
response = super(ModelAddFormView, self).forms_valid(form, inlines)
|
||||
response = super().forms_valid(form, inlines)
|
||||
LogEntry.objects.log_action(
|
||||
self.request.user.id,
|
||||
self.object,
|
||||
|
|
@ -456,12 +455,12 @@ class ModelDeleteView(Admin2ModelMixin, generic.DeleteView):
|
|||
permissions.ModelDeletePermission)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(ModelDeleteView, self).get_context_data(**kwargs)
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
def _format_callback(obj):
|
||||
opts = utils.model_options(obj)
|
||||
return '%s: %s' % (force_text(capfirst(opts.verbose_name)),
|
||||
force_text(obj))
|
||||
return '%s: %s' % (force_str(capfirst(opts.verbose_name)),
|
||||
force_str(obj))
|
||||
|
||||
using = router.db_for_write(self.get_object()._meta.model)
|
||||
collector = utils.NestedObjects(using=using)
|
||||
|
|
@ -477,7 +476,7 @@ class ModelDeleteView(Admin2ModelMixin, generic.DeleteView):
|
|||
self.get_object(),
|
||||
LogEntry.DELETION,
|
||||
'Object deleted.')
|
||||
return super(ModelDeleteView, self).delete(request, *args, **kwargs)
|
||||
return super().delete(request, *args, **kwargs)
|
||||
|
||||
|
||||
class ModelHistoryView(Admin2ModelMixin, generic.ListView):
|
||||
|
|
@ -493,13 +492,14 @@ class ModelHistoryView(Admin2ModelMixin, generic.ListView):
|
|||
app name.
|
||||
"""
|
||||
default_template_name = "model_history.html"
|
||||
paginate_by = 10
|
||||
permission_classes = (
|
||||
permissions.IsStaffPermission,
|
||||
permissions.ModelChangePermission
|
||||
)
|
||||
|
||||
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['object'] = self.get_object()
|
||||
return context
|
||||
|
|
@ -536,7 +536,7 @@ class PasswordChangeView(Admin2Mixin, generic.UpdateView):
|
|||
def get_form_class(self):
|
||||
if self.request.user == self.get_object():
|
||||
return self.admin_form_class
|
||||
return super(PasswordChangeView, self).get_form_class()
|
||||
return super().get_form_class()
|
||||
|
||||
def get_queryset(self):
|
||||
from django.contrib.auth import get_user_model
|
||||
|
|
@ -564,10 +564,7 @@ class LoginView(Admin2Mixin, generic.TemplateView):
|
|||
authentication_form = AdminAuthenticationForm
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
return auth_login(request,
|
||||
authentication_form=self.authentication_form,
|
||||
template_name=self.get_template_names(),
|
||||
*args, **kwargs)
|
||||
return DjangoLoginView.as_view(template_name=self.get_templates(), authentication_form=self.authentication_form, *args, **kwargs)(request)
|
||||
|
||||
|
||||
class LogoutView(Admin2Mixin, generic.TemplateView):
|
||||
|
|
@ -579,5 +576,6 @@ class LogoutView(Admin2Mixin, generic.TemplateView):
|
|||
default_template_name = 'auth/logout.html'
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
return auth_logout(request, template_name=self.get_template_names(),
|
||||
*args, **kwargs)
|
||||
auth_logout(request)
|
||||
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
|
||||
from sphinx import addnodes
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# django-admin2 documentation build configuration file, created by
|
||||
# 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
|
||||
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.
|
||||
Be sure to change from the default of ``master`` to ``develop``.
|
||||
Your pull request should be applied to the **main** branch of django-admin2.
|
||||
|
||||
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
|
||||
|
|
@ -132,8 +131,8 @@ Pull upstream changes into your fork regularly
|
|||
|
||||
To pull in upstream changes::
|
||||
|
||||
git remote add upstream https://github.com/twoscoops/django-admin2.git
|
||||
git pull upstream develop
|
||||
git remote add upstream https://github.com/jazzband/django-admin2.git
|
||||
git pull upstream main
|
||||
|
||||
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::
|
||||
|
||||
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
|
||||
|
||||
|
|
@ -262,8 +261,8 @@ How pull requests are checked, tested, and done
|
|||
|
||||
First we pull the code into a local branch::
|
||||
|
||||
git checkout develop
|
||||
git checkout -b <submitter-github-name>-<submitter-branch> develop
|
||||
git checkout main
|
||||
git checkout -b <submitter-github-name>-<submitter-branch> main
|
||||
git pull git://github.com/<submitter-github-name>/django-admin2.git <submitter-branch> <branch-name>
|
||||
|
||||
Then we run the tests::
|
||||
|
|
@ -279,11 +278,11 @@ We do the following:
|
|||
|
||||
We finish with a merge and push to GitHub::
|
||||
|
||||
git checkout develop
|
||||
git checkout main
|
||||
git merge <branch-name>
|
||||
git push origin develop
|
||||
git push origin main
|
||||
|
||||
.. _installation: install.html
|
||||
.. _GitHub project: https://github.com/twoscoops/django-admin2
|
||||
.. _issue tracker: https://github.com/twoscoops/django-admin2/issues
|
||||
.. _GitHub project: https://github.com/jazzband/django-admin2
|
||||
.. _issue tracker: https://github.com/jazzband/django-admin2/issues
|
||||
.. _pydanny: http://pydanny.com
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
Welcome to django-admin2's documentation!
|
||||
=========================================
|
||||
|
||||
.. image:: https://travis-ci.org/jazzband/django-admin2.png
|
||||
:alt: Build Status
|
||||
:target: https://travis-ci.org/jazzband/django-admin2
|
||||
.. image:: https://github.com/jazzband/django-admin2/workflows/Test/badge.svg
|
||||
:target: https://github.com/jazzband/django-admin2/actions
|
||||
:alt: GitHub Actions
|
||||
|
||||
**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**
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from setuptools import setup
|
||||
import re
|
||||
|
|
|
|||
|
|
@ -1,25 +1,23 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import division, absolute_import, unicode_literals
|
||||
|
||||
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.actions import BaseListAction
|
||||
|
||||
|
||||
|
||||
class CustomPublishAction(BaseListAction):
|
||||
|
||||
permission_classes = BaseListAction.permission_classes + (
|
||||
permissions.ModelChangePermission,
|
||||
)
|
||||
|
||||
description = ugettext_lazy('Publish selected items')
|
||||
success_message = pgettext_lazy('singular form',
|
||||
'Successfully published %(count)s %(items)s')
|
||||
success_message_plural = pgettext_lazy('plural form',
|
||||
'Successfully published %(count)s %(items)s')
|
||||
description = gettext_lazy('Publish selected items')
|
||||
success_message = pgettext_lazy(
|
||||
'singular form',
|
||||
'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"
|
||||
|
||||
|
|
@ -32,7 +30,7 @@ class PublishAllItemsAction(BaseListAction):
|
|||
permissions.ModelChangePermission,
|
||||
)
|
||||
|
||||
description = ugettext_lazy('Publish all items')
|
||||
description = gettext_lazy('Publish all items')
|
||||
success_message = pgettext_lazy(
|
||||
'singular form',
|
||||
'Successfully published %(count)s %(items)s',
|
||||
|
|
@ -52,10 +50,12 @@ class PublishAllItemsAction(BaseListAction):
|
|||
|
||||
def unpublish_items(request, queryset):
|
||||
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
|
||||
unpublish_items.description = ugettext_lazy('Unpublish selected items')
|
||||
unpublish_items.description = gettext_lazy('Unpublish selected items')
|
||||
|
||||
|
||||
def unpublish_all_items(request, queryset):
|
||||
|
|
@ -63,8 +63,9 @@ def unpublish_all_items(request, queryset):
|
|||
messages.add_message(
|
||||
request,
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import division, absolute_import, unicode_literals
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import Post, Comment
|
||||
|
|
@ -16,5 +13,6 @@ class PostAdmin(admin.ModelAdmin):
|
|||
list_filter = ['published', 'title']
|
||||
date_hierarchy = "published_date"
|
||||
|
||||
|
||||
admin.site.register(Post, PostAdmin)
|
||||
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.actions import DeleteSelectedAction
|
||||
|
|
@ -32,7 +30,7 @@ class PostAdmin(ModelAdmin2):
|
|||
}
|
||||
save_on_top = True
|
||||
date_hierarchy = "published_date"
|
||||
ordering = ["-published_date", "title",]
|
||||
ordering = ["-published_date", "title", ]
|
||||
|
||||
|
||||
class CommentAdmin(ModelAdmin2):
|
||||
|
|
@ -42,13 +40,13 @@ class CommentAdmin(ModelAdmin2):
|
|||
actions_on_bottom = True
|
||||
actions_selection_counter = False
|
||||
|
||||
|
||||
# Register the blog app with a verbose name
|
||||
djadmin2_site.register_app_verbose_name(
|
||||
'blog',
|
||||
ugettext_lazy('My Blog')
|
||||
gettext_lazy('My Blog')
|
||||
)
|
||||
|
||||
# Register each model with the admin
|
||||
djadmin2_site.register(Post, PostAdmin)
|
||||
djadmin2_site.register(Comment, CommentAdmin)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
|
@ -27,7 +24,8 @@ class Migration(migrations.Migration):
|
|||
fields=[
|
||||
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
|
||||
('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(
|
||||
|
|
@ -49,7 +47,9 @@ class Migration(migrations.Migration):
|
|||
fields=[
|
||||
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('event', models.OneToOneField(to='blog.Event')),
|
||||
('event', models.OneToOneField(
|
||||
to='blog.Event',
|
||||
on_delete=models.CASCADE)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'awesome guest',
|
||||
|
|
@ -59,7 +59,10 @@ class Migration(migrations.Migration):
|
|||
name='Location',
|
||||
fields=[
|
||||
('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(
|
||||
|
|
@ -79,6 +82,10 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='comment',
|
||||
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.utils import six
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Post(models.Model):
|
||||
title = models.CharField(max_length=255, verbose_name=_('title'))
|
||||
body = models.TextField(verbose_name=_('body'))
|
||||
|
|
@ -22,10 +16,10 @@ class Post(models.Model):
|
|||
verbose_name_plural = _('posts')
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Comment(models.Model):
|
||||
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'))
|
||||
|
||||
def __str__(self):
|
||||
|
|
@ -38,13 +32,12 @@ class Comment(models.Model):
|
|||
|
||||
# Models needed for testing NestedObjects
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Count(models.Model):
|
||||
num = models.PositiveSmallIntegerField()
|
||||
parent = models.ForeignKey('self', null=True)
|
||||
parent = models.ForeignKey('self', null=True, on_delete=models.CASCADE)
|
||||
|
||||
def __str__(self):
|
||||
return six.text_type(self.num)
|
||||
return str(self.num)
|
||||
|
||||
|
||||
class Event(models.Model):
|
||||
|
|
@ -52,11 +45,14 @@ class Event(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):
|
||||
event = models.OneToOneField(Event)
|
||||
event = models.OneToOneField(Event, on_delete=models.CASCADE)
|
||||
name = models.CharField(max_length=255)
|
||||
|
||||
class Meta:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
{% load i18n %}{% load staticfiles %}<!DOCTYPE html>
|
||||
{% load i18n %}{% load static %}<!DOCTYPE html>
|
||||
<html lang="{{ request.LANGUAGE_CODE }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n staticfiles %}
|
||||
{% load i18n static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{% extends "base.html" %}
|
||||
{% load staticfiles i18n %}
|
||||
{% load static i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
|
||||
from django.contrib.auth.models import AnonymousUser, User
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test import TestCase
|
||||
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.site import djadmin2_site
|
||||
|
|
@ -32,14 +30,16 @@ class IndexAPIViewTest(APITestCase):
|
|||
def test_response_ok(self):
|
||||
request = self.factory.get(reverse('admin2:api_index'))
|
||||
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)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_view_permission(self):
|
||||
request = self.factory.get(reverse('admin2:api_index'))
|
||||
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)
|
||||
|
||||
|
||||
|
|
@ -72,7 +72,7 @@ class ListCreateAPIViewTest(APITestCase):
|
|||
response.render()
|
||||
|
||||
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):
|
||||
request = self.factory.get(reverse('admin2:blog_post_api_list'))
|
||||
|
|
@ -84,7 +84,7 @@ class ListCreateAPIViewTest(APITestCase):
|
|||
response.render()
|
||||
|
||||
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)
|
||||
# next and previous fields exist, but are null because we have no
|
||||
# content
|
||||
|
|
@ -99,7 +99,7 @@ class RetrieveUpdateDestroyAPIViewTest(APITestCase):
|
|||
post = Post.objects.create(title='Foo', body='Bar')
|
||||
request = self.factory.get(
|
||||
reverse('admin2:blog_post_api_detail',
|
||||
kwargs={'pk': post.pk}))
|
||||
kwargs={'pk': post.pk}))
|
||||
request.user = self.user
|
||||
model_admin = self.get_model_admin(Post)
|
||||
view = apiviews.RetrieveUpdateDestroyAPIView.as_view(
|
||||
|
|
@ -111,7 +111,7 @@ class RetrieveUpdateDestroyAPIViewTest(APITestCase):
|
|||
post = Post.objects.create(title='Foo', body='Bar')
|
||||
request = self.factory.get(
|
||||
reverse('admin2:blog_post_api_detail',
|
||||
kwargs={'pk': post.pk}))
|
||||
kwargs={'pk': post.pk}))
|
||||
request.user = AnonymousUser()
|
||||
model_admin = self.get_model_admin(Post)
|
||||
view = apiviews.RetrieveUpdateDestroyAPIView.as_view(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
from django.contrib.auth.models import Group, User
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.urls import reverse
|
||||
|
||||
from .test_apiviews import APITestCase
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# vim:fenc=utf-8
|
||||
|
||||
import django_filters
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
from django.urls import reverse
|
||||
|
||||
from djadmin2 import filters as djadmin2_filters
|
||||
from djadmin2.types import ModelAdmin2
|
||||
|
|
@ -20,10 +17,9 @@ class ListFilterBuilderTest(TestCase):
|
|||
class PostAdminSimple(ModelAdmin2):
|
||||
list_filter = ['published', ]
|
||||
|
||||
|
||||
class PostAdminWithFilterInstances(ModelAdmin2):
|
||||
list_filter = [
|
||||
django_filters.BooleanFilter(name='published'),
|
||||
django_filters.BooleanFilter(field_name='published'),
|
||||
]
|
||||
|
||||
class FS(django_filters.FilterSet):
|
||||
|
|
@ -31,7 +27,6 @@ class ListFilterBuilderTest(TestCase):
|
|||
model = Post
|
||||
fields = ['published']
|
||||
|
||||
|
||||
class PostAdminWithFilterSetInst(ModelAdmin2):
|
||||
list_filter = FS
|
||||
|
||||
|
|
@ -46,19 +41,11 @@ class ListFilterBuilderTest(TestCase):
|
|||
self.assertTrue(
|
||||
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(
|
||||
request,
|
||||
PostAdminWithFilterInstances,
|
||||
Post.objects.all(),
|
||||
)
|
||||
self.assertNotEqual(
|
||||
list_filter_inst.filters['published'].widget,
|
||||
djadmin2_filters.NullBooleanLinksWidget,
|
||||
)
|
||||
list_filter_inst = djadmin2_filters.build_list_filter(
|
||||
request,
|
||||
PostAdminWithFilterSetInst,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django import forms
|
||||
from django.forms import modelform_factory
|
||||
from django.test import TestCase
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
from blog.models import Post
|
||||
from django.contrib.auth.models import User, Permission
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.template import Template, Context
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
from django.urls import reverse
|
||||
|
||||
from djadmin2.permissions import TemplatePermissionChecker
|
||||
from djadmin2.site import djadmin2_site
|
||||
|
|
@ -71,11 +71,14 @@ class TemplatePermissionTest(TestCase):
|
|||
|
||||
result = self.render('{{ permissions.has_add_permission }}', context)
|
||||
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')
|
||||
result = self.render('{{ permissions.blog_post.has_change_permission }}', context)
|
||||
result = self.render(
|
||||
'{{ permissions.blog_post.has_change_permission }}', context)
|
||||
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')
|
||||
|
||||
result = self.render(
|
||||
|
|
@ -112,7 +115,7 @@ class TemplatePermissionTest(TestCase):
|
|||
'{% load admin2_tags %}'
|
||||
'{{ permissions.has_add_permission }}'
|
||||
'{% with permissions|for_admin:post_admin as permissions %}'
|
||||
'{{ permissions.has_add_permission }}'
|
||||
'{{ permissions.has_add_permission }}'
|
||||
'{% endwith %}',
|
||||
context)
|
||||
self.assertEqual(result, 'FalseFalse')
|
||||
|
|
@ -131,7 +134,7 @@ class TemplatePermissionTest(TestCase):
|
|||
'{% load admin2_tags %}'
|
||||
'{{ permissions.has_add_permission }}'
|
||||
'{% with permissions|for_admin:post_admin as permissions %}'
|
||||
'{{ permissions.has_add_permission }}'
|
||||
'{{ permissions.has_add_permission }}'
|
||||
'{% endwith %}'
|
||||
'{{ permissions.blog_post.has_add_permission }}',
|
||||
context)
|
||||
|
|
@ -141,7 +144,7 @@ class TemplatePermissionTest(TestCase):
|
|||
result = self.render(
|
||||
'{% load admin2_tags %}'
|
||||
'{% with permissions|for_admin:"blog_post" as permissions %}'
|
||||
'{{ permissions.has_add_permission }}'
|
||||
'{{ permissions.has_add_permission }}'
|
||||
'{% endwith %}',
|
||||
context)
|
||||
self.assertEqual(result, 'True')
|
||||
|
|
@ -150,7 +153,7 @@ class TemplatePermissionTest(TestCase):
|
|||
result = self.render(
|
||||
'{% load admin2_tags %}'
|
||||
'{% with permissions|for_admin:"invalid_admin_name" as permissions %}'
|
||||
'{{ permissions.has_add_permission }}'
|
||||
'{{ permissions.has_add_permission }}'
|
||||
'{% endwith %}',
|
||||
context)
|
||||
self.assertEqual(result, '')
|
||||
|
|
@ -186,8 +189,8 @@ class TemplatePermissionTest(TestCase):
|
|||
# user add permission
|
||||
'{{ permissions.has_add_permission }}'
|
||||
'{% with permissions|for_admin:"blog_post"|for_view:"add" as post_add_perm %}'
|
||||
# post add permission
|
||||
'{{ post_add_perm }}'
|
||||
# post add permission
|
||||
'{{ post_add_perm }}'
|
||||
'{% endwith %}',
|
||||
context)
|
||||
self.assertEqual(result, 'FalseFalse')
|
||||
|
|
@ -212,8 +215,8 @@ class TemplatePermissionTest(TestCase):
|
|||
# user add permission
|
||||
'{{ permissions.has_add_permission }}'
|
||||
'{% with permissions|for_admin:"blog_post"|for_view:"add" as post_add_perm %}'
|
||||
# post add permission
|
||||
'{{ post_add_perm }}'
|
||||
# post add permission
|
||||
'{{ post_add_perm }}'
|
||||
'{% endwith %}'
|
||||
# user change permission
|
||||
'{{ permissions|for_view:"change" }}',
|
||||
|
|
@ -224,13 +227,13 @@ class TemplatePermissionTest(TestCase):
|
|||
result = self.render(
|
||||
'{% load admin2_tags %}'
|
||||
'{% with permissions|for_view:"change" as user_change_perm %}'
|
||||
'1{{ user_change_perm }}'
|
||||
'2{{ user_change_perm|for_view:"add" }}'
|
||||
# this shouldn't return True or False but '' since the
|
||||
# previously bound change view doesn't belong to the newly
|
||||
# bound blog_post admin
|
||||
'3{{ user_change_perm|for_admin:"blog_post" }}'
|
||||
'4{{ user_change_perm|for_admin:"blog_post"|for_view:"add" }}'
|
||||
'1{{ user_change_perm }}'
|
||||
'2{{ user_change_perm|for_view:"add" }}'
|
||||
# this shouldn't return True or False but '' since the
|
||||
# previously bound change view doesn't belong to the newly
|
||||
# bound blog_post admin
|
||||
'3{{ user_change_perm|for_admin:"blog_post" }}'
|
||||
'4{{ user_change_perm|for_admin:"blog_post"|for_view:"add" }}'
|
||||
'{% endwith %}',
|
||||
context)
|
||||
self.assertEqual(result, '1True2False34True')
|
||||
|
|
@ -282,7 +285,7 @@ class TemplatePermissionTest(TestCase):
|
|||
'{% load admin2_tags %}'
|
||||
'{{ permissions.has_add_permission }}'
|
||||
'{% with permissions|for_object:post as permissions %}'
|
||||
'{{ permissions.has_add_permission }}'
|
||||
'{{ permissions.has_add_permission }}'
|
||||
'{% endwith %}',
|
||||
context)
|
||||
self.assertEqual(result, 'TrueFalse')
|
||||
|
|
|
|||
|
|
@ -1,13 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import Group
|
||||
from django.core.urlresolvers import reverse
|
||||
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
|
||||
|
||||
|
|
@ -140,7 +137,7 @@ class PostListTest(BaseIntegrationTest):
|
|||
def test_actions_displayed(self):
|
||||
response = self.client.get(reverse("admin2:blog_post_index"))
|
||||
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):
|
||||
# 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)
|
||||
# caution : uses pluralization
|
||||
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):
|
||||
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):
|
||||
response = self.client.get(reverse("admin2:blog_post_index"))
|
||||
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):
|
||||
post = Post.objects.create(title="A Post Title",
|
||||
|
|
@ -350,7 +347,7 @@ class PostListTestCustomAction(BaseIntegrationTest):
|
|||
def test_unpublish_action_displayed_in_list(self):
|
||||
response = self.client.get(reverse("admin2:blog_post_index"))
|
||||
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):
|
||||
post = Post.objects.create(title="A Post Title",
|
||||
|
|
@ -380,7 +377,7 @@ class PostCreateViewTest(BaseIntegrationTest):
|
|||
def test_view_ok(self):
|
||||
response = self.client.get(reverse("admin2:blog_post_create"))
|
||||
self.assertNotIn(
|
||||
'''enctype="multipart/form-data"''', force_text(response.content))
|
||||
'''enctype="multipart/form-data"''', force_str(response.content))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_create_post(self):
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
#from django.shortcuts import render
|
||||
from django.views.generic import ListView, DetailView
|
||||
|
||||
from .models import Post
|
||||
|
|
@ -7,6 +6,7 @@ from .models import Post
|
|||
class BlogListView(ListView):
|
||||
model = Post
|
||||
template_name = 'blog_list.html'
|
||||
paginate_by = 10
|
||||
|
||||
|
||||
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, ...)
|
||||
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
|
||||
# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/
|
||||
|
|
@ -40,6 +42,7 @@ INSTALLED_APPS = [
|
|||
'django.contrib.staticfiles',
|
||||
# Uncomment the next line to enable admin documentation:
|
||||
# 'django.contrib.admindocs',
|
||||
'django_filters',
|
||||
'rest_framework',
|
||||
'djadmin2',
|
||||
'djadmin2.tests',
|
||||
|
|
@ -49,14 +52,13 @@ INSTALLED_APPS = [
|
|||
'polls'
|
||||
]
|
||||
|
||||
MIDDLEWARE_CLASSES = [
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.locale.LocaleMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
]
|
||||
|
|
@ -66,7 +68,7 @@ ROOT_URLCONF = 'example.urls'
|
|||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'DIRS': [os.path.join(BASE_DIR, 'templates')],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
|
|
@ -145,6 +147,13 @@ MEDIA_URL = "/media/"
|
|||
|
||||
REST_FRAMEWORK = {
|
||||
'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 django.conf import settings
|
||||
from django.conf.urls import include, url
|
||||
from django.conf.urls.static import static
|
||||
from django.contrib import admin
|
||||
from django.urls import re_path
|
||||
|
||||
from djadmin2.site import djadmin2_site
|
||||
|
||||
admin.autodiscover()
|
||||
|
||||
djadmin2_site.autodiscover()
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^admin2/', include(djadmin2_site.urls)),
|
||||
url(r'^admin/', include(admin.site.urls)),
|
||||
url(r'^blog/', BlogListView.as_view(template_name="blog/blog_list.html"), name='blog_list'),
|
||||
url(r'^blog/detail(?P<pk>\d+)/$', BlogDetailView.as_view(template_name="blog/blog_detail.html"), name='blog_detail'),
|
||||
url(r'^$', BlogListView.as_view(template_name="blog/home.html"), name='home'),
|
||||
re_path(r"^admin2/", djadmin2_site.urls),
|
||||
re_path(r"^admin/", admin.site.urls),
|
||||
re_path(
|
||||
r"^blog/",
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import division, absolute_import, unicode_literals
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
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 .models import CaptionedFile, UncaptionedFile
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
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.utils.encoding import python_2_unicode_compatible
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class CaptionedFile(models.Model):
|
||||
caption = models.CharField(max_length=200, verbose_name=_('caption'))
|
||||
publication = models.FileField(upload_to='captioned-files', verbose_name=_('Uploaded File'))
|
||||
|
|
@ -19,7 +14,6 @@ class CaptionedFile(models.Model):
|
|||
verbose_name_plural = _('Captioned Files')
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class UncaptionedFile(models.Model):
|
||||
publication = models.FileField(upload_to='uncaptioned-files', verbose_name=_('Uploaded File'))
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
from os import path
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from files.models import CaptionedFile
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
from os import path
|
||||
|
||||
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.utils.encoding import force_text
|
||||
from django.utils.encoding import force_str
|
||||
|
||||
from ..models import CaptionedFile
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ class CaptionedFileListTest(BaseIntegrationTest):
|
|||
def test_actions_displayed(self):
|
||||
response = self.client.get(reverse("admin2:files_captionedfile_index"))
|
||||
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):
|
||||
captioned_file = CaptionedFile.objects.create(
|
||||
|
|
@ -55,7 +55,7 @@ class CaptionedFileListTest(BaseIntegrationTest):
|
|||
response = self.client.post(
|
||||
reverse("admin2:files_captionedfile_index"), params)
|
||||
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):
|
||||
captioned_file = CaptionedFile.objects.create(
|
||||
|
|
@ -93,7 +93,7 @@ class CaptionedFileCreateViewTest(BaseIntegrationTest):
|
|||
response = self.client.get(
|
||||
reverse("admin2:files_captionedfile_create"))
|
||||
self.assertIn(
|
||||
'enctype="multipart/form-data"', force_text(response.content))
|
||||
'enctype="multipart/form-data"', force_str(response.content))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_create_captioned_file(self):
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import division, absolute_import, unicode_literals
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
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.types import Admin2TabularInline, ModelAdmin2
|
||||
from .models import Poll, Choice
|
||||
|
|
@ -8,7 +5,7 @@ from .models import Poll, Choice
|
|||
|
||||
class ChoiceInline(Admin2TabularInline):
|
||||
model = Choice
|
||||
extra = 3
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class PollAdmin(ModelAdmin2):
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
|
@ -37,6 +34,9 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='choice',
|
||||
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
|
||||
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Poll(models.Model):
|
||||
question = models.CharField(max_length=200, verbose_name=_('question'))
|
||||
pub_date = models.DateTimeField(verbose_name=_('date published'))
|
||||
|
|
@ -28,9 +23,12 @@ class Poll(models.Model):
|
|||
verbose_name_plural = _('polls')
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
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(
|
||||
max_length=200, verbose_name=_('choice text'))
|
||||
votes = models.IntegerField(default=0, verbose_name=_('votes'))
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
from django.contrib.auth import get_user_model
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test import TestCase, Client
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.encoding import force_str
|
||||
|
||||
from ..models import Poll
|
||||
|
||||
|
|
@ -40,7 +40,7 @@ class PollListTest(BaseIntegrationTest):
|
|||
def test_actions_displayed(self):
|
||||
response = self.client.get(reverse("admin2:polls_poll_index"))
|
||||
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):
|
||||
poll = Poll.objects.create(
|
||||
|
|
@ -49,7 +49,7 @@ class PollListTest(BaseIntegrationTest):
|
|||
'selected_model_pk': str(poll.pk)}
|
||||
response = self.client.post(reverse("admin2:polls_poll_index"), params)
|
||||
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):
|
||||
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.contrib.console import confirm
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
django-extra-views<=0.7.1
|
||||
django-braces>=1.3.0
|
||||
djangorestframework<=3.3.3
|
||||
django-filter>=0.15.3
|
||||
django-debug-toolbar>=1.5
|
||||
future>=0.15.2
|
||||
pytz>=2016.4
|
||||
django-extra-views
|
||||
django-braces
|
||||
djangorestframework
|
||||
django-filter
|
||||
django-debug-toolbar
|
||||
pytz
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
-rrequirements.txt
|
||||
flake8==2.5.4
|
||||
flake8>=2.5.4
|
||||
pytest
|
||||
pytest-django
|
||||
pytest-django
|
||||
pytest-cov
|
||||
|
|
|
|||
22
setup.py
22
setup.py
|
|
@ -1,5 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from setuptools import setup
|
||||
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())
|
||||
HISTORY = open('HISTORY.rst').read()
|
||||
|
||||
|
||||
class PyTest(TestCommand):
|
||||
user_options = [('pytest-args=', 'a', "Arguments to pass to py.test")]
|
||||
|
||||
|
|
@ -100,6 +100,7 @@ class PyTest(TestCommand):
|
|||
errno = pytest.main(self.pytest_args)
|
||||
sys.exit(errno)
|
||||
|
||||
|
||||
setup(
|
||||
name='django-admin2',
|
||||
version=version,
|
||||
|
|
@ -112,8 +113,10 @@ setup(
|
|||
"License :: OSI Approved :: BSD License",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: JavaScript",
|
||||
"Programming Language :: Python :: 2.7",
|
||||
"Programming Language :: Python :: 3.3",
|
||||
"Programming Language :: Python :: 3.5",
|
||||
"Programming Language :: Python :: 3.6",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Topic :: Internet :: WWW/HTTP",
|
||||
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
|
|
@ -127,13 +130,12 @@ setup(
|
|||
include_package_data=True,
|
||||
#test_suite='runtests.runtests',
|
||||
install_requires=[
|
||||
'django>=1.8.0',
|
||||
'django-extra-views<=0.7.1',
|
||||
'django>=2.2',
|
||||
'django-extra-views>=0.12.0',
|
||||
'django-braces>=1.3.0',
|
||||
'djangorestframework<=3.3.3',
|
||||
'django-filter>=0.15.3',
|
||||
'pytz==2016.4',
|
||||
'future>=0.15.2',
|
||||
'djangorestframework>=3.11.1',
|
||||
'django-filter>=2.4.0',
|
||||
'pytz>=2016.4',
|
||||
],
|
||||
extras_require={
|
||||
'testing': ['pytest', 'pytest-django', 'pytest-ipdb'],
|
||||
|
|
|
|||
28
tox.ini
28
tox.ini
|
|
@ -6,21 +6,29 @@ exclude = migrations/*,docs/*
|
|||
|
||||
[tox]
|
||||
envlist =
|
||||
py27-{1.8,1.9,1.10,master},
|
||||
py33-{1.8},
|
||||
py34-{1.8,1.9,1.10,master},
|
||||
py35-{1.8,1.9,1.10,master},
|
||||
py35-{2.2},
|
||||
py36-{2.2,3.1},
|
||||
py37-{2.2,3.1,3.2},
|
||||
py38-{2.2,3.1,3.2,main},
|
||||
|
||||
[gh-actions]
|
||||
python =
|
||||
3.5: py35
|
||||
3.6: py36
|
||||
3.7: py37
|
||||
3.8: py38
|
||||
|
||||
[testenv]
|
||||
commands =
|
||||
flake8 djadmin2
|
||||
py.test []
|
||||
pytest --cov-append --cov djadmin2 --cov-report=xml []
|
||||
deps =
|
||||
-rrequirements_test.txt
|
||||
1.8: Django>=1.8,<1.9
|
||||
1.9: Django>=1.9,<1.10
|
||||
1.10: Django>=1.10,<1.11
|
||||
master: https://github.com/django/django/tarball/master
|
||||
3.1: Django>=3.1,<3.2
|
||||
3.2: Django>=3.2,<4.0
|
||||
2.2: Django>=2.2,<2.3
|
||||
main: https://github.com/django/django/tarball/main
|
||||
setenv=
|
||||
DJANGO_SETTINGS_MODULE = example.settings
|
||||
PYTHONPATH = {toxinidir}/example:{toxinidir}
|
||||
PYTHONDONTWRITEBYTECODE=1
|
||||
PYTHONWARNINGS=once
|
||||
|
|
|
|||
Loading…
Reference in a new issue