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