mirror of
https://github.com/jazzband/django-admin2.git
synced 2026-05-26 15:44:07 +00:00
Merge pull request #431 from arthur-wsw/django_1_8
Django 1.8 and 1.9 compatibility
This commit is contained in:
commit
d9b3d2c8a6
66 changed files with 887 additions and 709 deletions
6
.eggs/README.txt
Normal file
6
.eggs/README.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
This directory contains eggs that were downloaded by setuptools to build, test, and run plug-ins.
|
||||||
|
|
||||||
|
This directory caches those eggs to prevent repeated downloads.
|
||||||
|
|
||||||
|
However, it is safe to delete this directory.
|
||||||
|
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -59,3 +59,8 @@ logfile
|
||||||
|
|
||||||
# test media upload
|
# test media upload
|
||||||
media
|
media
|
||||||
|
|
||||||
|
# PyCharm
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
.cache
|
||||||
63
.travis.yml
63
.travis.yml
|
|
@ -1,47 +1,28 @@
|
||||||
sudo: false
|
sudo: false
|
||||||
language: python
|
language: python
|
||||||
python: "2.7"
|
python:
|
||||||
|
- "2.7"
|
||||||
|
- "3.3"
|
||||||
|
- "3.4"
|
||||||
|
- "3.5"
|
||||||
env:
|
env:
|
||||||
matrix:
|
- DJANGO=1.8
|
||||||
- TOX_ENV=py27-dj1.6.x
|
- DJANGO=1.9
|
||||||
- TOX_ENV=py27-dj1.7.x
|
- DJANGO=master
|
||||||
- TOX_ENV=py27-dj1.8.x
|
matrix:
|
||||||
- TOX_ENV=py27-dj1.9.x
|
exclude:
|
||||||
- TOX_ENV=py33-dj1.6.x
|
- python: "3.3"
|
||||||
- TOX_ENV=py33-dj1.7.x
|
env: DJANGO=1.9
|
||||||
- TOX_ENV=py33-dj1.8.x
|
- python: "3.3"
|
||||||
- TOX_ENV=py33-dj1.9.x
|
env: DJANGO=master
|
||||||
- TOX_ENV=py34-dj1.6.x
|
allow_failures:
|
||||||
- TOX_ENV=py34-dj1.7.x
|
- python: "2.7"
|
||||||
- TOX_ENV=py34-dj1.8.x
|
env: DJANGO=master
|
||||||
- TOX_ENV=py34-dj1.9.x
|
- python: "3.4"
|
||||||
- TOX_ENV=pypy-dj1.6.x
|
env: DJANGO=master
|
||||||
- TOX_ENV=pypy-dj1.7.x
|
- python: "3.5"
|
||||||
- TOX_ENV=pypy-dj1.8.x
|
env: DJANGO=master
|
||||||
- TOX_ENV=pypy-dj1.9.x
|
|
||||||
- TOX_ENV=pypy3-dj1.6.x
|
|
||||||
- TOX_ENV=pypy3-dj1.8.x
|
|
||||||
- TOX_ENV=pypy3-dj1.9.x
|
|
||||||
install:
|
install:
|
||||||
- pip install tox
|
- pip install tox
|
||||||
script:
|
script:
|
||||||
- tox -e $TOX_ENV
|
- tox -e py${TRAVIS_PYTHON_VERSION//[.]/}-$DJANGO
|
||||||
# for now commented. We have to figure which version use for coverage
|
|
||||||
# and coveralls
|
|
||||||
#after_success:
|
|
||||||
# - coverage report
|
|
||||||
# - pip install --quiet python-coveralls
|
|
||||||
# - coveralls
|
|
||||||
matrix:
|
|
||||||
allow_failures:
|
|
||||||
- env: TOX_ENV=py27-dj1.8.x
|
|
||||||
- env: TOX_ENV=py27-dj1.9.x
|
|
||||||
- env: TOX_ENV=py33-dj1.8.x
|
|
||||||
- env: TOX_ENV=py33-dj1.9.x
|
|
||||||
- env: TOX_ENV=py34-dj1.8.x
|
|
||||||
- env: TOX_ENV=py34-dj1.9.x
|
|
||||||
- env: TOX_ENV=pypy-dj1.8.x
|
|
||||||
- env: TOX_ENV=pypy-dj1.9.x
|
|
||||||
- env: TOX_ENV=pypy3-dj1.8.x
|
|
||||||
- env: TOX_ENV=pypy3-dj1.9.x
|
|
||||||
- env: TOX_ENV=pypy3-dj1.9.x
|
|
||||||
|
|
@ -52,6 +52,7 @@ Developers
|
||||||
* marangonico
|
* marangonico
|
||||||
* Kamil Gałuszka (@galuszkak / galuszkak@gmail.com)
|
* Kamil Gałuszka (@galuszkak / galuszkak@gmail.com)
|
||||||
* Germano Gabbianelli (@tyrion)
|
* Germano Gabbianelli (@tyrion)
|
||||||
|
* Arthur (@arthur-wsw / arthur@wallstreetweb.net)
|
||||||
|
|
||||||
Translators
|
Translators
|
||||||
-----------
|
-----------
|
||||||
|
|
|
||||||
14
HISTORY.rst
14
HISTORY.rst
|
|
@ -1,6 +1,20 @@
|
||||||
History
|
History
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.6.2 (?)
|
||||||
|
|
||||||
|
* Fix Django 1.8 issues and add 1.9 compatibility
|
||||||
|
* Update all dependancies (DRF, floppyforms, filters, ...)
|
||||||
|
* Regenerate example project to make it django 1.9 compatible
|
||||||
|
* Update tox and travis and add flake8
|
||||||
|
* Rename AdminModel2Mixin to Admin2ModelMixin
|
||||||
|
* Add migrations
|
||||||
|
* Replace IPAddressField with GenericIPAddressField
|
||||||
|
* Fix password link in user admin
|
||||||
|
* Fix user logout on password change
|
||||||
|
* Fix tests
|
||||||
|
|
||||||
|
|
||||||
0.6.1 (2014-02-26)
|
0.6.1 (2014-02-26)
|
||||||
|
|
||||||
* Fix empty form display
|
* Fix empty form display
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
from __future__ import division, absolute_import, unicode_literals
|
||||||
|
|
||||||
|
|
||||||
__version__ = '0.6.1'
|
__version__ = '0.6.1'
|
||||||
|
|
||||||
__author__ = 'Daniel Greenfeld & Contributors'
|
__author__ = 'Daniel Greenfeld & Contributors'
|
||||||
|
|
@ -10,17 +11,4 @@ VERSION = __version__ # synonym
|
||||||
# Default datetime input and output formats
|
# Default datetime input and output formats
|
||||||
ISO_8601 = 'iso-8601'
|
ISO_8601 = 'iso-8601'
|
||||||
|
|
||||||
from . import core
|
default_app_config = "djadmin2.apps.Djadmin2Config"
|
||||||
from . import types
|
|
||||||
|
|
||||||
|
|
||||||
default = core.Admin2()
|
|
||||||
ModelAdmin2 = types.ModelAdmin2
|
|
||||||
Admin2TabularInline = types.Admin2TabularInline
|
|
||||||
Admin2StackedInline = types.Admin2StackedInline
|
|
||||||
|
|
||||||
# Utility to make migration between versions easier
|
|
||||||
sites = default
|
|
||||||
ModelAdmin = ModelAdmin2
|
|
||||||
AdminInline = Admin2TabularInline
|
|
||||||
Admin2Inline = Admin2TabularInline
|
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,15 @@
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
from __future__ import division, absolute_import, unicode_literals
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.views.generic import TemplateView
|
from django.db import router
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
from django.utils.text import capfirst
|
from django.utils.text import capfirst
|
||||||
from django.utils.translation import ugettext_lazy, ungettext, pgettext_lazy
|
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
from django.utils.translation import ugettext_lazy, ungettext, pgettext_lazy
|
||||||
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
from . import permissions, utils
|
from . import permissions, utils
|
||||||
from .viewmixins import AdminModel2Mixin
|
from .viewmixins import Admin2ModelMixin
|
||||||
|
|
||||||
|
|
||||||
def get_description(action):
|
def get_description(action):
|
||||||
|
|
@ -21,7 +22,7 @@ def get_description(action):
|
||||||
return capfirst(action.__name__.replace('_', ' '))
|
return capfirst(action.__name__.replace('_', ' '))
|
||||||
|
|
||||||
|
|
||||||
class BaseListAction(AdminModel2Mixin, TemplateView):
|
class BaseListAction(Admin2ModelMixin, TemplateView):
|
||||||
|
|
||||||
permission_classes = (permissions.IsStaffPermission,)
|
permission_classes = (permissions.IsStaffPermission,)
|
||||||
|
|
||||||
|
|
@ -54,7 +55,7 @@ class BaseListAction(AdminModel2Mixin, TemplateView):
|
||||||
super(BaseListAction, self).__init__(*args, **kwargs)
|
super(BaseListAction, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
""" Replaced `get_queryset` from `AdminModel2Mixin`"""
|
""" Replaced `get_queryset` from `Admin2ModelMixin`"""
|
||||||
return self.queryset
|
return self.queryset
|
||||||
|
|
||||||
def description(self):
|
def description(self):
|
||||||
|
|
@ -94,7 +95,9 @@ class BaseListAction(AdminModel2Mixin, TemplateView):
|
||||||
return '%s: %s' % (force_text(capfirst(opts.verbose_name)),
|
return '%s: %s' % (force_text(capfirst(opts.verbose_name)),
|
||||||
force_text(obj))
|
force_text(obj))
|
||||||
|
|
||||||
collector = utils.NestedObjects(using=None)
|
using = router.db_for_write(self.model)
|
||||||
|
|
||||||
|
collector = utils.NestedObjects(using=using)
|
||||||
collector.collect(self.queryset)
|
collector.collect(self.queryset)
|
||||||
|
|
||||||
context.update({
|
context.update({
|
||||||
|
|
@ -166,7 +169,6 @@ class DeleteSelectedAction(BaseListAction):
|
||||||
# objects, so render a template asking for their confirmation.
|
# objects, so render a template asking for their confirmation.
|
||||||
return self.get(request)
|
return self.get(request)
|
||||||
|
|
||||||
|
|
||||||
def process_queryset(self):
|
def process_queryset(self):
|
||||||
# The user has confirmed that they want to delete the objects.
|
# The user has confirmed that they want to delete the objects.
|
||||||
self.get_queryset().delete()
|
self.get_queryset().delete()
|
||||||
|
|
|
||||||
|
|
@ -4,34 +4,34 @@ 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
|
||||||
|
|
||||||
from rest_framework.relations import PrimaryKeyRelatedField
|
from rest_framework.relations import PrimaryKeyRelatedField
|
||||||
|
|
||||||
import djadmin2
|
|
||||||
from djadmin2.forms import UserCreationForm, UserChangeForm
|
|
||||||
from djadmin2.apiviews import Admin2APISerializer
|
from djadmin2.apiviews import Admin2APISerializer
|
||||||
|
from djadmin2.forms import UserCreationForm, UserChangeForm
|
||||||
|
from djadmin2.site import djadmin2_site
|
||||||
|
from djadmin2.types import ModelAdmin2
|
||||||
|
|
||||||
|
|
||||||
class GroupSerializer(Admin2APISerializer):
|
class GroupSerializer(Admin2APISerializer):
|
||||||
permissions = PrimaryKeyRelatedField(many=True)
|
permissions = PrimaryKeyRelatedField(many=True, read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Group
|
model = Group
|
||||||
|
|
||||||
|
|
||||||
class GroupAdmin2(djadmin2.ModelAdmin2):
|
class GroupAdmin2(ModelAdmin2):
|
||||||
api_serializer_class = GroupSerializer
|
api_serializer_class = GroupSerializer
|
||||||
|
|
||||||
|
|
||||||
class UserSerializer(Admin2APISerializer):
|
class UserSerializer(Admin2APISerializer):
|
||||||
user_permissions = PrimaryKeyRelatedField(many=True)
|
user_permissions = PrimaryKeyRelatedField(many=True, read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
exclude = ('passwords',)
|
exclude = ('password',)
|
||||||
|
|
||||||
|
|
||||||
class UserAdmin2(djadmin2.ModelAdmin2):
|
class UserAdmin2(ModelAdmin2):
|
||||||
create_form_class = UserCreationForm
|
create_form_class = UserCreationForm
|
||||||
update_form_class = UserChangeForm
|
update_form_class = UserChangeForm
|
||||||
search_fields = ('username', 'groups__name', 'first_name', 'last_name',
|
search_fields = ('username', 'groups__name', 'first_name', 'last_name',
|
||||||
|
|
@ -43,15 +43,15 @@ class UserAdmin2(djadmin2.ModelAdmin2):
|
||||||
|
|
||||||
|
|
||||||
# Register each model with the admin
|
# Register each model with the admin
|
||||||
djadmin2.default.register(User, UserAdmin2)
|
djadmin2_site.register(User, UserAdmin2)
|
||||||
djadmin2.default.register(Group, GroupAdmin2)
|
djadmin2_site.register(Group, GroupAdmin2)
|
||||||
|
|
||||||
|
|
||||||
# Register the sites app if it's been activated in INSTALLED_APPS
|
# Register the sites app if it's been activated in INSTALLED_APPS
|
||||||
if "django.contrib.sites" in settings.INSTALLED_APPS:
|
if "django.contrib.sites" in settings.INSTALLED_APPS:
|
||||||
|
|
||||||
class SiteAdmin2(djadmin2.ModelAdmin2):
|
class SiteAdmin2(ModelAdmin2):
|
||||||
list_display = ('domain', 'name')
|
list_display = ('domain', 'name')
|
||||||
search_fields = ('domain', 'name')
|
search_fields = ('domain', 'name')
|
||||||
|
|
||||||
djadmin2.default.register(Site, SiteAdmin2)
|
djadmin2_site.register(Site, SiteAdmin2)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
from __future__ import division, absolute_import, unicode_literals
|
||||||
|
|
||||||
from django.utils.encoding import force_str
|
from django.utils.encoding import force_str
|
||||||
|
|
||||||
from rest_framework import fields, generics, serializers
|
from rest_framework import fields, generics, serializers
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.reverse import reverse
|
from rest_framework.reverse import reverse
|
||||||
|
|
@ -17,11 +16,30 @@ API_VERSION = '0.1'
|
||||||
class Admin2APISerializer(serializers.HyperlinkedModelSerializer):
|
class Admin2APISerializer(serializers.HyperlinkedModelSerializer):
|
||||||
_default_view_name = 'admin2:%(app_label)s_%(model_name)s_api_detail'
|
_default_view_name = 'admin2:%(app_label)s_%(model_name)s_api_detail'
|
||||||
|
|
||||||
pk = fields.Field(source='pk')
|
pk = fields.ReadOnlyField()
|
||||||
__unicode__ = fields.Field(source='__str__')
|
__unicode__ = fields.ReadOnlyField(source='__str__')
|
||||||
|
|
||||||
|
def get_extra_kwargs(self):
|
||||||
|
extra_kwargs = super(Admin2APISerializer, self).get_extra_kwargs()
|
||||||
|
extra_kwargs.update({
|
||||||
|
'url': {'view_name': self._get_default_view_name(self.Meta.model)}
|
||||||
|
})
|
||||||
|
return extra_kwargs
|
||||||
|
|
||||||
|
def _get_default_view_name(self, model):
|
||||||
|
"""
|
||||||
|
Return the view name to use if 'view_name' is not specified in 'Meta'
|
||||||
|
"""
|
||||||
|
model_meta = model._meta
|
||||||
|
format_kwargs = {
|
||||||
|
'app_label': model_meta.app_label,
|
||||||
|
'model_name': model_meta.object_name.lower()
|
||||||
|
}
|
||||||
|
return self._default_view_name % format_kwargs
|
||||||
|
|
||||||
|
|
||||||
class Admin2APIMixin(Admin2Mixin):
|
class Admin2APIMixin(Admin2Mixin):
|
||||||
|
model = None
|
||||||
raise_exception = True
|
raise_exception = True
|
||||||
|
|
||||||
def get_serializer_class(self):
|
def get_serializer_class(self):
|
||||||
|
|
|
||||||
14
djadmin2/apps.py
Normal file
14
djadmin2/apps.py
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
from django.db.models.signals import post_migrate
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from djadmin2.permissions import create_view_permissions
|
||||||
|
|
||||||
|
|
||||||
|
class Djadmin2Config(AppConfig):
|
||||||
|
name = 'djadmin2'
|
||||||
|
verbose_name = _("Django Admin2")
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
post_migrate.connect(create_view_permissions,
|
||||||
|
dispatch_uid="django-admin2.djadmin2.permissions.create_view_permissions")
|
||||||
|
|
@ -5,12 +5,11 @@ Issue #99.
|
||||||
"""
|
"""
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
from __future__ import division, absolute_import, unicode_literals
|
||||||
|
|
||||||
from django.conf.urls import patterns, include, url
|
|
||||||
from django.conf import settings
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
|
||||||
|
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.conf.urls import include, url
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
|
||||||
from . import apiviews
|
from . import apiviews
|
||||||
from . import types
|
from . import types
|
||||||
|
|
@ -160,8 +159,7 @@ class Admin2(object):
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_urls(self):
|
def get_urls(self):
|
||||||
urlpatterns = patterns(
|
urlpatterns = [
|
||||||
'',
|
|
||||||
url(regex=r'^$',
|
url(regex=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'
|
||||||
|
|
@ -188,11 +186,10 @@ class Admin2(object):
|
||||||
**self.get_api_index_kwargs()),
|
**self.get_api_index_kwargs()),
|
||||||
name='api_index'
|
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 += patterns(
|
urlpatterns += [
|
||||||
'',
|
|
||||||
url('^{}/{}/'.format(
|
url('^{}/{}/'.format(
|
||||||
model_options.app_label,
|
model_options.app_label,
|
||||||
model_options.object_name.lower()),
|
model_options.object_name.lower()),
|
||||||
|
|
@ -201,11 +198,10 @@ class Admin2(object):
|
||||||
model_options.app_label,
|
model_options.app_label,
|
||||||
model_options.object_name.lower()),
|
model_options.object_name.lower()),
|
||||||
include(model_admin.api_urls)),
|
include(model_admin.api_urls)),
|
||||||
)
|
]
|
||||||
return urlpatterns
|
return urlpatterns
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def urls(self):
|
def urls(self):
|
||||||
# We set the application and instance namespace here
|
# We set the application and instance namespace here
|
||||||
return self.get_urls(), self.name, self.name
|
return self.get_urls(), self.name, self.name
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,19 +2,17 @@
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
from __future__ import division, absolute_import, unicode_literals
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
|
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
|
||||||
from django import forms
|
|
||||||
from django.forms.util import flatatt
|
|
||||||
from django.utils.html import format_html
|
|
||||||
from django.utils.encoding import force_text, force_bytes
|
|
||||||
from django.utils.safestring import mark_safe
|
|
||||||
from django.forms import widgets as django_widgets
|
|
||||||
from django.utils import six
|
|
||||||
from django.utils.translation import ugettext_lazy
|
|
||||||
|
|
||||||
import django_filters
|
import django_filters
|
||||||
|
from django import forms
|
||||||
|
from django.forms import widgets as django_widgets
|
||||||
|
from django.forms.utils import flatatt
|
||||||
|
from django.utils import six
|
||||||
|
from django.utils.encoding import force_text
|
||||||
|
from django.utils.html import format_html
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
from django.utils.translation import ugettext_lazy
|
||||||
|
|
||||||
from .utils import type_str
|
from .utils import type_str
|
||||||
|
|
||||||
|
|
@ -97,21 +95,21 @@ def build_list_filter(request, model_admin, queryset):
|
||||||
'fields': fields,
|
'fields': fields,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
return type(type_str('%sFilterSet' % queryset.model.__name__),(django_filters.FilterSet, ),filterset_dict,)(request.GET, queryset=queryset)
|
return type(type_str('%sFilterSet' % queryset.model.__name__), (django_filters.FilterSet, ), filterset_dict,)(request.GET, queryset=queryset)
|
||||||
|
|
||||||
|
|
||||||
def build_date_filter(request, model_admin, queryset):
|
def build_date_filter(request, model_admin, queryset, field_name="published_date"):
|
||||||
filterset_dict = {
|
filterset_dict = {
|
||||||
"year": NumericDateFilter(
|
"year": NumericDateFilter(
|
||||||
name="published_date",
|
name=field_name,
|
||||||
lookup_type="year",
|
lookup_type="year",
|
||||||
),
|
),
|
||||||
"month": NumericDateFilter(
|
"month": NumericDateFilter(
|
||||||
name="published_date",
|
name=field_name,
|
||||||
lookup_type="month",
|
lookup_type="month",
|
||||||
),
|
),
|
||||||
"day": NumericDateFilter(
|
"day": NumericDateFilter(
|
||||||
name="published_date",
|
name=field_name,
|
||||||
lookup_type="day",
|
lookup_type="day",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,16 @@ from __future__ import division, absolute_import, unicode_literals
|
||||||
|
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
|
||||||
|
import django
|
||||||
|
import django.forms
|
||||||
|
import django.forms.extras.widgets
|
||||||
|
import django.forms.models
|
||||||
|
import floppyforms
|
||||||
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
|
||||||
import django
|
from django.core.urlresolvers import reverse_lazy
|
||||||
import django.forms
|
from django.utils.translation import ugettext_lazy as _
|
||||||
import django.forms.models
|
|
||||||
import django.forms.extras.widgets
|
|
||||||
from django.utils.translation import ugettext_lazy
|
|
||||||
|
|
||||||
import floppyforms
|
|
||||||
|
|
||||||
|
|
||||||
_WIDGET_COMMON_ATTRIBUTES = (
|
_WIDGET_COMMON_ATTRIBUTES = (
|
||||||
|
|
@ -166,9 +166,7 @@ _django_to_floppyforms_widget = {
|
||||||
django.forms.extras.widgets.SelectDateWidget:
|
django.forms.extras.widgets.SelectDateWidget:
|
||||||
_create_widget(
|
_create_widget(
|
||||||
floppyforms.widgets.SelectDateWidget,
|
floppyforms.widgets.SelectDateWidget,
|
||||||
init_arguments=
|
init_arguments=('years',) if django.VERSION >= (1, 7) else ('years', 'required')),
|
||||||
('years',)
|
|
||||||
if django.VERSION >= (1, 7) else ('years', 'required')),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_django_field_to_floppyform_widget = {
|
_django_field_to_floppyform_widget = {
|
||||||
|
|
@ -184,8 +182,8 @@ _django_field_to_floppyform_widget = {
|
||||||
_create_widget(floppyforms.widgets.URLInput),
|
_create_widget(floppyforms.widgets.URLInput),
|
||||||
django.forms.fields.SlugField:
|
django.forms.fields.SlugField:
|
||||||
_create_widget(floppyforms.widgets.SlugInput),
|
_create_widget(floppyforms.widgets.SlugInput),
|
||||||
django.forms.fields.IPAddressField:
|
django.forms.fields.GenericIPAddressField:
|
||||||
_create_widget(floppyforms.widgets.IPAddressInput),
|
_create_widget(floppyforms.widgets.TextInput),
|
||||||
django.forms.fields.SplitDateTimeField:
|
django.forms.fields.SplitDateTimeField:
|
||||||
_create_splitdatetimewidget(floppyforms.widgets.SplitDateTimeWidget),
|
_create_splitdatetimewidget(floppyforms.widgets.SplitDateTimeWidget),
|
||||||
}
|
}
|
||||||
|
|
@ -201,11 +199,10 @@ def allow_floppify_widget_for_field(field):
|
||||||
# replaces the default TextInput with a NumberInput, if localization is
|
# replaces the default TextInput with a NumberInput, if localization is
|
||||||
# turned off. That applies for Django 1.6 upwards.
|
# turned off. That applies for Django 1.6 upwards.
|
||||||
# See the relevant source code in django:
|
# See the relevant source code in django:
|
||||||
# https://github.com/django/django/blob/1.6/django/forms/fields.py#L225
|
# https://github.com/django/django/blob/1.9.6/django/forms/fields.py#L261-264
|
||||||
if django.VERSION >= (1, 6):
|
if isinstance(field, django.forms.IntegerField) and not field.localize:
|
||||||
if isinstance(field, django.forms.IntegerField) and not field.localize:
|
if field.widget.__class__ is django.forms.NumberInput:
|
||||||
if field.widget.__class__ is django.forms.NumberInput:
|
return True
|
||||||
return True
|
|
||||||
|
|
||||||
# We can check if the widget was replaced by comparing the class of the
|
# We can check if the widget was replaced by comparing the class of the
|
||||||
# specified widget with the default widget that is specified on the field
|
# specified widget with the default widget that is specified on the field
|
||||||
|
|
@ -272,8 +269,10 @@ def modelform_factory(model, form=django.forms.models.ModelForm, fields=None,
|
||||||
|
|
||||||
# Translators : %(username)s will be replaced by the username_field name
|
# Translators : %(username)s will be replaced by the username_field name
|
||||||
# (default : username, but could be email, or something else)
|
# (default : username, but could be email, or something else)
|
||||||
ERROR_MESSAGE = ugettext_lazy("Please enter the correct %(username)s and password "
|
ERROR_MESSAGE = _(
|
||||||
"for a staff account. Note that both fields may be case-sensitive.")
|
"Please enter the correct %(username)s and password "
|
||||||
|
"for a staff account. Note that both fields may be case-sensitive."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class AdminAuthenticationForm(AuthenticationForm):
|
class AdminAuthenticationForm(AuthenticationForm):
|
||||||
|
|
@ -283,10 +282,13 @@ class AdminAuthenticationForm(AuthenticationForm):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
error_messages = {
|
error_messages = {
|
||||||
'required': ugettext_lazy("Please log in again, because your session has expired."),
|
'required': _("Please log in again, because your session has expired."),
|
||||||
}
|
}
|
||||||
this_is_the_login_form = django.forms.BooleanField(widget=floppyforms.HiddenInput,
|
this_is_the_login_form = django.forms.BooleanField(
|
||||||
initial=1, error_messages=error_messages)
|
widget=floppyforms.HiddenInput,
|
||||||
|
initial=1,
|
||||||
|
error_messages=error_messages
|
||||||
|
)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
username = self.cleaned_data.get('username')
|
username = self.cleaned_data.get('username')
|
||||||
|
|
@ -306,5 +308,17 @@ class AdminAuthenticationForm(AuthenticationForm):
|
||||||
return self.cleaned_data
|
return self.cleaned_data
|
||||||
|
|
||||||
|
|
||||||
|
class Admin2UserChangeForm(UserChangeForm):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(Admin2UserChangeForm, self).__init__(*args, **kwargs)
|
||||||
|
print(self.fields['password'].help_text)
|
||||||
|
self.fields['password'].help_text = _("Raw passwords are not stored, so there is no way to see this user's password, but you can change the password using <a href=\"%s\">this form</a>." % self.get_update_password_url())
|
||||||
|
|
||||||
|
def get_update_password_url(self):
|
||||||
|
if self.instance and self.instance.pk:
|
||||||
|
return reverse_lazy('admin2:password_change', args=[self.instance.pk])
|
||||||
|
return 'password/'
|
||||||
|
|
||||||
UserCreationForm = floppify_form(UserCreationForm)
|
UserCreationForm = floppify_form(UserCreationForm)
|
||||||
UserChangeForm = floppify_form(UserChangeForm)
|
UserChangeForm = floppify_form(Admin2UserChangeForm)
|
||||||
|
|
|
||||||
34
djadmin2/migrations/0001_initial.py
Normal file
34
djadmin2/migrations/0001_initial.py
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('contenttypes', '0002_remove_content_type_name'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='LogEntry',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
|
||||||
|
('action_time', models.DateTimeField(verbose_name='action time', auto_now=True)),
|
||||||
|
('object_id', models.TextField(verbose_name='object id', null=True, blank=True)),
|
||||||
|
('object_repr', models.CharField(max_length=200, verbose_name='object repr')),
|
||||||
|
('action_flag', models.PositiveSmallIntegerField(verbose_name='action flag')),
|
||||||
|
('change_message', models.TextField(verbose_name='change message', blank=True)),
|
||||||
|
('content_type', models.ForeignKey(related_name='log_entries', null=True, blank=True, to='contenttypes.ContentType')),
|
||||||
|
('user', models.ForeignKey(related_name='log_entries', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'log entry',
|
||||||
|
'ordering': ('-action_time',),
|
||||||
|
'verbose_name_plural': 'log entries',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
0
djadmin2/migrations/__init__.py
Normal file
0
djadmin2/migrations/__init__.py
Normal file
|
|
@ -5,13 +5,11 @@ 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.db.models import signals
|
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
from django.utils.encoding import smart_text
|
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
from django.utils.encoding import python_2_unicode_compatible
|
||||||
|
from django.utils.encoding import smart_text
|
||||||
from django.utils.translation import ugettext, ugettext_lazy as _
|
from django.utils.translation import ugettext, ugettext_lazy as _
|
||||||
|
|
||||||
from . import permissions
|
|
||||||
from .utils import quote
|
from .utils import quote
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -99,10 +97,3 @@ class LogEntry(models.Model):
|
||||||
quote(self.object_id)
|
quote(self.object_id)
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# setup signal handlers here, since ``models.py`` will be imported by django
|
|
||||||
# for sure if ``djadmin2`` is listed in the ``INSTALLED_APPS``.
|
|
||||||
|
|
||||||
signals.post_syncdb.connect(
|
|
||||||
permissions.create_view_permissions,
|
|
||||||
dispatch_uid="django-admin2.djadmin2.permissions.create_view_permissions")
|
|
||||||
|
|
|
||||||
|
|
@ -20,15 +20,14 @@ from __future__ import division, absolute_import, unicode_literals
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from django.contrib.auth import models as auth_models
|
from django.contrib.auth import get_permission_codename
|
||||||
from django.contrib.contenttypes import models as contenttypes_models
|
from django.db.utils import DEFAULT_DB_ALIAS
|
||||||
from django.db.models import get_models
|
from django.apps import apps
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.db import router
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
|
||||||
from . import utils
|
|
||||||
from django.utils.encoding import python_2_unicode_compatible, force_text
|
from django.utils.encoding import python_2_unicode_compatible, force_text
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger('djadmin2')
|
logger = logging.getLogger('djadmin2')
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -363,14 +362,13 @@ class TemplatePermissionChecker(object):
|
||||||
else:
|
else:
|
||||||
return self._view.has_permission(self._obj)
|
return self._view.has_permission(self._obj)
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self._view is None:
|
if self._view is None:
|
||||||
return ''
|
return ''
|
||||||
return force_text(bool(self))
|
return force_text(bool(self))
|
||||||
|
|
||||||
|
|
||||||
def create_view_permissions(app, created_models, verbosity, **kwargs):
|
def create_view_permissions(app_config, verbosity=2, interactive=True, using=DEFAULT_DB_ALIAS, **kwargs): # noqa
|
||||||
"""
|
"""
|
||||||
Create 'view' permissions for all models.
|
Create 'view' permissions for all models.
|
||||||
|
|
||||||
|
|
@ -378,40 +376,64 @@ def create_view_permissions(app, created_models, verbosity, **kwargs):
|
||||||
Since we want to support read-only views, we need to add our own
|
Since we want to support read-only views, we need to add our own
|
||||||
permission.
|
permission.
|
||||||
|
|
||||||
Copied from ``django.contrib.auth.management.create_permissions``.
|
Copied from ``https://github.com/django/django/blob/1.9.6/django/contrib/auth/management/__init__.py#L60``.
|
||||||
"""
|
|
||||||
# Is there any reason for doing this import here?
|
|
||||||
|
|
||||||
app_models = get_models(app)
|
"""
|
||||||
|
if not app_config.models_module:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
Permission = apps.get_model('auth', 'Permission')
|
||||||
|
except LookupError:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not router.allow_migrate_model(using, Permission):
|
||||||
|
return
|
||||||
|
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
# This will hold the permissions we're looking for as
|
# This will hold the permissions we're looking for as
|
||||||
# (content_type, (codename, name))
|
# (content_type, (codename, name))
|
||||||
searched_perms = list()
|
searched_perms = list()
|
||||||
# The codenames and ctypes that should exist.
|
# The codenames and ctypes that should exist.
|
||||||
ctypes = set()
|
ctypes = set()
|
||||||
for klass in app_models:
|
for klass in app_config.get_models():
|
||||||
ctype = contenttypes_models.ContentType.objects.get_for_model(klass)
|
# Force looking up the content types in the current database
|
||||||
ctypes.add(ctype)
|
# before creating foreign keys to them.
|
||||||
|
ctype = ContentType.objects.db_manager(using).get_for_model(klass)
|
||||||
|
|
||||||
opts = utils.model_options(klass)
|
ctypes.add(ctype)
|
||||||
perm = ('view_%s' % opts.object_name.lower(), u'Can view %s' % opts.verbose_name_raw)
|
perm = (get_permission_codename('view', klass._meta), 'Can view %s' % (klass._meta.verbose_name_raw))
|
||||||
searched_perms.append((ctype, perm))
|
searched_perms.append((ctype, perm))
|
||||||
|
|
||||||
# Find all the Permissions that have a content_type for a model we're
|
# Find all the Permissions that have a content_type for a model we're
|
||||||
# looking for. We don't need to check for codenames since we already have
|
# looking for. We don't need to check for codenames since we already have
|
||||||
# a list of the ones we're going to create.
|
# a list of the ones we're going to create.
|
||||||
all_perms = set(auth_models.Permission.objects.filter(
|
all_perms = set(Permission.objects.using(using).filter(
|
||||||
content_type__in=ctypes,
|
content_type__in=ctypes,
|
||||||
).values_list(
|
).values_list(
|
||||||
"content_type", "codename"
|
"content_type", "codename"
|
||||||
))
|
))
|
||||||
|
|
||||||
perms = [
|
perms = [
|
||||||
auth_models.Permission(codename=codename, name=name, content_type=ctype)
|
Permission(codename=codename, name=name, content_type=ct)
|
||||||
for ctype, (codename, name) in searched_perms
|
for ct, (codename, name) in searched_perms
|
||||||
if (ctype.pk, codename) not in all_perms
|
if (ct.pk, codename) not in all_perms
|
||||||
]
|
]
|
||||||
auth_models.Permission.objects.bulk_create(perms)
|
# Validate the permissions before bulk_creation to avoid cryptic
|
||||||
|
# database error when the verbose_name is longer than 50 characters
|
||||||
|
permission_name_max_length = Permission._meta.get_field('name').max_length
|
||||||
|
verbose_name_max_length = permission_name_max_length - 11 # len('Can change ') prefix
|
||||||
|
for perm in perms:
|
||||||
|
if len(perm.name) > permission_name_max_length:
|
||||||
|
raise ValidationError(
|
||||||
|
"The verbose_name of %s.%s is longer than %s characters" % (
|
||||||
|
perm.content_type.app_label,
|
||||||
|
perm.content_type.model,
|
||||||
|
verbose_name_max_length,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Permission.objects.using(using).bulk_create(perms)
|
||||||
if verbosity >= 2:
|
if verbosity >= 2:
|
||||||
for perm in perms:
|
for perm in perms:
|
||||||
logger.info("Adding permission '%s'" % perm)
|
print("Adding permission '%s'" % perm)
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,9 @@ 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.utils import formats, timezone
|
from django.utils import formats, timezone
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
from django.template.loader import render_to_string
|
|
||||||
|
|
||||||
from djadmin2 import settings
|
from djadmin2 import settings
|
||||||
|
|
||||||
|
|
|
||||||
3
djadmin2/site.py
Normal file
3
djadmin2/site.py
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
from . import core
|
||||||
|
|
||||||
|
djadmin2_site = core.Admin2()
|
||||||
62
djadmin2/tests/migrations/0001_initial.py
Normal file
62
djadmin2/tests/migrations/0001_initial.py
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='BigThing',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='RendererTestModel',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
|
||||||
|
('decimal', models.DecimalField(max_digits=10, decimal_places=5)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SmallThing',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='TagsTestsModel',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
|
||||||
|
('field1', models.CharField(max_length=23)),
|
||||||
|
('field2', models.CharField(max_length=42, verbose_name='second field')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Tags Test Model',
|
||||||
|
'verbose_name_plural': 'Tags Test Models',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Thing',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='UtilsTestModel',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
|
||||||
|
('field1', models.CharField(max_length=23)),
|
||||||
|
('field2', models.CharField(max_length=42, verbose_name='second field')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Utils Test Model',
|
||||||
|
'verbose_name_plural': 'Utils Test Models',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
0
djadmin2/tests/migrations/__init__.py
Normal file
0
djadmin2/tests/migrations/__init__.py
Normal file
50
djadmin2/tests/models.py
Normal file
50
djadmin2/tests/models.py
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Thing(models.Model):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SmallThing(models.Model):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BigThing(models.Model):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TagsTestsModel(models.Model):
|
||||||
|
|
||||||
|
field1 = models.CharField(max_length=23)
|
||||||
|
field2 = models.CharField('second field', max_length=42)
|
||||||
|
|
||||||
|
def was_published_recently(self):
|
||||||
|
return True
|
||||||
|
was_published_recently.boolean = True
|
||||||
|
was_published_recently.short_description = 'Published recently?'
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Tags Test Model"
|
||||||
|
verbose_name_plural = "Tags Test Models"
|
||||||
|
|
||||||
|
|
||||||
|
class RendererTestModel(models.Model):
|
||||||
|
decimal = models.DecimalField(decimal_places=5, max_digits=10)
|
||||||
|
|
||||||
|
|
||||||
|
class UtilsTestModel(models.Model):
|
||||||
|
|
||||||
|
field1 = models.CharField(max_length=23)
|
||||||
|
field2 = models.CharField('second field', max_length=42)
|
||||||
|
|
||||||
|
def simple_method(self):
|
||||||
|
return 42
|
||||||
|
|
||||||
|
def was_published_recently(self):
|
||||||
|
return True
|
||||||
|
was_published_recently.boolean = True
|
||||||
|
was_published_recently.short_description = 'Published recently?'
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Utils Test Model"
|
||||||
|
verbose_name_plural = "Utils Test Models"
|
||||||
|
|
@ -1,12 +1,8 @@
|
||||||
from django.db import models
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from ..core import Admin2
|
from ..core import Admin2
|
||||||
from ..actions import get_description
|
from ..actions import get_description
|
||||||
|
from .models import Thing
|
||||||
|
|
||||||
class Thing(models.Model):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class TestAction(object):
|
class TestAction(object):
|
||||||
|
|
@ -26,24 +22,24 @@ class ActionTest(TestCase):
|
||||||
self.admin2.registry[Thing].list_actions.extend([
|
self.admin2.registry[Thing].list_actions.extend([
|
||||||
TestAction,
|
TestAction,
|
||||||
test_function,
|
test_function,
|
||||||
])
|
])
|
||||||
self.assertEquals(
|
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.assertEquals(
|
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.assertEquals(
|
self.assertEquals(
|
||||||
get_description(
|
get_description(
|
||||||
self.admin2.registry[Thing].list_actions[2]
|
self.admin2.registry[Thing].list_actions[2]
|
||||||
),
|
),
|
||||||
'Test function'
|
'Test function'
|
||||||
)
|
)
|
||||||
self.admin2.registry[Thing].list_actions.remove(TestAction)
|
self.admin2.registry[Thing].list_actions.remove(TestAction)
|
||||||
self.admin2.registry[Thing].list_actions.remove(test_function)
|
self.admin2.registry[Thing].list_actions.remove(test_function)
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,10 @@
|
||||||
from django.db import models
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.forms.formsets import formset_factory
|
from django.forms.formsets import formset_factory
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from ..templatetags import admin2_tags
|
from ..templatetags import admin2_tags
|
||||||
from ..views import IndexView
|
from ..views import IndexView
|
||||||
|
from .models import TagsTestsModel
|
||||||
|
|
||||||
class TagsTestsModel(models.Model):
|
|
||||||
|
|
||||||
field1 = models.CharField(max_length=23)
|
|
||||||
field2 = models.CharField('second field', max_length=42)
|
|
||||||
|
|
||||||
def was_published_recently(self):
|
|
||||||
return True
|
|
||||||
was_published_recently.boolean = True
|
|
||||||
was_published_recently.short_description = 'Published recently?'
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Tags Test Model"
|
|
||||||
verbose_name_plural = "Tags Test Models"
|
|
||||||
|
|
||||||
|
|
||||||
class TagsTestForm(forms.Form):
|
class TagsTestForm(forms.Form):
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ from django.test.client import RequestFactory
|
||||||
|
|
||||||
import floppyforms
|
import floppyforms
|
||||||
|
|
||||||
import djadmin2
|
from djadmin2.site import djadmin2_site
|
||||||
from ..admin2 import UserAdmin2
|
from ..admin2 import UserAdmin2
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -27,7 +27,7 @@ class UserAdminTest(TestCase):
|
||||||
|
|
||||||
request = self.factory.get(reverse('admin2:auth_user_create'))
|
request = self.factory.get(reverse('admin2:auth_user_create'))
|
||||||
request.user = self.user
|
request.user = self.user
|
||||||
model_admin = UserAdmin2(User, djadmin2.default)
|
model_admin = UserAdmin2(User, djadmin2_site)
|
||||||
view = model_admin.create_view.view.as_view(
|
view = model_admin.create_view.view.as_view(
|
||||||
**model_admin.get_create_kwargs())
|
**model_admin.get_create_kwargs())
|
||||||
response = view(request)
|
response = view(request)
|
||||||
|
|
@ -48,7 +48,7 @@ class UserAdminTest(TestCase):
|
||||||
request = self.factory.get(
|
request = self.factory.get(
|
||||||
reverse('admin2:auth_user_update', args=(self.user.pk,)))
|
reverse('admin2:auth_user_update', args=(self.user.pk,)))
|
||||||
request.user = self.user
|
request.user = self.user
|
||||||
model_admin = UserAdmin2(User, djadmin2.default)
|
model_admin = UserAdmin2(User, djadmin2_site)
|
||||||
view = model_admin.update_view.view.as_view(
|
view = model_admin.update_view.view.as_view(
|
||||||
**model_admin.get_update_kwargs())
|
**model_admin.get_update_kwargs())
|
||||||
response = view(request, pk=self.user.pk)
|
response = view(request, pk=self.user.pk)
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,12 @@
|
||||||
from django.db import models
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
|
||||||
from django.test import TestCase
|
|
||||||
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
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
import djadmin2
|
from djadmin2.site import djadmin2_site
|
||||||
from ..types import ModelAdmin2
|
from .models import SmallThing
|
||||||
from ..core import Admin2
|
from ..core import Admin2
|
||||||
|
from ..types import ModelAdmin2
|
||||||
|
|
||||||
class SmallThing(models.Model):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
APP_LABEL, APP_VERBOSE_NAME = 'app_one_label', 'App One Verbose Name'
|
APP_LABEL, APP_VERBOSE_NAME = 'app_one_label', 'App One Verbose Name'
|
||||||
|
|
||||||
|
|
@ -71,4 +66,4 @@ class Admin2Test(TestCase):
|
||||||
def test_default_entries(self):
|
def test_default_entries(self):
|
||||||
expected_default_models = (User, Group, Site)
|
expected_default_models = (User, Group, Site)
|
||||||
for model in expected_default_models:
|
for model in expected_default_models:
|
||||||
self.assertTrue(isinstance(djadmin2.default.registry[model], ModelAdmin2))
|
self.assertTrue(isinstance(djadmin2_site.registry[model], ModelAdmin2))
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,11 @@ import datetime as dt
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.db import models
|
|
||||||
from django.utils.translation import activate
|
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
from django.utils.translation import activate
|
||||||
|
|
||||||
from .. import renderers
|
from .. import renderers
|
||||||
|
from .models import RendererTestModel
|
||||||
|
|
||||||
class RendererTestModel(models.Model):
|
|
||||||
decimal = models.DecimalField(decimal_places=5, max_digits=10)
|
|
||||||
|
|
||||||
|
|
||||||
class BooleanRendererTest(TestCase):
|
class BooleanRendererTest(TestCase):
|
||||||
|
|
@ -109,7 +105,7 @@ class NumberRendererTest(TestCase):
|
||||||
self.assertEqual('42.5', out)
|
self.assertEqual('42.5', out)
|
||||||
|
|
||||||
def testEndlessFloat(self):
|
def testEndlessFloat(self):
|
||||||
out = self.renderer(1.0/3, None)
|
out = self.renderer(1.0 / 3, None)
|
||||||
if six.PY2:
|
if six.PY2:
|
||||||
self.assertEqual('0.333333333333', out)
|
self.assertEqual('0.333333333333', out)
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
from django.db import models
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.views.generic import View
|
|
||||||
|
|
||||||
from .. import views
|
from .. import views
|
||||||
from ..types import ModelAdmin2, immutable_admin_factory
|
from ..types import ModelAdmin2, immutable_admin_factory
|
||||||
from ..core import Admin2
|
from ..core import Admin2
|
||||||
|
from .models import BigThing
|
||||||
|
|
||||||
|
|
||||||
class ModelAdmin(object):
|
class ModelAdmin(object):
|
||||||
|
|
@ -40,10 +39,6 @@ class ImmutableAdminFactoryTests(TestCase):
|
||||||
self.immutable_admin.d
|
self.immutable_admin.d
|
||||||
|
|
||||||
|
|
||||||
class BigThing(models.Model):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ModelAdminTest(TestCase):
|
class ModelAdminTest(TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,9 @@
|
||||||
from django.db import models
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
|
||||||
from .. import utils
|
from .. import utils
|
||||||
from ..views import IndexView
|
from ..views import IndexView
|
||||||
|
from .models import UtilsTestModel
|
||||||
|
|
||||||
class UtilsTestModel(models.Model):
|
|
||||||
|
|
||||||
field1 = models.CharField(max_length=23)
|
|
||||||
field2 = models.CharField('second field', max_length=42)
|
|
||||||
|
|
||||||
def simple_method(self):
|
|
||||||
return 42
|
|
||||||
|
|
||||||
def was_published_recently(self):
|
|
||||||
return True
|
|
||||||
was_published_recently.boolean = True
|
|
||||||
was_published_recently.short_description = 'Published recently?'
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Utils Test Model"
|
|
||||||
verbose_name_plural = "Utils Test Models"
|
|
||||||
|
|
||||||
|
|
||||||
class UtilsTest(TestCase):
|
class UtilsTest(TestCase):
|
||||||
|
|
@ -40,7 +22,7 @@ class UtilsTest(TestCase):
|
||||||
self.assertEquals(
|
self.assertEquals(
|
||||||
UtilsTestModel._meta,
|
UtilsTestModel._meta,
|
||||||
utils.model_options(UtilsTestModel)
|
utils.model_options(UtilsTestModel)
|
||||||
)
|
)
|
||||||
UtilsTestModel._meta.verbose_name = "Utils Test Model"
|
UtilsTestModel._meta.verbose_name = "Utils Test Model"
|
||||||
UtilsTestModel._meta.verbose_name_plural = "Utils Test Models"
|
UtilsTestModel._meta.verbose_name_plural = "Utils Test Models"
|
||||||
|
|
||||||
|
|
@ -55,7 +37,7 @@ class UtilsTest(TestCase):
|
||||||
self.assertEquals(
|
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"
|
self.instance._meta.verbose_name = "Utils Test Model"
|
||||||
self.instance._meta.verbose_name_plural = "Utils Test Models"
|
self.instance._meta.verbose_name_plural = "Utils Test Models"
|
||||||
|
|
||||||
|
|
@ -166,8 +148,6 @@ class UtilsTest(TestCase):
|
||||||
"str"
|
"str"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_attr(self):
|
def test_get_attr(self):
|
||||||
class Klass(object):
|
class Klass(object):
|
||||||
attr = "value"
|
attr = "value"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.views.generic import View
|
|
||||||
|
|
||||||
from .. import views
|
from .. import views
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,21 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
from __future__ import division, absolute_import, unicode_literals
|
||||||
|
|
||||||
from collections import namedtuple
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
from collections import namedtuple
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.conf.urls import patterns, url
|
|
||||||
from django.utils.six import with_metaclass
|
|
||||||
|
|
||||||
import extra_views
|
import extra_views
|
||||||
|
from django.conf.urls import url
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.utils.six import with_metaclass
|
||||||
|
|
||||||
|
from . import actions
|
||||||
from . import apiviews
|
from . import apiviews
|
||||||
from . import settings
|
from . import settings
|
||||||
from . import views
|
|
||||||
from . import actions
|
|
||||||
from . import utils
|
from . import utils
|
||||||
|
from . import views
|
||||||
from .forms import modelform_factory
|
from .forms import modelform_factory
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -202,7 +201,8 @@ class ModelAdmin2(with_metaclass(ModelAdminBase2)):
|
||||||
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({
|
||||||
'paginate_by': self.list_per_page,
|
'queryset': self.model.objects.all(),
|
||||||
|
# 'paginate_by': self.list_per_page,
|
||||||
})
|
})
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
|
|
@ -236,11 +236,10 @@ class ModelAdmin2(with_metaclass(ModelAdminBase2)):
|
||||||
name=self.get_prefixed_view_name(admin_view.name)
|
name=self.get_prefixed_view_name(admin_view.name)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return patterns('', *pattern_list)
|
return pattern_list
|
||||||
|
|
||||||
def get_api_urls(self):
|
def get_api_urls(self):
|
||||||
return patterns(
|
return [
|
||||||
'',
|
|
||||||
url(
|
url(
|
||||||
regex=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()),
|
||||||
|
|
@ -252,7 +251,7 @@ class ModelAdmin2(with_metaclass(ModelAdminBase2)):
|
||||||
**self.get_api_detail_kwargs()),
|
**self.get_api_detail_kwargs()),
|
||||||
name=self.get_prefixed_view_name('api_detail'),
|
name=self.get_prefixed_view_name('api_detail'),
|
||||||
),
|
),
|
||||||
)
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def urls(self):
|
def urls(self):
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,12 @@ from __future__ import division, absolute_import, unicode_literals
|
||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
from django.db.models import ProtectedError
|
from django.db.models.deletion import Collector, ProtectedError
|
||||||
from django.db.models import ManyToManyRel
|
from django.db.models.sql.constants import QUERY_TERMS
|
||||||
from django.db.models.deletion import Collector
|
|
||||||
from django.db.models.fields.related import ForeignObjectRel
|
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from django.utils.encoding import force_bytes, force_text
|
from django.utils.encoding import force_bytes, force_text
|
||||||
|
|
||||||
|
|
||||||
def lookup_needs_distinct(opts, lookup_path):
|
def lookup_needs_distinct(opts, lookup_path):
|
||||||
"""
|
"""
|
||||||
Returns True if 'distinct()' should be used to query the given lookup path.
|
Returns True if 'distinct()' should be used to query the given lookup path.
|
||||||
|
|
@ -17,13 +16,24 @@ def lookup_needs_distinct(opts, lookup_path):
|
||||||
This is adopted from the Django core. django-admin2 mandates that code
|
This is adopted from the Django core. django-admin2 mandates that code
|
||||||
doesn't depend on imports from django.contrib.admin.
|
doesn't depend on imports from django.contrib.admin.
|
||||||
|
|
||||||
https://github.com/django/django/blob/1.5.1/django/contrib/admin/util.py#L20
|
https://github.com/django/django/blob/1.9.6/django/contrib/admin/utils.py#L22
|
||||||
"""
|
"""
|
||||||
field_name = lookup_path.split('__', 1)[0]
|
|
||||||
field = opts.get_field_by_name(field_name)[0]
|
lookup_fields = lookup_path.split('__')
|
||||||
condition1 = hasattr(field, 'rel') and isinstance(field.rel, ManyToManyRel)
|
# Remove the last item of the lookup path if it is a query term
|
||||||
condition2 = isinstance(field, ForeignObjectRel) and not field.field.unique
|
if lookup_fields[-1] in QUERY_TERMS:
|
||||||
return condition1 or condition2
|
lookup_fields = lookup_fields[:-1]
|
||||||
|
# Now go through the fields (following all relations) and look for an m2m
|
||||||
|
for field_name in lookup_fields:
|
||||||
|
field = opts.get_field(field_name)
|
||||||
|
if hasattr(field, 'get_path_info'):
|
||||||
|
# This field is a relation, update opts to follow the relation
|
||||||
|
path_info = field.get_path_info()
|
||||||
|
opts = path_info[-1].to_opts
|
||||||
|
if any(path.m2m for path in path_info):
|
||||||
|
# This field is a m2m relation so we know we need to call distinct
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def model_options(model):
|
def model_options(model):
|
||||||
|
|
@ -91,10 +101,8 @@ def get_attr(obj, attr):
|
||||||
and the __str__ attribute.
|
and the __str__ attribute.
|
||||||
"""
|
"""
|
||||||
if attr == '__str__':
|
if attr == '__str__':
|
||||||
if six.PY2:
|
from builtins import str as text
|
||||||
value = unicode(obj)
|
value = text(obj)
|
||||||
else:
|
|
||||||
value = str(obj)
|
|
||||||
else:
|
else:
|
||||||
attribute = getattr(obj, attr)
|
attribute = getattr(obj, attr)
|
||||||
value = attribute() if callable(attribute) else attribute
|
value = attribute() if callable(attribute) else attribute
|
||||||
|
|
@ -106,15 +114,14 @@ class NestedObjects(Collector):
|
||||||
This is adopted from the Django core. django-admin2 mandates that code
|
This is adopted from the Django core. django-admin2 mandates that code
|
||||||
doesn't depend on imports from django.contrib.admin.
|
doesn't depend on imports from django.contrib.admin.
|
||||||
|
|
||||||
https://github.com/django/django/blob/1.8c1/django/contrib/admin/utils.py#L160-L221
|
https://github.com/django/django/blob/1.9.6/django/contrib/admin/utils.py#L171-L231
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(NestedObjects, self).__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_count = defaultdict(int)
|
self.model_objs = defaultdict(set)
|
||||||
|
|
||||||
def add_edge(self, source, target):
|
def add_edge(self, source, target):
|
||||||
self.edges.setdefault(source, []).append(target)
|
self.edges.setdefault(source, []).append(target)
|
||||||
|
|
@ -129,7 +136,7 @@ class NestedObjects(Collector):
|
||||||
self.add_edge(getattr(obj, related_name), obj)
|
self.add_edge(getattr(obj, related_name), obj)
|
||||||
else:
|
else:
|
||||||
self.add_edge(None, obj)
|
self.add_edge(None, obj)
|
||||||
self.model_count[obj._meta.verbose_name_plural] += 1
|
self.model_objs[obj._meta.model].add(obj)
|
||||||
try:
|
try:
|
||||||
return super(NestedObjects, self).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:
|
||||||
|
|
@ -157,7 +164,6 @@ class NestedObjects(Collector):
|
||||||
def nested(self, format_callback=None):
|
def nested(self, format_callback=None):
|
||||||
"""
|
"""
|
||||||
Return the graph as a nested list.
|
Return the graph as a nested list.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
seen = set()
|
seen = set()
|
||||||
roots = []
|
roots = []
|
||||||
|
|
@ -183,14 +189,14 @@ def quote(s):
|
||||||
This is adopted from the Django core. django-admin2 mandates that code
|
This is adopted from the Django core. django-admin2 mandates that code
|
||||||
doesn't depend on imports from django.contrib.admin.
|
doesn't depend on imports from django.contrib.admin.
|
||||||
|
|
||||||
https://github.com/django/django/blob/1.5.1/django/contrib/admin/util.py#L48-L62
|
https://github.com/django/django/blob/1.9.6/django/contrib/admin/utils.py#L66-L73
|
||||||
"""
|
"""
|
||||||
if not isinstance(s, six.string_types):
|
if not isinstance(s, 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)):
|
||||||
c = res[i]
|
c = res[i]
|
||||||
if c in """:/_#?;@&=+$,"<>%\\""":
|
if c in """:/_#?;@&=+$,"[]<>%\n\\""":
|
||||||
res[i] = '_%02X' % ord(c)
|
res[i] = '_%02X' % ord(c)
|
||||||
return ''.join(res)
|
return ''.join(res)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -116,11 +116,11 @@ class Admin2Mixin(PermissionMixin):
|
||||||
return super(Admin2Mixin, self).dispatch(request, *args, **kwargs)
|
return super(Admin2Mixin, self).dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class AdminModel2Mixin(Admin2Mixin):
|
class Admin2ModelMixin(Admin2Mixin):
|
||||||
model_admin = None
|
model_admin = None
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(AdminModel2Mixin, self).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
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,12 @@
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
from __future__ import division, absolute_import, unicode_literals
|
||||||
|
|
||||||
import operator
|
import operator
|
||||||
|
from datetime import datetime
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
|
|
||||||
from datetime import datetime
|
import extra_views
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.contrib.auth import update_session_auth_hash
|
||||||
from django.contrib.auth.forms import (PasswordChangeForm,
|
from django.contrib.auth.forms import (PasswordChangeForm,
|
||||||
AdminPasswordChangeForm)
|
AdminPasswordChangeForm)
|
||||||
from django.contrib.auth.views import (logout as auth_logout,
|
from django.contrib.auth.views import (logout as auth_logout,
|
||||||
|
|
@ -21,14 +22,13 @@ from django.utils.encoding import force_text
|
||||||
from django.utils.text import capfirst
|
from django.utils.text import capfirst
|
||||||
from django.utils.translation import ugettext_lazy
|
from django.utils.translation import ugettext_lazy
|
||||||
from django.views import generic
|
from django.views import generic
|
||||||
import extra_views
|
|
||||||
|
|
||||||
|
|
||||||
from . import permissions, utils
|
from . import permissions, utils
|
||||||
from .forms import AdminAuthenticationForm
|
|
||||||
from .viewmixins import Admin2Mixin, AdminModel2Mixin, Admin2ModelFormMixin
|
|
||||||
from .filters import build_list_filter, build_date_filter
|
from .filters import build_list_filter, build_date_filter
|
||||||
|
from .forms import AdminAuthenticationForm
|
||||||
from .models import LogEntry
|
from .models import LogEntry
|
||||||
|
from .viewmixins import Admin2Mixin, Admin2ModelMixin, Admin2ModelFormMixin
|
||||||
|
|
||||||
|
|
||||||
class AdminView(object):
|
class AdminView(object):
|
||||||
|
|
||||||
|
|
@ -101,7 +101,7 @@ class AppIndexView(Admin2Mixin, generic.TemplateView):
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
class ModelListView(AdminModel2Mixin, generic.ListView):
|
class ModelListView(Admin2ModelMixin, generic.ListView):
|
||||||
"""Context Variables
|
"""Context Variables
|
||||||
|
|
||||||
:is_paginated: If the page is paginated (page has a next button)
|
:is_paginated: If the page is paginated (page has a next button)
|
||||||
|
|
@ -186,7 +186,7 @@ class ModelListView(AdminModel2Mixin, generic.ListView):
|
||||||
queryset = self.build_list_filter(queryset).qs
|
queryset = self.build_list_filter(queryset).qs
|
||||||
|
|
||||||
if self.model_admin.date_hierarchy:
|
if self.model_admin.date_hierarchy:
|
||||||
queryset = self.build_date_filter(queryset).qs
|
queryset = self.build_date_filter(queryset, self.model_admin.date_hierarchy).qs
|
||||||
|
|
||||||
queryset = self._modify_queryset_for_sort(queryset)
|
queryset = self._modify_queryset_for_sort(queryset)
|
||||||
|
|
||||||
|
|
@ -233,7 +233,7 @@ class ModelListView(AdminModel2Mixin, generic.ListView):
|
||||||
)
|
)
|
||||||
return self._list_filter
|
return self._list_filter
|
||||||
|
|
||||||
def build_date_filter(self, queryset=None):
|
def build_date_filter(self, queryset=None, field_name=None):
|
||||||
if not hasattr(self, "_date_filter"):
|
if not hasattr(self, "_date_filter"):
|
||||||
if queryset is None:
|
if queryset is None:
|
||||||
queryset = self.get_queryset()
|
queryset = self.get_queryset()
|
||||||
|
|
@ -241,6 +241,7 @@ class ModelListView(AdminModel2Mixin, generic.ListView):
|
||||||
self.request,
|
self.request,
|
||||||
self.model_admin,
|
self.model_admin,
|
||||||
queryset,
|
queryset,
|
||||||
|
field_name
|
||||||
)
|
)
|
||||||
|
|
||||||
return self._date_filter
|
return self._date_filter
|
||||||
|
|
@ -271,38 +272,38 @@ class ModelListView(AdminModel2Mixin, generic.ListView):
|
||||||
|
|
||||||
context["active_day"] = new_date.strftime("%B %d")
|
context["active_day"] = new_date.strftime("%B %d")
|
||||||
|
|
||||||
context["dates"] = self._format_days(context)
|
context["dates"] = self._format_days(self.get_queryset())
|
||||||
elif year and month:
|
elif year and month:
|
||||||
context["previous_date"] = {
|
context["previous_date"] = {
|
||||||
"link": "?year=%s" % (year),
|
"link": "?year=%s" % (year),
|
||||||
"text": "‹ %s" % year,
|
"text": "‹ %s" % year,
|
||||||
}
|
}
|
||||||
|
|
||||||
context["dates"] = self._format_days(context)
|
context["dates"] = self._format_days(self.get_queryset())
|
||||||
elif year:
|
elif year:
|
||||||
context["previous_date"] = {
|
context["previous_date"] = {
|
||||||
"link": "?",
|
"link": "?",
|
||||||
"text": ugettext_lazy("‹ All dates"),
|
"text": ugettext_lazy("‹ All dates"),
|
||||||
}
|
}
|
||||||
|
|
||||||
context["dates"] = self._format_months(context)
|
context["dates"] = self._format_months(self.get_queryset())
|
||||||
else:
|
else:
|
||||||
context["dates"] = self._format_years(context)
|
context["dates"] = self._format_years(self.get_queryset())
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def _format_years(self, context):
|
def _format_years(self, queryset):
|
||||||
years = self._qs_date_or_datetime(context['object_list'], 'year')
|
years = self._qs_date_or_datetime(queryset, 'year')
|
||||||
if len(years) == 1:
|
if len(years) == 1:
|
||||||
return self._format_months(context)
|
return self._format_months(queryset)
|
||||||
else:
|
else:
|
||||||
return [
|
return [
|
||||||
(("?year=%s" % year.strftime("%Y")), year.strftime("%Y"))
|
(("?year=%s" % year.strftime("%Y")), year.strftime("%Y"))
|
||||||
for year in
|
for year in
|
||||||
self._qs_date_or_datetime(context['object_list'], 'year')
|
self._qs_date_or_datetime(queryset, 'year')
|
||||||
]
|
]
|
||||||
|
|
||||||
def _format_months(self, context):
|
def _format_months(self, queryset):
|
||||||
return [
|
return [
|
||||||
(
|
(
|
||||||
"?year=%s&month=%s" % (
|
"?year=%s&month=%s" % (
|
||||||
|
|
@ -310,10 +311,10 @@ class ModelListView(AdminModel2Mixin, generic.ListView):
|
||||||
),
|
),
|
||||||
date.strftime("%B %Y")
|
date.strftime("%B %Y")
|
||||||
) for date in
|
) for date in
|
||||||
self._qs_date_or_datetime(context['object_list'], 'month')
|
self._qs_date_or_datetime(queryset, 'month')
|
||||||
]
|
]
|
||||||
|
|
||||||
def _format_days(self, context):
|
def _format_days(self, queryset):
|
||||||
return [
|
return [
|
||||||
(
|
(
|
||||||
"?year=%s&month=%s&day=%s" % (
|
"?year=%s&month=%s&day=%s" % (
|
||||||
|
|
@ -323,7 +324,7 @@ class ModelListView(AdminModel2Mixin, generic.ListView):
|
||||||
),
|
),
|
||||||
date.strftime("%B %d")
|
date.strftime("%B %d")
|
||||||
) for date in
|
) for date in
|
||||||
self._qs_date_or_datetime(context['object_list'], 'day')
|
self._qs_date_or_datetime(queryset, 'day')
|
||||||
]
|
]
|
||||||
|
|
||||||
def _qs_date_or_datetime(self, object_list, type):
|
def _qs_date_or_datetime(self, object_list, type):
|
||||||
|
|
@ -345,7 +346,7 @@ class ModelListView(AdminModel2Mixin, generic.ListView):
|
||||||
return self.model_admin.search_fields
|
return self.model_admin.search_fields
|
||||||
|
|
||||||
|
|
||||||
class ModelDetailView(AdminModel2Mixin, generic.DetailView):
|
class ModelDetailView(Admin2ModelMixin, generic.DetailView):
|
||||||
"""Context Variables
|
"""Context Variables
|
||||||
|
|
||||||
:model: Type of object you are editing
|
:model: Type of object you are editing
|
||||||
|
|
@ -363,7 +364,7 @@ class ModelDetailView(AdminModel2Mixin, generic.DetailView):
|
||||||
permissions.ModelViewPermission)
|
permissions.ModelViewPermission)
|
||||||
|
|
||||||
|
|
||||||
class ModelEditFormView(AdminModel2Mixin, Admin2ModelFormMixin,
|
class ModelEditFormView(Admin2ModelMixin, Admin2ModelFormMixin,
|
||||||
extra_views.UpdateWithInlinesView):
|
extra_views.UpdateWithInlinesView):
|
||||||
"""Context Variables
|
"""Context Variables
|
||||||
|
|
||||||
|
|
@ -399,7 +400,7 @@ class ModelEditFormView(AdminModel2Mixin, Admin2ModelFormMixin,
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
class ModelAddFormView(AdminModel2Mixin, Admin2ModelFormMixin,
|
class ModelAddFormView(Admin2ModelMixin, Admin2ModelFormMixin,
|
||||||
extra_views.CreateWithInlinesView):
|
extra_views.CreateWithInlinesView):
|
||||||
"""Context Variables
|
"""Context Variables
|
||||||
|
|
||||||
|
|
@ -435,7 +436,7 @@ class ModelAddFormView(AdminModel2Mixin, Admin2ModelFormMixin,
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
class ModelDeleteView(AdminModel2Mixin, generic.DeleteView):
|
class ModelDeleteView(Admin2ModelMixin, generic.DeleteView):
|
||||||
"""Context Variables
|
"""Context Variables
|
||||||
|
|
||||||
:model: Type of object you are editing
|
:model: Type of object you are editing
|
||||||
|
|
@ -479,7 +480,7 @@ class ModelDeleteView(AdminModel2Mixin, generic.DeleteView):
|
||||||
return super(ModelDeleteView, self).delete(request, *args, **kwargs)
|
return super(ModelDeleteView, self).delete(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class ModelHistoryView(AdminModel2Mixin, generic.ListView):
|
class ModelHistoryView(Admin2ModelMixin, generic.ListView):
|
||||||
"""Context Variables
|
"""Context Variables
|
||||||
|
|
||||||
:model: Type of object you are editing
|
:model: Type of object you are editing
|
||||||
|
|
@ -541,6 +542,13 @@ class PasswordChangeView(Admin2Mixin, generic.UpdateView):
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
return get_user_model()._default_manager.all()
|
return get_user_model()._default_manager.all()
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
self.object = form.save()
|
||||||
|
if self.request.user == self.get_object():
|
||||||
|
update_session_auth_hash(self.request, form.user)
|
||||||
|
return HttpResponseRedirect(self.get_success_url())
|
||||||
|
|
||||||
|
|
||||||
class PasswordChangeDoneView(Admin2Mixin, generic.TemplateView):
|
class PasswordChangeDoneView(Admin2Mixin, generic.TemplateView):
|
||||||
|
|
||||||
default_template_name = 'auth/password_change_done.html'
|
default_template_name = 'auth/password_change_done.html'
|
||||||
|
|
|
||||||
|
|
@ -33,17 +33,17 @@ Add djadmin2 urls to your URLconf:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
# urls.py
|
# urls.py
|
||||||
from django.conf.urls import patterns, include
|
from django.conf.urls import include
|
||||||
|
|
||||||
import djadmin2
|
import djadmin2
|
||||||
|
|
||||||
djadmin2.default.autodiscover()
|
djadmin2.default.autodiscover()
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = patterns(
|
urlpatterns = [
|
||||||
...
|
...
|
||||||
url(r'^admin2/', include(djadmin2.default.urls)),
|
url(r'^admin2/', include(djadmin2.default.urls)),
|
||||||
)
|
]
|
||||||
|
|
||||||
Development Installation
|
Development Installation
|
||||||
=========================
|
=========================
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ How To Create a Theme
|
||||||
A Django Admin 2 theme is merely a packaged Django app. Here are the necessary steps to create a theme called '*dandy*':
|
A Django Admin 2 theme is merely a packaged Django app. Here are the necessary steps to create a theme called '*dandy*':
|
||||||
|
|
||||||
|
|
||||||
1. Make sure you have Django 1.5 or higher installed.
|
1. Make sure you have Django 1.8 or higher installed.
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
from __future__ import division, absolute_import, unicode_literals
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy, pgettext_lazy
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
from django.utils.translation import ugettext_lazy, pgettext_lazy
|
||||||
|
|
||||||
from djadmin2.actions import BaseListAction
|
|
||||||
from djadmin2 import permissions
|
from djadmin2 import permissions
|
||||||
|
from djadmin2.actions import BaseListAction
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,21 +3,22 @@ from __future__ import division, absolute_import, unicode_literals
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy
|
from django.utils.translation import ugettext_lazy
|
||||||
|
|
||||||
import djadmin2
|
|
||||||
from djadmin2 import renderers
|
from djadmin2 import renderers
|
||||||
from djadmin2.actions import DeleteSelectedAction
|
from djadmin2.actions import DeleteSelectedAction
|
||||||
|
|
||||||
# Import your custom models
|
# Import your custom models
|
||||||
|
from djadmin2.site import djadmin2_site
|
||||||
|
from djadmin2.types import Admin2TabularInline, ModelAdmin2
|
||||||
from .actions import (CustomPublishAction, PublishAllItemsAction,
|
from .actions import (CustomPublishAction, PublishAllItemsAction,
|
||||||
unpublish_items, unpublish_all_items)
|
unpublish_items, unpublish_all_items)
|
||||||
from .models import Post, Comment
|
from .models import Post, Comment
|
||||||
|
|
||||||
|
|
||||||
class CommentInline(djadmin2.Admin2TabularInline):
|
class CommentInline(Admin2TabularInline):
|
||||||
model = Comment
|
model = Comment
|
||||||
|
|
||||||
|
|
||||||
class PostAdmin(djadmin2.ModelAdmin2):
|
class PostAdmin(ModelAdmin2):
|
||||||
list_actions = [
|
list_actions = [
|
||||||
DeleteSelectedAction, CustomPublishAction,
|
DeleteSelectedAction, CustomPublishAction,
|
||||||
PublishAllItemsAction, unpublish_items,
|
PublishAllItemsAction, unpublish_items,
|
||||||
|
|
@ -34,7 +35,7 @@ class PostAdmin(djadmin2.ModelAdmin2):
|
||||||
ordering = ["-published_date", "title",]
|
ordering = ["-published_date", "title",]
|
||||||
|
|
||||||
|
|
||||||
class CommentAdmin(djadmin2.ModelAdmin2):
|
class CommentAdmin(ModelAdmin2):
|
||||||
search_fields = ('body', '=post__title')
|
search_fields = ('body', '=post__title')
|
||||||
list_filter = ['post', ]
|
list_filter = ['post', ]
|
||||||
actions_on_top = True
|
actions_on_top = True
|
||||||
|
|
@ -42,12 +43,12 @@ class CommentAdmin(djadmin2.ModelAdmin2):
|
||||||
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.default.register_app_verbose_name(
|
djadmin2_site.register_app_verbose_name(
|
||||||
'blog',
|
'blog',
|
||||||
ugettext_lazy('My Blog')
|
ugettext_lazy('My Blog')
|
||||||
)
|
)
|
||||||
|
|
||||||
# Register each model with the admin
|
# Register each model with the admin
|
||||||
djadmin2.default.register(Post, PostAdmin)
|
djadmin2_site.register(Post, PostAdmin)
|
||||||
djadmin2.default.register(Comment, CommentAdmin)
|
djadmin2_site.register(Comment, CommentAdmin)
|
||||||
|
|
||||||
|
|
|
||||||
84
example/blog/migrations/0001_initial.py
Normal file
84
example/blog/migrations/0001_initial.py
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Comment',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
|
||||||
|
('body', models.TextField(verbose_name='body')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'comment',
|
||||||
|
'verbose_name_plural': 'comments',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Count',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
|
||||||
|
('num', models.PositiveSmallIntegerField()),
|
||||||
|
('parent', models.ForeignKey(to='blog.Count', null=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Event',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
|
||||||
|
('date', models.DateTimeField(auto_now_add=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='EventGuide',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
|
||||||
|
('event', models.ForeignKey(to='blog.Event', on_delete=django.db.models.deletion.DO_NOTHING)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Guest',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
|
||||||
|
('name', models.CharField(max_length=255)),
|
||||||
|
('event', models.OneToOneField(to='blog.Event')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'awesome guest',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Location',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
|
||||||
|
('event', models.OneToOneField(verbose_name='awesome event', to='blog.Event')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Post',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
|
||||||
|
('title', models.CharField(max_length=255, verbose_name='title')),
|
||||||
|
('body', models.TextField(verbose_name='body')),
|
||||||
|
('published', models.BooleanField(verbose_name='published', default=False)),
|
||||||
|
('published_date', models.DateField(blank=True, null=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'post',
|
||||||
|
'verbose_name_plural': 'posts',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='comment',
|
||||||
|
name='post',
|
||||||
|
field=models.ForeignKey(related_name='comments', verbose_name='post', to='blog.Post'),
|
||||||
|
),
|
||||||
|
]
|
||||||
0
example/blog/migrations/__init__.py
Normal file
0
example/blog/migrations/__init__.py
Normal file
|
|
@ -1,4 +1,4 @@
|
||||||
{% load i18n static %}<!DOCTYPE html>
|
{% load i18n staticfiles %}<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
|
|
@ -7,8 +7,8 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<!-- Bootstrap -->
|
<!-- Bootstrap -->
|
||||||
{% block css %}
|
{% block css %}
|
||||||
<link href="{{ STATIC_URL }}themes/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen">
|
<link href="{% static "themes/bootstrap/css/bootstrap.min.css" %}" rel="stylesheet" media="screen">
|
||||||
<link href="{{ STATIC_URL }}themes/bootstrap/css/bootstrap-custom.css" rel="stylesheet" media="screen">
|
<link href="{% static "themes/bootstrap/css/bootstrap-custom.css" %}" rel="stylesheet" media="screen">
|
||||||
{% endblock css %}
|
{% endblock css %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
@ -19,8 +19,8 @@
|
||||||
|
|
||||||
{% block javascript %}
|
{% block javascript %}
|
||||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
|
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
|
||||||
<script>window.jQuery || document.write('<script src="{{ STATIC_URL }}themes/bootstrap/js/jquery.min.js">\x3C/script>')</script>
|
<script>window.jQuery || document.write('<script src="{% static "themes/bootstrap/js/jquery.min.js" %}">\x3C/script>')</script>
|
||||||
<script src="{{ STATIC_URL }}themes/bootstrap/js/bootstrap.min.js"></script>
|
<script src="{% static "themes/bootstrap/js/bootstrap.min.js" %}"></script>
|
||||||
{% endblock javascript %}
|
{% endblock javascript %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,17 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
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.core.urlresolvers import reverse
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
import json
|
|
||||||
|
|
||||||
|
|
||||||
from djadmin2 import apiviews
|
from djadmin2 import apiviews
|
||||||
from djadmin2 import default
|
from djadmin2.site import djadmin2_site
|
||||||
from djadmin2 import ModelAdmin2
|
from djadmin2.types import ModelAdmin2
|
||||||
from ..models import Post
|
from ..models import Post
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -24,21 +25,21 @@ class APITestCase(TestCase):
|
||||||
self.user.save()
|
self.user.save()
|
||||||
|
|
||||||
def get_model_admin(self, model):
|
def get_model_admin(self, model):
|
||||||
return ModelAdmin2(model, default)
|
return ModelAdmin2(model, djadmin2_site)
|
||||||
|
|
||||||
|
|
||||||
class IndexAPIViewTest(APITestCase):
|
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(**default.get_api_index_kwargs())
|
view = apiviews.IndexAPIView.as_view(**djadmin2_site.get_api_index_kwargs())
|
||||||
response = view(request)
|
response = view(request)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
def test_view_permission(self):
|
def test_view_permission(self):
|
||||||
request = self.factory.get(reverse('admin2:api_index'))
|
request = self.factory.get(reverse('admin2:api_index'))
|
||||||
request.user = AnonymousUser()
|
request.user = AnonymousUser()
|
||||||
view = apiviews.IndexAPIView.as_view(**default.get_api_index_kwargs())
|
view = apiviews.IndexAPIView.as_view(**djadmin2_site.get_api_index_kwargs())
|
||||||
self.assertRaises(PermissionDenied, view, request)
|
self.assertRaises(PermissionDenied, view, request)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -71,7 +72,7 @@ class ListCreateAPIViewTest(APITestCase):
|
||||||
response.render()
|
response.render()
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertIn('"__unicode__": "Foo"', force_text(response.content))
|
self.assertIn('"__unicode__":"Foo"', force_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'))
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,15 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# vim:fenc=utf-8
|
# vim:fenc=utf-8
|
||||||
|
|
||||||
|
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.core.urlresolvers import reverse
|
|
||||||
|
|
||||||
|
from djadmin2 import filters as djadmin2_filters
|
||||||
|
from djadmin2.types import ModelAdmin2
|
||||||
from ..models import Post
|
from ..models import Post
|
||||||
|
|
||||||
import djadmin2
|
|
||||||
import djadmin2.filters as djadmin2_filters
|
|
||||||
|
|
||||||
import django_filters
|
|
||||||
|
|
||||||
class ListFilterBuilderTest(TestCase):
|
class ListFilterBuilderTest(TestCase):
|
||||||
|
|
||||||
|
|
@ -18,11 +17,11 @@ class ListFilterBuilderTest(TestCase):
|
||||||
self.rf = RequestFactory()
|
self.rf = RequestFactory()
|
||||||
|
|
||||||
def test_filter_building(self):
|
def test_filter_building(self):
|
||||||
class PostAdminSimple(djadmin2.ModelAdmin2):
|
class PostAdminSimple(ModelAdmin2):
|
||||||
list_filter = ['published', ]
|
list_filter = ['published', ]
|
||||||
|
|
||||||
|
|
||||||
class PostAdminWithFilterInstances(djadmin2.ModelAdmin2):
|
class PostAdminWithFilterInstances(ModelAdmin2):
|
||||||
list_filter = [
|
list_filter = [
|
||||||
django_filters.BooleanFilter(name='published'),
|
django_filters.BooleanFilter(name='published'),
|
||||||
]
|
]
|
||||||
|
|
@ -33,7 +32,7 @@ class ListFilterBuilderTest(TestCase):
|
||||||
fields = ['published']
|
fields = ['published']
|
||||||
|
|
||||||
|
|
||||||
class PostAdminWithFilterSetInst(djadmin2.ModelAdmin2):
|
class PostAdminWithFilterSetInst(ModelAdmin2):
|
||||||
list_filter = FS
|
list_filter = FS
|
||||||
|
|
||||||
Post.objects.create(title="post_1_title", body="body")
|
Post.objects.create(title="post_1_title", body="body")
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import floppyforms
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
import floppyforms
|
|
||||||
|
|
||||||
from djadmin2.forms import floppify_widget, floppify_form, modelform_factory
|
from djadmin2.forms import floppify_widget, floppify_form, modelform_factory
|
||||||
from ..models import Post
|
from ..models import Post
|
||||||
|
|
||||||
|
|
@ -103,7 +102,6 @@ class GetFloppyformWidgetTest(TestCase):
|
||||||
floppyforms.widgets.HiddenInput)
|
floppyforms.widgets.HiddenInput)
|
||||||
|
|
||||||
widget = forms.widgets.HiddenInput()
|
widget = forms.widgets.HiddenInput()
|
||||||
widget.is_hidden = False
|
|
||||||
self.assertExpectWidget(
|
self.assertExpectWidget(
|
||||||
widget,
|
widget,
|
||||||
floppyforms.widgets.HiddenInput,
|
floppyforms.widgets.HiddenInput,
|
||||||
|
|
@ -162,7 +160,7 @@ class GetFloppyformWidgetTest(TestCase):
|
||||||
forms.DateInput(),
|
forms.DateInput(),
|
||||||
floppyforms.DateInput)
|
floppyforms.DateInput)
|
||||||
|
|
||||||
widget = forms.widgets.DateInput(format='DATE_FORMAT')
|
widget = forms.widgets.DateInput(format='%Y-%m-%d')
|
||||||
self.assertExpectWidget(
|
self.assertExpectWidget(
|
||||||
widget,
|
widget,
|
||||||
floppyforms.widgets.DateInput,
|
floppyforms.widgets.DateInput,
|
||||||
|
|
@ -311,13 +309,13 @@ class GetFloppyformWidgetTest(TestCase):
|
||||||
floppyforms.widgets.SplitDateTimeWidget)
|
floppyforms.widgets.SplitDateTimeWidget)
|
||||||
|
|
||||||
widget = forms.widgets.SplitDateTimeWidget(
|
widget = forms.widgets.SplitDateTimeWidget(
|
||||||
date_format='DATE_FORMAT', time_format='TIME_FORMAT')
|
date_format='%Y-%m-%d', time_format='TIME_FORMAT')
|
||||||
new_widget = floppify_widget(widget)
|
new_widget = floppify_widget(widget)
|
||||||
self.assertTrue(isinstance(
|
self.assertTrue(isinstance(
|
||||||
new_widget.widgets[0], floppyforms.widgets.DateInput))
|
new_widget.widgets[0], floppyforms.widgets.DateInput))
|
||||||
self.assertTrue(isinstance(
|
self.assertTrue(isinstance(
|
||||||
new_widget.widgets[1], floppyforms.widgets.TimeInput))
|
new_widget.widgets[1], floppyforms.widgets.TimeInput))
|
||||||
self.assertEqual(new_widget.widgets[0].format, 'DATE_FORMAT')
|
self.assertEqual(new_widget.widgets[0].format, '%Y-%m-%d')
|
||||||
self.assertEqual(new_widget.widgets[1].format, 'TIME_FORMAT')
|
self.assertEqual(new_widget.widgets[1].format, 'TIME_FORMAT')
|
||||||
|
|
||||||
def test_splithiddendatetime_widget(self):
|
def test_splithiddendatetime_widget(self):
|
||||||
|
|
@ -327,13 +325,13 @@ class GetFloppyformWidgetTest(TestCase):
|
||||||
floppyforms.widgets.SplitHiddenDateTimeWidget)
|
floppyforms.widgets.SplitHiddenDateTimeWidget)
|
||||||
|
|
||||||
widget = forms.widgets.SplitHiddenDateTimeWidget(
|
widget = forms.widgets.SplitHiddenDateTimeWidget(
|
||||||
date_format='DATE_FORMAT', time_format='TIME_FORMAT')
|
date_format='%Y-%m-%d', time_format='TIME_FORMAT')
|
||||||
new_widget = floppify_widget(widget)
|
new_widget = floppify_widget(widget)
|
||||||
self.assertTrue(isinstance(
|
self.assertTrue(isinstance(
|
||||||
new_widget.widgets[0], floppyforms.widgets.DateInput))
|
new_widget.widgets[0], floppyforms.widgets.DateInput))
|
||||||
self.assertTrue(isinstance(
|
self.assertTrue(isinstance(
|
||||||
new_widget.widgets[1], floppyforms.widgets.TimeInput))
|
new_widget.widgets[1], floppyforms.widgets.TimeInput))
|
||||||
self.assertEqual(new_widget.widgets[0].format, 'DATE_FORMAT')
|
self.assertEqual(new_widget.widgets[0].format, '%Y-%m-%d')
|
||||||
self.assertEqual(new_widget.widgets[0].is_hidden, True)
|
self.assertEqual(new_widget.widgets[0].is_hidden, True)
|
||||||
self.assertEqual(new_widget.widgets[1].format, 'TIME_FORMAT')
|
self.assertEqual(new_widget.widgets[1].format, 'TIME_FORMAT')
|
||||||
self.assertEqual(new_widget.widgets[1].is_hidden, True)
|
self.assertEqual(new_widget.widgets[1].is_hidden, True)
|
||||||
|
|
@ -489,13 +487,13 @@ class FieldWidgetTest(TestCase):
|
||||||
self.assertTrue(isinstance(widget, floppyforms.widgets.SlugInput))
|
self.assertTrue(isinstance(widget, floppyforms.widgets.SlugInput))
|
||||||
self.assertEqual(widget.input_type, 'text')
|
self.assertEqual(widget.input_type, 'text')
|
||||||
|
|
||||||
def test_ipaddress_field(self):
|
def test_genericipaddress_field(self):
|
||||||
class MyForm(forms.ModelForm):
|
class MyForm(forms.ModelForm):
|
||||||
ipaddress = forms.IPAddressField()
|
ipaddress = forms.GenericIPAddressField()
|
||||||
|
|
||||||
form_class = modelform_factory(model=Post, form=MyForm, exclude=[])
|
form_class = modelform_factory(model=Post, form=MyForm, exclude=[])
|
||||||
widget = form_class().fields['ipaddress'].widget
|
widget = form_class().fields['ipaddress'].widget
|
||||||
self.assertTrue(isinstance(widget, floppyforms.widgets.IPAddressInput))
|
self.assertTrue(isinstance(widget, floppyforms.widgets.TextInput))
|
||||||
self.assertEqual(widget.input_type, 'text')
|
self.assertEqual(widget.input_type, 'text')
|
||||||
|
|
||||||
def test_splitdatetime_field(self):
|
def test_splitdatetime_field(self):
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
from django.db import DEFAULT_DB_ALIAS
|
from django.db import DEFAULT_DB_ALIAS, router
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from djadmin2.utils import NestedObjects
|
from djadmin2.utils import NestedObjects
|
||||||
|
from ..models import Count, Event, EventGuide
|
||||||
from ..models import Count, Event, EventGuide, Guest, Location
|
|
||||||
|
|
||||||
|
|
||||||
class NestedObjectsTests(TestCase):
|
class NestedObjectsTests(TestCase):
|
||||||
|
|
@ -66,7 +65,8 @@ class NestedObjectsTests(TestCase):
|
||||||
Check that the nested collector doesn't query for DO_NOTHING objects.
|
Check that the nested collector doesn't query for DO_NOTHING objects.
|
||||||
"""
|
"""
|
||||||
objs = [Event.objects.create()]
|
objs = [Event.objects.create()]
|
||||||
n = NestedObjects(using=None)
|
using = router.db_for_write(Event._meta.model)
|
||||||
|
n = NestedObjects(using=using)
|
||||||
EventGuide.objects.create(event=objs[0])
|
EventGuide.objects.create(event=objs[0])
|
||||||
with self.assertNumQueries(2):
|
with self.assertNumQueries(2):
|
||||||
# One for Location, one for Guest, and no query for EventGuide
|
# One for Location, one for Guest, and no query for EventGuide
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
|
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.core.urlresolvers import reverse
|
||||||
|
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
|
||||||
|
|
||||||
import djadmin2
|
|
||||||
from djadmin2 import ModelAdmin2
|
|
||||||
from djadmin2.permissions import TemplatePermissionChecker
|
from djadmin2.permissions import TemplatePermissionChecker
|
||||||
|
from djadmin2.site import djadmin2_site
|
||||||
from blog.models import Post
|
from djadmin2.types import ModelAdmin2
|
||||||
|
|
||||||
|
|
||||||
class TemplatePermissionTest(TestCase):
|
class TemplatePermissionTest(TestCase):
|
||||||
|
|
@ -26,7 +26,7 @@ class TemplatePermissionTest(TestCase):
|
||||||
return template.render(context)
|
return template.render(context)
|
||||||
|
|
||||||
def test_permission_wrapper(self):
|
def test_permission_wrapper(self):
|
||||||
model_admin = ModelAdmin2(Post, djadmin2.default)
|
model_admin = ModelAdmin2(Post, djadmin2_site)
|
||||||
request = self.factory.get(reverse('admin2:blog_post_index'))
|
request = self.factory.get(reverse('admin2:blog_post_index'))
|
||||||
request.user = self.user
|
request.user = self.user
|
||||||
permissions = TemplatePermissionChecker(request, model_admin)
|
permissions = TemplatePermissionChecker(request, model_admin)
|
||||||
|
|
@ -48,8 +48,8 @@ class TemplatePermissionTest(TestCase):
|
||||||
codename='add_post')
|
codename='add_post')
|
||||||
self.user.user_permissions.add(post_add_permission)
|
self.user.user_permissions.add(post_add_permission)
|
||||||
# invalidate the users permission cache
|
# invalidate the users permission cache
|
||||||
if hasattr(self.user, '_perm_cache'):
|
self.user = get_object_or_404(User, pk=self.user.id)
|
||||||
del self.user._perm_cache
|
request.user = self.user
|
||||||
|
|
||||||
result = self.render('{{ permissions.has_add_permission }}', context)
|
result = self.render('{{ permissions.has_add_permission }}', context)
|
||||||
self.assertEqual(result, 'True')
|
self.assertEqual(result, 'True')
|
||||||
|
|
@ -61,7 +61,7 @@ class TemplatePermissionTest(TestCase):
|
||||||
codename='add_post')
|
codename='add_post')
|
||||||
self.user.user_permissions.add(post_add_permission)
|
self.user.user_permissions.add(post_add_permission)
|
||||||
|
|
||||||
model_admin = ModelAdmin2(Post, djadmin2.default)
|
model_admin = ModelAdmin2(Post, djadmin2_site)
|
||||||
request = self.factory.get(reverse('admin2:blog_post_index'))
|
request = self.factory.get(reverse('admin2:blog_post_index'))
|
||||||
request.user = self.user
|
request.user = self.user
|
||||||
permissions = TemplatePermissionChecker(request, model_admin)
|
permissions = TemplatePermissionChecker(request, model_admin)
|
||||||
|
|
@ -89,8 +89,8 @@ class TemplatePermissionTest(TestCase):
|
||||||
self.assertEqual(result, '')
|
self.assertEqual(result, '')
|
||||||
|
|
||||||
def test_admin_binding(self):
|
def test_admin_binding(self):
|
||||||
user_admin = djadmin2.default.get_admin_by_name('auth_user')
|
user_admin = djadmin2_site.get_admin_by_name('auth_user')
|
||||||
post_admin = djadmin2.default.get_admin_by_name('blog_post')
|
post_admin = djadmin2_site.get_admin_by_name('blog_post')
|
||||||
request = self.factory.get(reverse('admin2:auth_user_index'))
|
request = self.factory.get(reverse('admin2:auth_user_index'))
|
||||||
request.user = self.user
|
request.user = self.user
|
||||||
permissions = TemplatePermissionChecker(request, user_admin)
|
permissions = TemplatePermissionChecker(request, user_admin)
|
||||||
|
|
@ -121,10 +121,11 @@ class TemplatePermissionTest(TestCase):
|
||||||
content_type__app_label='blog',
|
content_type__app_label='blog',
|
||||||
content_type__model='post',
|
content_type__model='post',
|
||||||
codename='add_post')
|
codename='add_post')
|
||||||
|
|
||||||
self.user.user_permissions.add(post_add_permission)
|
self.user.user_permissions.add(post_add_permission)
|
||||||
# invalidate the users permission cache
|
# invalidate the users permission cache
|
||||||
if hasattr(self.user, '_perm_cache'):
|
self.user = get_object_or_404(User, pk=self.user.id)
|
||||||
del self.user._perm_cache
|
request.user = self.user
|
||||||
|
|
||||||
result = self.render(
|
result = self.render(
|
||||||
'{% load admin2_tags %}'
|
'{% load admin2_tags %}'
|
||||||
|
|
@ -155,8 +156,8 @@ class TemplatePermissionTest(TestCase):
|
||||||
self.assertEqual(result, '')
|
self.assertEqual(result, '')
|
||||||
|
|
||||||
def test_view_binding(self):
|
def test_view_binding(self):
|
||||||
user_admin = djadmin2.default.get_admin_by_name('auth_user')
|
user_admin = djadmin2_site.get_admin_by_name('auth_user')
|
||||||
post_admin = djadmin2.default.get_admin_by_name('blog_post')
|
post_admin = djadmin2_site.get_admin_by_name('blog_post')
|
||||||
request = self.factory.get(reverse('admin2:auth_user_index'))
|
request = self.factory.get(reverse('admin2:auth_user_index'))
|
||||||
request.user = self.user
|
request.user = self.user
|
||||||
permissions = TemplatePermissionChecker(request, user_admin)
|
permissions = TemplatePermissionChecker(request, user_admin)
|
||||||
|
|
@ -203,8 +204,8 @@ class TemplatePermissionTest(TestCase):
|
||||||
self.user.user_permissions.add(user_change_permission)
|
self.user.user_permissions.add(user_change_permission)
|
||||||
|
|
||||||
# invalidate the users permission cache
|
# invalidate the users permission cache
|
||||||
if hasattr(self.user, '_perm_cache'):
|
self.user = get_object_or_404(User, pk=self.user.id)
|
||||||
del self.user._perm_cache
|
request.user = self.user
|
||||||
|
|
||||||
result = self.render(
|
result = self.render(
|
||||||
'{% load admin2_tags %}'
|
'{% load admin2_tags %}'
|
||||||
|
|
@ -235,7 +236,7 @@ class TemplatePermissionTest(TestCase):
|
||||||
self.assertEqual(result, '1True2False34True')
|
self.assertEqual(result, '1True2False34True')
|
||||||
|
|
||||||
def test_object_level_permission(self):
|
def test_object_level_permission(self):
|
||||||
model_admin = ModelAdmin2(Post, djadmin2.default)
|
model_admin = ModelAdmin2(Post, djadmin2_site)
|
||||||
request = self.factory.get(reverse('admin2:blog_post_index'))
|
request = self.factory.get(reverse('admin2:blog_post_index'))
|
||||||
request.user = self.user
|
request.user = self.user
|
||||||
permissions = TemplatePermissionChecker(request, model_admin)
|
permissions = TemplatePermissionChecker(request, model_admin)
|
||||||
|
|
@ -264,8 +265,8 @@ class TemplatePermissionTest(TestCase):
|
||||||
codename='add_post')
|
codename='add_post')
|
||||||
self.user.user_permissions.add(post_add_permission)
|
self.user.user_permissions.add(post_add_permission)
|
||||||
# invalidate the users permission cache
|
# invalidate the users permission cache
|
||||||
if hasattr(self.user, '_perm_cache'):
|
self.user = get_object_or_404(User, pk=self.user.id)
|
||||||
del self.user._perm_cache
|
request.user = self.user
|
||||||
|
|
||||||
# object level permission are not supported by default. So this will
|
# object level permission are not supported by default. So this will
|
||||||
# return ``False``.
|
# return ``False``.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
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
|
||||||
|
|
@ -515,18 +516,19 @@ class TestAuthViews(TestCase):
|
||||||
def test_change_password_for_myself(self):
|
def test_change_password_for_myself(self):
|
||||||
self.client.login(username=self.user.username,
|
self.client.login(username=self.user.username,
|
||||||
password='password')
|
password='password')
|
||||||
|
|
||||||
request = self.client.post(reverse('admin2:password_change',
|
request = self.client.post(reverse('admin2:password_change',
|
||||||
kwargs={'pk': self.user.pk}),
|
kwargs={'pk': self.user.pk}),
|
||||||
{'old_password': 'password',
|
{'old_password': 'password',
|
||||||
'new_password1': 'user',
|
'new_password1': 'new_password',
|
||||||
'new_password2': 'user'})
|
'new_password2': 'new_password'})
|
||||||
self.assertRedirects(request, reverse('admin2:password_change_done'))
|
self.assertRedirects(request, reverse('admin2:password_change_done'))
|
||||||
self.client.logout()
|
self.client.logout()
|
||||||
|
|
||||||
self.assertFalse(self.client.login(username=self.user.username,
|
self.assertFalse(self.client.login(username=self.user.username,
|
||||||
password='password'))
|
password='password'))
|
||||||
self.assertTrue(self.client.login(username=self.user.username,
|
self.assertTrue(self.client.login(username=self.user.username,
|
||||||
password='user'))
|
password='new_password'))
|
||||||
|
|
||||||
def test_change_password(self):
|
def test_change_password(self):
|
||||||
self.client.login(username=self.user.username,
|
self.client.login(username=self.user.username,
|
||||||
|
|
@ -538,8 +540,7 @@ class TestAuthViews(TestCase):
|
||||||
|
|
||||||
request = self.client.post(reverse('admin2:password_change',
|
request = self.client.post(reverse('admin2:password_change',
|
||||||
kwargs={'pk': new_user.pk}),
|
kwargs={'pk': new_user.pk}),
|
||||||
{'old_password': 'new_user',
|
{'password1': 'new_user_password',
|
||||||
'password1': 'new_user_password',
|
|
||||||
'password2': 'new_user_password'})
|
'password2': 'new_user_password'})
|
||||||
self.assertRedirects(request, reverse('admin2:password_change_done'))
|
self.assertRedirects(request, reverse('admin2:password_change_done'))
|
||||||
self.client.logout()
|
self.client.logout()
|
||||||
|
|
|
||||||
BIN
example/db.sqlite3
Normal file
BIN
example/db.sqlite3
Normal file
Binary file not shown.
|
|
@ -1,68 +1,137 @@
|
||||||
# Django settings for example project.
|
"""
|
||||||
|
Django settings for example project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 1.9.6.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/1.9/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/1.9/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||||
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = '*ymubzn8p_s7vrm%jsqvr6$qnea_5mcp(ao0z-yh1q0gro!0g1'
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
TEMPLATE_DEBUG = DEBUG
|
|
||||||
|
|
||||||
ADMINS = (
|
ALLOWED_HOSTS = []
|
||||||
# ('Your Name', 'your_email@example.com'),
|
|
||||||
)
|
|
||||||
|
|
||||||
MANAGERS = ADMINS
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.sites',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
# Uncomment the next line to enable admin documentation:
|
||||||
|
# 'django.contrib.admindocs',
|
||||||
|
'floppyforms',
|
||||||
|
'rest_framework',
|
||||||
|
'crispy_forms',
|
||||||
|
'djadmin2',
|
||||||
|
'djadmin2.tests',
|
||||||
|
'djadmin2.themes.djadmin2theme_default',
|
||||||
|
'blog',
|
||||||
|
'files',
|
||||||
|
'polls'
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE_CLASSES = [
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.middleware.locale.LocaleMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'example.urls'
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [],
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': [
|
||||||
|
'django.template.context_processors.debug',
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = 'example.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/1.9/ref/settings/#databases
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
'NAME': 'example.db',
|
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Hosts/domain names that are valid for this site; required if DEBUG is False
|
|
||||||
# See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts
|
|
||||||
ALLOWED_HOSTS = []
|
|
||||||
|
|
||||||
# Local time zone for this installation. Choices can be found here:
|
# Password validation
|
||||||
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
|
# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators
|
||||||
# although not all choices may be available on all operating systems.
|
|
||||||
# In a Windows environment this must be set to your system time zone.
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
TIME_ZONE = 'America/Chicago'
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/1.9/topics/i18n/
|
||||||
|
|
||||||
# Language code for this installation. All choices can be found here:
|
|
||||||
# http://www.i18nguy.com/unicode/language-identifiers.html
|
|
||||||
LANGUAGE_CODE = 'en-us'
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_L10N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
SITE_ID = 1
|
SITE_ID = 1
|
||||||
|
|
||||||
# If you set this to False, Django will make some optimizations so as not
|
# Static files (CSS, JavaScript, Images)
|
||||||
# to load the internationalization machinery.
|
# https://docs.djangoproject.com/en/1.9/howto/static-files/
|
||||||
USE_I18N = True
|
|
||||||
|
|
||||||
# If you set this to False, Django will not format dates, numbers and
|
|
||||||
# calendars according to the current locale.
|
|
||||||
USE_L10N = True
|
|
||||||
|
|
||||||
# If you set this to False, Django will not use timezone-aware datetimes.
|
|
||||||
USE_TZ = True
|
|
||||||
|
|
||||||
# Absolute filesystem path to the directory that will hold user-uploaded files.
|
|
||||||
# Example: "/var/www/example.com/media/"
|
|
||||||
MEDIA_ROOT = ''
|
|
||||||
|
|
||||||
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
|
|
||||||
# trailing slash.
|
|
||||||
# Examples: "http://example.com/media/", "http://media.example.com/"
|
|
||||||
MEDIA_URL = ''
|
|
||||||
|
|
||||||
# Absolute path to the directory static files should be collected to.
|
|
||||||
# Don't put anything in this directory yourself; store your static files
|
|
||||||
# in apps' "static/" subdirectories and in STATICFILES_DIRS.
|
|
||||||
# Example: "/var/www/example.com/static/"
|
|
||||||
STATIC_ROOT = ''
|
|
||||||
|
|
||||||
# URL prefix for static files.
|
|
||||||
# Example: "http://example.com/static/", "http://static.example.com/"
|
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = '/static/'
|
||||||
|
|
||||||
# Additional locations of static files
|
# Additional locations of static files
|
||||||
|
|
@ -73,94 +142,12 @@ STATICFILES_DIRS = (
|
||||||
os.path.join(BASE_DIR, "static"),
|
os.path.join(BASE_DIR, "static"),
|
||||||
)
|
)
|
||||||
|
|
||||||
# List of finder classes that know how to find static files in
|
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
||||||
# various locations.
|
MEDIA_URL = "/media/"
|
||||||
STATICFILES_FINDERS = (
|
|
||||||
'django.contrib.staticfiles.finders.FileSystemFinder',
|
|
||||||
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
|
|
||||||
# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
|
|
||||||
)
|
|
||||||
|
|
||||||
# Make this unique, and don't share it with anybody.
|
|
||||||
SECRET_KEY = '*ymubzn8p_s7vrm%jsqvr6$qnea_5mcp(ao0z-yh1q0gro!0g1'
|
|
||||||
|
|
||||||
# List of callables that know how to import templates from various sources.
|
|
||||||
TEMPLATE_LOADERS = (
|
|
||||||
'django.template.loaders.filesystem.Loader',
|
|
||||||
'django.template.loaders.app_directories.Loader',
|
|
||||||
# 'django.template.loaders.eggs.Loader',
|
|
||||||
)
|
|
||||||
|
|
||||||
MIDDLEWARE_CLASSES = (
|
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
|
||||||
'django.middleware.locale.LocaleMiddleware',
|
|
||||||
'django.middleware.common.CommonMiddleware',
|
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
|
||||||
# Uncomment the next line for simple clickjacking protection:
|
|
||||||
# 'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
|
||||||
)
|
|
||||||
|
|
||||||
ROOT_URLCONF = 'example.urls'
|
|
||||||
|
|
||||||
# Python dotted path to the WSGI application used by Django's runserver.
|
|
||||||
WSGI_APPLICATION = 'example.wsgi.application'
|
|
||||||
|
|
||||||
TEMPLATE_DIRS = (
|
|
||||||
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
|
|
||||||
# Always use forward slashes, even on Windows.
|
|
||||||
# Don't forget to use absolute paths, not relative paths.
|
|
||||||
)
|
|
||||||
|
|
||||||
INSTALLED_APPS = (
|
|
||||||
'django.contrib.auth',
|
|
||||||
'django.contrib.contenttypes',
|
|
||||||
'django.contrib.sessions',
|
|
||||||
'django.contrib.sites',
|
|
||||||
'django.contrib.messages',
|
|
||||||
'django.contrib.staticfiles',
|
|
||||||
'django.contrib.admin',
|
|
||||||
# Uncomment the next line to enable admin documentation:
|
|
||||||
# 'django.contrib.admindocs',
|
|
||||||
'floppyforms',
|
|
||||||
'rest_framework',
|
|
||||||
'crispy_forms',
|
|
||||||
'djadmin2',
|
|
||||||
'djadmin2.themes.djadmin2theme_default',
|
|
||||||
'blog',
|
|
||||||
'files',
|
|
||||||
'polls'
|
|
||||||
)
|
|
||||||
|
|
||||||
# A sample logging configuration. The only tangible logging
|
|
||||||
# performed by this configuration is to send an email to
|
|
||||||
# the site admins on every HTTP 500 error when DEBUG=False.
|
|
||||||
# See http://docs.djangoproject.com/en/dev/topics/logging for
|
|
||||||
# more details on how to customize your logging configuration.
|
|
||||||
LOGGING = {
|
|
||||||
'version': 1,
|
|
||||||
'disable_existing_loggers': False,
|
|
||||||
'filters': {
|
|
||||||
'require_debug_false': {
|
|
||||||
'()': 'django.utils.log.RequireDebugFalse'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'handlers': {
|
|
||||||
'mail_admins': {
|
|
||||||
'level': 'ERROR',
|
|
||||||
'filters': ['require_debug_false'],
|
|
||||||
'class': 'django.utils.log.AdminEmailHandler'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'loggers': {
|
|
||||||
'django.request': {
|
|
||||||
'handlers': ['mail_admins'],
|
|
||||||
'level': 'ERROR',
|
|
||||||
'propagate': True,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
ADMIN2_THEME_DIRECTORY = "djadmin2theme_default"
|
ADMIN2_THEME_DIRECTORY = "djadmin2theme_default"
|
||||||
|
|
||||||
|
REST_FRAMEWORK = {
|
||||||
|
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
|
||||||
|
'PAGE_SIZE': 10
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,21 @@
|
||||||
from django.conf.urls import patterns, include, url
|
from __future__ import unicode_literals
|
||||||
from django.contrib import admin
|
|
||||||
|
|
||||||
from blog.views import BlogListView, BlogDetailView
|
from blog.views import BlogListView, BlogDetailView
|
||||||
|
from django.conf import settings
|
||||||
|
from django.conf.urls import include, url
|
||||||
|
from django.conf.urls.static import static
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from djadmin2.site import djadmin2_site
|
||||||
|
|
||||||
admin.autodiscover()
|
admin.autodiscover()
|
||||||
|
djadmin2_site.autodiscover()
|
||||||
|
|
||||||
import djadmin2
|
urlpatterns = [
|
||||||
|
url(r'^admin2/', include(djadmin2_site.urls)),
|
||||||
djadmin2.default.autodiscover()
|
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
|
||||||
url(r'^admin2/', include(djadmin2.default.urls)),
|
|
||||||
url(r'^admin/', include(admin.site.urls)),
|
url(r'^admin/', include(admin.site.urls)),
|
||||||
url(r'^blog/', BlogListView.as_view(template_name="blog/blog_list.html"), name='blog_list'),
|
url(r'^blog/', BlogListView.as_view(template_name="blog/blog_list.html"), name='blog_list'),
|
||||||
url(r'^blog/detail(?P<pk>\d+)/$', BlogDetailView.as_view(template_name="blog/blog_detail.html"), name='blog_detail'),
|
url(r'^blog/detail(?P<pk>\d+)/$', BlogDetailView.as_view(template_name="blog/blog_detail.html"), name='blog_detail'),
|
||||||
url(r'^$', BlogListView.as_view(template_name="blog/home.html"), name='home'),
|
url(r'^$', BlogListView.as_view(template_name="blog/home.html"), name='home'),
|
||||||
)
|
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,16 @@
|
||||||
"""
|
"""
|
||||||
WSGI config for example project.
|
WSGI config for example project.
|
||||||
|
|
||||||
This module contains the WSGI application used by Django's development server
|
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||||
and any production WSGI deployments. It should expose a module-level variable
|
|
||||||
named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
|
|
||||||
this application via the ``WSGI_APPLICATION`` setting.
|
|
||||||
|
|
||||||
Usually you will have the standard Django WSGI application here, but it also
|
|
||||||
might make sense to replace the whole Django WSGI application with a custom one
|
|
||||||
that later delegates to the Django one. For example, you could introduce WSGI
|
|
||||||
middleware here, or combine a Django application with an application of another
|
|
||||||
framework.
|
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/1.9/howto/deployment/wsgi/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
# We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks
|
from django.core.wsgi import get_wsgi_application
|
||||||
# if running multiple sites in the same mod_wsgi process. To fix this, use
|
|
||||||
# mod_wsgi daemon mode with each site in its own daemon process, or use
|
|
||||||
# os.environ["DJANGO_SETTINGS_MODULE"] = "example.settings"
|
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings")
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings")
|
||||||
|
|
||||||
# This application object is used by any WSGI server configured to use this
|
|
||||||
# file. This includes Django's development server, if the WSGI_APPLICATION
|
|
||||||
# setting points here.
|
|
||||||
from django.core.wsgi import get_wsgi_application
|
|
||||||
application = get_wsgi_application()
|
application = get_wsgi_application()
|
||||||
|
|
||||||
# Apply WSGI middleware here.
|
|
||||||
# from helloworld.wsgi import HelloWorldApplication
|
|
||||||
# application = HelloWorldApplication(application)
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
from __future__ import division, absolute_import, unicode_literals
|
||||||
|
|
||||||
import djadmin2
|
from djadmin2.site import djadmin2_site
|
||||||
|
|
||||||
from .models import CaptionedFile, UncaptionedFile
|
from .models import CaptionedFile, UncaptionedFile
|
||||||
|
|
||||||
|
|
||||||
djadmin2.default.register(CaptionedFile)
|
djadmin2_site.register(CaptionedFile)
|
||||||
djadmin2.default.register(UncaptionedFile)
|
djadmin2_site.register(UncaptionedFile)
|
||||||
|
|
|
||||||
36
example/files/migrations/0001_initial.py
Normal file
36
example/files/migrations/0001_initial.py
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='CaptionedFile',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
|
||||||
|
('caption', models.CharField(max_length=200, verbose_name='caption')),
|
||||||
|
('publication', models.FileField(verbose_name='Uploaded File', upload_to='captioned-files')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Captioned File',
|
||||||
|
'verbose_name_plural': 'Captioned Files',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='UncaptionedFile',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
|
||||||
|
('publication', models.FileField(verbose_name='Uploaded File', upload_to='uncaptioned-files')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Uncaptioned File',
|
||||||
|
'verbose_name_plural': 'Uncaptioned Files',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
0
example/files/migrations/__init__.py
Normal file
0
example/files/migrations/__init__.py
Normal file
|
|
@ -9,8 +9,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
@python_2_unicode_compatible
|
@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(
|
publication = models.FileField(upload_to='captioned-files', verbose_name=_('Uploaded File'))
|
||||||
upload_to='media', verbose_name=_('Uploaded File'))
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.caption
|
return self.caption
|
||||||
|
|
@ -22,11 +21,10 @@ class CaptionedFile(models.Model):
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class UncaptionedFile(models.Model):
|
class UncaptionedFile(models.Model):
|
||||||
publication = models.FileField(
|
publication = models.FileField(upload_to='uncaptioned-files', verbose_name=_('Uploaded File'))
|
||||||
upload_to='media', verbose_name=_('Uploaded File'))
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.publication
|
return self.publication.name
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Uncaptioned File')
|
verbose_name = _('Uncaptioned File')
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,8 @@
|
||||||
from django.test import TestCase
|
|
||||||
from django.utils import timezone
|
|
||||||
|
|
||||||
from files.models import CaptionedFile
|
|
||||||
from files.models import UncaptionedFile
|
|
||||||
|
|
||||||
|
|
||||||
from os import path
|
from os import path
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
from files.models import CaptionedFile
|
||||||
|
|
||||||
|
|
||||||
fixture_dir = path.join(path.abspath(path.dirname(__file__)), 'fixtures')
|
fixture_dir = path.join(path.abspath(path.dirname(__file__)), 'fixtures')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
|
from os import path
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.test import TestCase, Client
|
from django.test import TestCase, Client
|
||||||
from django.utils import timezone
|
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
|
|
||||||
from ..models import CaptionedFile
|
from ..models import CaptionedFile
|
||||||
|
|
||||||
from os import path
|
|
||||||
|
|
||||||
fixture_dir = path.join(path.abspath(path.dirname(__file__)), 'fixtures')
|
fixture_dir = path.join(path.abspath(path.dirname(__file__)), 'fixtures')
|
||||||
fixture_file = path.join(fixture_dir, 'pubtest.txt')
|
fixture_file = path.join(fixture_dir, 'pubtest.txt')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import division, absolute_import, unicode_literals
|
from __future__ import division, absolute_import, unicode_literals
|
||||||
|
|
||||||
import djadmin2
|
from djadmin2.site import djadmin2_site
|
||||||
|
from djadmin2.types import Admin2TabularInline, ModelAdmin2
|
||||||
from .models import Poll, Choice
|
from .models import Poll, Choice
|
||||||
|
|
||||||
|
|
||||||
class ChoiceInline(djadmin2.Admin2TabularInline):
|
class ChoiceInline(Admin2TabularInline):
|
||||||
model = Choice
|
model = Choice
|
||||||
extra = 3
|
extra = 3
|
||||||
|
|
||||||
|
|
||||||
class PollAdmin(djadmin2.ModelAdmin2):
|
class PollAdmin(ModelAdmin2):
|
||||||
fieldsets = [
|
fieldsets = [
|
||||||
(None, {'fields': ['question']}),
|
(None, {'fields': ['question']}),
|
||||||
('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),
|
('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),
|
||||||
|
|
@ -23,4 +23,4 @@ class PollAdmin(djadmin2.ModelAdmin2):
|
||||||
date_hierarchy = 'pub_date'
|
date_hierarchy = 'pub_date'
|
||||||
|
|
||||||
|
|
||||||
djadmin2.default.register(Poll, PollAdmin)
|
djadmin2_site.register(Poll, PollAdmin)
|
||||||
|
|
|
||||||
42
example/polls/migrations/0001_initial.py
Normal file
42
example/polls/migrations/0001_initial.py
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Choice',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
|
||||||
|
('choice_text', models.CharField(max_length=200, verbose_name='choice text')),
|
||||||
|
('votes', models.IntegerField(verbose_name='votes', default=0)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'choice',
|
||||||
|
'verbose_name_plural': 'choices',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Poll',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
|
||||||
|
('question', models.CharField(max_length=200, verbose_name='question')),
|
||||||
|
('pub_date', models.DateTimeField(verbose_name='date published')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'poll',
|
||||||
|
'verbose_name_plural': 'polls',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='choice',
|
||||||
|
name='poll',
|
||||||
|
field=models.ForeignKey(verbose_name='poll', to='polls.Poll'),
|
||||||
|
),
|
||||||
|
]
|
||||||
0
example/polls/migrations/__init__.py
Normal file
0
example/polls/migrations/__init__.py
Normal file
|
|
@ -3,9 +3,9 @@ from __future__ import division, absolute_import, unicode_literals
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from django.utils.encoding import python_2_unicode_compatible
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from polls.models import Poll
|
|
||||||
from polls.models import Choice
|
from polls.models import Choice
|
||||||
|
from polls.models import Poll
|
||||||
|
|
||||||
|
|
||||||
class PollTestCase(TestCase):
|
class PollTestCase(TestCase):
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
django-extra-views>=0.6.5
|
django-extra-views>=0.6.5
|
||||||
django-braces>=1.3.0
|
django-braces>=1.3.0
|
||||||
djangorestframework<=2.4.4
|
djangorestframework>=3.3.3
|
||||||
django-floppyforms<=1.2
|
django-floppyforms>=1.6.2
|
||||||
django-filter<0.12.0
|
django-filter>=0.13.0
|
||||||
django-crispy-forms>=1.3.2
|
django-crispy-forms>=1.3.2
|
||||||
django-debug-toolbar>=0.9.4
|
django-debug-toolbar>=0.9.4
|
||||||
pytz==2014.7
|
future>=0.15.2
|
||||||
|
pytz==2016.4
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
-rrequirements.txt
|
-rrequirements.txt
|
||||||
|
flake8==2.5.4
|
||||||
pytest
|
pytest
|
||||||
pytest-django
|
pytest-django
|
||||||
11
setup.py
11
setup.py
|
|
@ -127,14 +127,15 @@ setup(
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
#test_suite='runtests.runtests',
|
#test_suite='runtests.runtests',
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'django>=1.6.0',
|
'django>=1.8.0',
|
||||||
'django-extra-views>=0.6.5',
|
'django-extra-views>=0.6.5',
|
||||||
'django-braces>=1.3.0',
|
'django-braces>=1.3.0',
|
||||||
'djangorestframework<=2.4.4',
|
'djangorestframework>=3.3.3',
|
||||||
'django-floppyforms<=1.2',
|
'django-floppyforms>=1.6.2',
|
||||||
'django-filter<0.12.0',
|
'django-filter>=0.13.0',
|
||||||
'django-crispy-forms>=1.3.2',
|
'django-crispy-forms>=1.3.2',
|
||||||
'pytz==2014.7'
|
'pytz==2014.7',
|
||||||
|
'future>=0.15.2',
|
||||||
],
|
],
|
||||||
extras_require={
|
extras_require={
|
||||||
'testing': ['pytest', 'pytest-django', 'pytest-ipdb'],
|
'testing': ['pytest', 'pytest-django', 'pytest-ipdb'],
|
||||||
|
|
|
||||||
151
tox.ini
151
tox.ini
|
|
@ -1,138 +1,27 @@
|
||||||
|
[flake8]
|
||||||
|
ignore = E265,E501
|
||||||
|
max-line-length = 100
|
||||||
|
max-complexity = 10
|
||||||
|
exclude = migrations/*,docs/*
|
||||||
|
|
||||||
[tox]
|
[tox]
|
||||||
envlist = py27-dj1.6.x, py33-dj1.6.x, py34-dj1.6.x, pypy-dj1.6.x,
|
envlist =
|
||||||
pypy3-dj1.6.x,
|
py27-{1.8,1.9,master},
|
||||||
py27-dj1.7.x, py33-dj1.7.x, py34-dj1.7.x, pypy-dj1.7.x,
|
py33-{1.8},
|
||||||
pypy3-dj1.7.x,
|
py34-{1.8,1.9,master},
|
||||||
py27-dj1.8.x, py33-dj1.8.x, py34-dj1.8.x, pypy-dj1.8.x,
|
py35-{1.8,1.9,master},
|
||||||
pypy3-dj1.8.x,
|
|
||||||
py27-dj1.9.x, py33-dj1.9.x, py34-dj1.9.x, pypy-dj1.9.x,
|
|
||||||
pypy3-dj1.9.x,
|
|
||||||
skipsdist = True
|
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
commands = py.test []
|
commands =
|
||||||
deps = -rrequirements_test.txt
|
flake8 djadmin2
|
||||||
|
py.test []
|
||||||
|
deps =
|
||||||
|
-rrequirements_test.txt
|
||||||
|
1.8: Django>=1.8,<1.9
|
||||||
|
1.9: Django>=1.9,<1.10
|
||||||
|
master: https://github.com/django/django/tarball/master
|
||||||
|
usedevelop = True
|
||||||
setenv=
|
setenv=
|
||||||
DJANGO_SETTINGS_MODULE = example.settings
|
DJANGO_SETTINGS_MODULE = example.settings
|
||||||
PYTHONPATH = {toxinidir}/example:{toxinidir}
|
PYTHONPATH = {toxinidir}/example:{toxinidir}
|
||||||
|
|
||||||
[testenv:py27-dj1.6.x]
|
|
||||||
basepython=python2.7
|
|
||||||
deps =
|
|
||||||
Django>=1.6,<1.7
|
|
||||||
coverage
|
|
||||||
{[testenv]deps}
|
|
||||||
|
|
||||||
[testenv:py27-dj1.7.x]
|
|
||||||
basepython=python2.7
|
|
||||||
deps =
|
|
||||||
Django>=1.7,<1.8
|
|
||||||
{[testenv]deps}
|
|
||||||
|
|
||||||
[testenv:py33-dj1.6.x]
|
|
||||||
basepython=python3.3
|
|
||||||
deps =
|
|
||||||
Django>=1.6,<1.7
|
|
||||||
{[testenv]deps}
|
|
||||||
|
|
||||||
[testenv:py34-dj1.6.x]
|
|
||||||
basepython=python3.4
|
|
||||||
deps =
|
|
||||||
Django>=1.6,<1.7
|
|
||||||
{[testenv]deps}
|
|
||||||
|
|
||||||
[testenv:py33-dj1.7.x]
|
|
||||||
basepython=python3.3
|
|
||||||
deps =
|
|
||||||
Django>=1.7,<1.8
|
|
||||||
{[testenv]deps}
|
|
||||||
|
|
||||||
[testenv:py34-dj1.7.x]
|
|
||||||
basepython=python3.4
|
|
||||||
deps =
|
|
||||||
Django>=1.7,<1.8
|
|
||||||
{[testenv]deps}
|
|
||||||
|
|
||||||
[testenv:pypy-dj1.6.x]
|
|
||||||
basepython=pypy
|
|
||||||
deps =
|
|
||||||
Django>=1.6,<1.7
|
|
||||||
{[testenv]deps}
|
|
||||||
|
|
||||||
[testenv:pypy3-dj1.6.x]
|
|
||||||
basepython=pypy3
|
|
||||||
deps =
|
|
||||||
Django>=1.6,<1.7
|
|
||||||
{[testenv]deps}
|
|
||||||
|
|
||||||
[testenv:pypy-dj1.7.x]
|
|
||||||
basepython=pypy
|
|
||||||
deps =
|
|
||||||
Django>=1.7,<1.8
|
|
||||||
{[testenv]deps}
|
|
||||||
|
|
||||||
[testenv:pypy3-dj1.7.x]
|
|
||||||
basepython=pypy3
|
|
||||||
deps =
|
|
||||||
Django>=1.7,<1.8
|
|
||||||
{[testenv]deps}
|
|
||||||
|
|
||||||
[testenv:py27-dj1.8.x]
|
|
||||||
basepython=python2.7
|
|
||||||
deps =
|
|
||||||
Django>=1.8,<1.9
|
|
||||||
{[testenv]deps}
|
|
||||||
|
|
||||||
[testenv:py34-dj1.8.x]
|
|
||||||
basepython=python3.4
|
|
||||||
deps =
|
|
||||||
Django>=1.8,<1.9
|
|
||||||
{[testenv]deps}
|
|
||||||
|
|
||||||
[testenv:py33-dj1.8.x]
|
|
||||||
basepython=python2.7
|
|
||||||
deps =
|
|
||||||
Django>=1.8,<1.9
|
|
||||||
{[testenv]deps}
|
|
||||||
|
|
||||||
[testenv:pypy-dj1.8.x]
|
|
||||||
basepython=pypy
|
|
||||||
deps =
|
|
||||||
Django>=1.8,<1.9
|
|
||||||
{[testenv]deps}
|
|
||||||
|
|
||||||
[testenv:pypy3-dj1.8.x]
|
|
||||||
basepython=pypy3
|
|
||||||
deps =
|
|
||||||
Django>=1.8,<1.9
|
|
||||||
{[testenv]deps}
|
|
||||||
|
|
||||||
[testenv:py27-dj1.9.x]
|
|
||||||
basepython=python2.7
|
|
||||||
deps =
|
|
||||||
Django>=1.9,<1.9.999
|
|
||||||
{[testenv]deps}
|
|
||||||
|
|
||||||
[testenv:py34-dj1.9.x]
|
|
||||||
basepython=python3.4
|
|
||||||
deps =
|
|
||||||
Django>=1.9,<1.9.999
|
|
||||||
{[testenv]deps}
|
|
||||||
|
|
||||||
[testenv:py33-dj1.9.x]
|
|
||||||
basepython=python2.7
|
|
||||||
deps =
|
|
||||||
Django>=1.9,<1.9.999
|
|
||||||
{[testenv]deps}
|
|
||||||
|
|
||||||
[testenv:pypy-dj1.9.x]
|
|
||||||
basepython=pypy
|
|
||||||
deps =
|
|
||||||
Django>=1.9,<1.9.999
|
|
||||||
{[testenv]deps}
|
|
||||||
|
|
||||||
[testenv:pypy3-dj1.9.x]
|
|
||||||
basepython=pypy3
|
|
||||||
deps =
|
|
||||||
Django>=1.9,<1.9.999
|
|
||||||
{[testenv]deps}
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue