mirror of
https://github.com/jazzband/django-admin2.git
synced 2026-03-17 06:30:25 +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
|
||||
media
|
||||
|
||||
# PyCharm
|
||||
.idea/
|
||||
|
||||
.cache
|
||||
63
.travis.yml
63
.travis.yml
|
|
@ -1,47 +1,28 @@
|
|||
sudo: false
|
||||
language: python
|
||||
python: "2.7"
|
||||
python:
|
||||
- "2.7"
|
||||
- "3.3"
|
||||
- "3.4"
|
||||
- "3.5"
|
||||
env:
|
||||
matrix:
|
||||
- TOX_ENV=py27-dj1.6.x
|
||||
- TOX_ENV=py27-dj1.7.x
|
||||
- TOX_ENV=py27-dj1.8.x
|
||||
- TOX_ENV=py27-dj1.9.x
|
||||
- TOX_ENV=py33-dj1.6.x
|
||||
- TOX_ENV=py33-dj1.7.x
|
||||
- TOX_ENV=py33-dj1.8.x
|
||||
- TOX_ENV=py33-dj1.9.x
|
||||
- TOX_ENV=py34-dj1.6.x
|
||||
- TOX_ENV=py34-dj1.7.x
|
||||
- TOX_ENV=py34-dj1.8.x
|
||||
- TOX_ENV=py34-dj1.9.x
|
||||
- TOX_ENV=pypy-dj1.6.x
|
||||
- TOX_ENV=pypy-dj1.7.x
|
||||
- TOX_ENV=pypy-dj1.8.x
|
||||
- TOX_ENV=pypy-dj1.9.x
|
||||
- TOX_ENV=pypy3-dj1.6.x
|
||||
- TOX_ENV=pypy3-dj1.8.x
|
||||
- TOX_ENV=pypy3-dj1.9.x
|
||||
- DJANGO=1.8
|
||||
- DJANGO=1.9
|
||||
- DJANGO=master
|
||||
matrix:
|
||||
exclude:
|
||||
- python: "3.3"
|
||||
env: DJANGO=1.9
|
||||
- python: "3.3"
|
||||
env: DJANGO=master
|
||||
allow_failures:
|
||||
- python: "2.7"
|
||||
env: DJANGO=master
|
||||
- python: "3.4"
|
||||
env: DJANGO=master
|
||||
- python: "3.5"
|
||||
env: DJANGO=master
|
||||
install:
|
||||
- pip install tox
|
||||
script:
|
||||
- tox -e $TOX_ENV
|
||||
# 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
|
||||
- tox -e py${TRAVIS_PYTHON_VERSION//[.]/}-$DJANGO
|
||||
|
|
@ -52,6 +52,7 @@ Developers
|
|||
* marangonico
|
||||
* Kamil Gałuszka (@galuszkak / galuszkak@gmail.com)
|
||||
* Germano Gabbianelli (@tyrion)
|
||||
* Arthur (@arthur-wsw / arthur@wallstreetweb.net)
|
||||
|
||||
Translators
|
||||
-----------
|
||||
|
|
|
|||
14
HISTORY.rst
14
HISTORY.rst
|
|
@ -1,6 +1,20 @@
|
|||
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)
|
||||
|
||||
* Fix empty form display
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import division, absolute_import, unicode_literals
|
||||
|
||||
|
||||
__version__ = '0.6.1'
|
||||
|
||||
__author__ = 'Daniel Greenfeld & Contributors'
|
||||
|
|
@ -10,17 +11,4 @@ VERSION = __version__ # synonym
|
|||
# Default datetime input and output formats
|
||||
ISO_8601 = 'iso-8601'
|
||||
|
||||
from . import core
|
||||
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
|
||||
default_app_config = "djadmin2.apps.Djadmin2Config"
|
||||
|
|
|
|||
|
|
@ -2,14 +2,15 @@
|
|||
from __future__ import division, absolute_import, unicode_literals
|
||||
|
||||
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.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_lazy, ungettext, pgettext_lazy
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
from . import permissions, utils
|
||||
from .viewmixins import AdminModel2Mixin
|
||||
from .viewmixins import Admin2ModelMixin
|
||||
|
||||
|
||||
def get_description(action):
|
||||
|
|
@ -21,7 +22,7 @@ def get_description(action):
|
|||
return capfirst(action.__name__.replace('_', ' '))
|
||||
|
||||
|
||||
class BaseListAction(AdminModel2Mixin, TemplateView):
|
||||
class BaseListAction(Admin2ModelMixin, TemplateView):
|
||||
|
||||
permission_classes = (permissions.IsStaffPermission,)
|
||||
|
||||
|
|
@ -54,7 +55,7 @@ class BaseListAction(AdminModel2Mixin, TemplateView):
|
|||
super(BaseListAction, self).__init__(*args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
""" Replaced `get_queryset` from `AdminModel2Mixin`"""
|
||||
""" Replaced `get_queryset` from `Admin2ModelMixin`"""
|
||||
return self.queryset
|
||||
|
||||
def description(self):
|
||||
|
|
@ -94,7 +95,9 @@ class BaseListAction(AdminModel2Mixin, TemplateView):
|
|||
return '%s: %s' % (force_text(capfirst(opts.verbose_name)),
|
||||
force_text(obj))
|
||||
|
||||
collector = utils.NestedObjects(using=None)
|
||||
using = router.db_for_write(self.model)
|
||||
|
||||
collector = utils.NestedObjects(using=using)
|
||||
collector.collect(self.queryset)
|
||||
|
||||
context.update({
|
||||
|
|
@ -166,7 +169,6 @@ class DeleteSelectedAction(BaseListAction):
|
|||
# objects, so render a template asking for their confirmation.
|
||||
return self.get(request)
|
||||
|
||||
|
||||
def process_queryset(self):
|
||||
# The user has confirmed that they want to delete the objects.
|
||||
self.get_queryset().delete()
|
||||
|
|
|
|||
|
|
@ -4,34 +4,34 @@ from __future__ import division, absolute_import, unicode_literals
|
|||
from django.conf import settings
|
||||
from django.contrib.auth.models import Group, User
|
||||
from django.contrib.sites.models import Site
|
||||
|
||||
from rest_framework.relations import PrimaryKeyRelatedField
|
||||
|
||||
import djadmin2
|
||||
from djadmin2.forms import UserCreationForm, UserChangeForm
|
||||
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):
|
||||
permissions = PrimaryKeyRelatedField(many=True)
|
||||
permissions = PrimaryKeyRelatedField(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Group
|
||||
|
||||
|
||||
class GroupAdmin2(djadmin2.ModelAdmin2):
|
||||
class GroupAdmin2(ModelAdmin2):
|
||||
api_serializer_class = GroupSerializer
|
||||
|
||||
|
||||
class UserSerializer(Admin2APISerializer):
|
||||
user_permissions = PrimaryKeyRelatedField(many=True)
|
||||
user_permissions = PrimaryKeyRelatedField(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
exclude = ('passwords',)
|
||||
exclude = ('password',)
|
||||
|
||||
|
||||
class UserAdmin2(djadmin2.ModelAdmin2):
|
||||
class UserAdmin2(ModelAdmin2):
|
||||
create_form_class = UserCreationForm
|
||||
update_form_class = UserChangeForm
|
||||
search_fields = ('username', 'groups__name', 'first_name', 'last_name',
|
||||
|
|
@ -43,15 +43,15 @@ class UserAdmin2(djadmin2.ModelAdmin2):
|
|||
|
||||
|
||||
# Register each model with the admin
|
||||
djadmin2.default.register(User, UserAdmin2)
|
||||
djadmin2.default.register(Group, GroupAdmin2)
|
||||
djadmin2_site.register(User, UserAdmin2)
|
||||
djadmin2_site.register(Group, GroupAdmin2)
|
||||
|
||||
|
||||
# Register the sites app if it's been activated in INSTALLED_APPS
|
||||
if "django.contrib.sites" in settings.INSTALLED_APPS:
|
||||
|
||||
class SiteAdmin2(djadmin2.ModelAdmin2):
|
||||
class SiteAdmin2(ModelAdmin2):
|
||||
list_display = ('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 django.utils.encoding import force_str
|
||||
|
||||
from rest_framework import fields, generics, serializers
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.reverse import reverse
|
||||
|
|
@ -17,11 +16,30 @@ API_VERSION = '0.1'
|
|||
class Admin2APISerializer(serializers.HyperlinkedModelSerializer):
|
||||
_default_view_name = 'admin2:%(app_label)s_%(model_name)s_api_detail'
|
||||
|
||||
pk = fields.Field(source='pk')
|
||||
__unicode__ = fields.Field(source='__str__')
|
||||
pk = fields.ReadOnlyField()
|
||||
__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):
|
||||
model = None
|
||||
raise_exception = True
|
||||
|
||||
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 django.conf.urls import patterns, include, url
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
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 types
|
||||
|
|
@ -160,8 +159,7 @@ class Admin2(object):
|
|||
}
|
||||
|
||||
def get_urls(self):
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
urlpatterns = [
|
||||
url(regex=r'^$',
|
||||
view=self.index_view.as_view(**self.get_index_kwargs()),
|
||||
name='dashboard'
|
||||
|
|
@ -188,11 +186,10 @@ class Admin2(object):
|
|||
**self.get_api_index_kwargs()),
|
||||
name='api_index'
|
||||
),
|
||||
)
|
||||
]
|
||||
for model, model_admin in self.registry.items():
|
||||
model_options = utils.model_options(model)
|
||||
urlpatterns += patterns(
|
||||
'',
|
||||
urlpatterns += [
|
||||
url('^{}/{}/'.format(
|
||||
model_options.app_label,
|
||||
model_options.object_name.lower()),
|
||||
|
|
@ -201,11 +198,10 @@ class Admin2(object):
|
|||
model_options.app_label,
|
||||
model_options.object_name.lower()),
|
||||
include(model_admin.api_urls)),
|
||||
)
|
||||
]
|
||||
return urlpatterns
|
||||
|
||||
@property
|
||||
def urls(self):
|
||||
# We set the application and instance namespace here
|
||||
return self.get_urls(), self.name, self.name
|
||||
|
||||
|
|
|
|||
|
|
@ -2,19 +2,17 @@
|
|||
from __future__ import division, absolute_import, unicode_literals
|
||||
|
||||
import collections
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
|
|
@ -97,21 +95,21 @@ def build_list_filter(request, model_admin, queryset):
|
|||
'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 = {
|
||||
"year": NumericDateFilter(
|
||||
name="published_date",
|
||||
name=field_name,
|
||||
lookup_type="year",
|
||||
),
|
||||
"month": NumericDateFilter(
|
||||
name="published_date",
|
||||
name=field_name,
|
||||
lookup_type="month",
|
||||
),
|
||||
"day": NumericDateFilter(
|
||||
name="published_date",
|
||||
name=field_name,
|
||||
lookup_type="day",
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,16 +3,16 @@ from __future__ import division, absolute_import, unicode_literals
|
|||
|
||||
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.forms import AuthenticationForm
|
||||
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
|
||||
import django
|
||||
import django.forms
|
||||
import django.forms.models
|
||||
import django.forms.extras.widgets
|
||||
from django.utils.translation import ugettext_lazy
|
||||
|
||||
import floppyforms
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
_WIDGET_COMMON_ATTRIBUTES = (
|
||||
|
|
@ -166,9 +166,7 @@ _django_to_floppyforms_widget = {
|
|||
django.forms.extras.widgets.SelectDateWidget:
|
||||
_create_widget(
|
||||
floppyforms.widgets.SelectDateWidget,
|
||||
init_arguments=
|
||||
('years',)
|
||||
if django.VERSION >= (1, 7) else ('years', 'required')),
|
||||
init_arguments=('years',) if django.VERSION >= (1, 7) else ('years', 'required')),
|
||||
}
|
||||
|
||||
_django_field_to_floppyform_widget = {
|
||||
|
|
@ -184,8 +182,8 @@ _django_field_to_floppyform_widget = {
|
|||
_create_widget(floppyforms.widgets.URLInput),
|
||||
django.forms.fields.SlugField:
|
||||
_create_widget(floppyforms.widgets.SlugInput),
|
||||
django.forms.fields.IPAddressField:
|
||||
_create_widget(floppyforms.widgets.IPAddressInput),
|
||||
django.forms.fields.GenericIPAddressField:
|
||||
_create_widget(floppyforms.widgets.TextInput),
|
||||
django.forms.fields.SplitDateTimeField:
|
||||
_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
|
||||
# turned off. That applies for Django 1.6 upwards.
|
||||
# See the relevant source code in django:
|
||||
# https://github.com/django/django/blob/1.6/django/forms/fields.py#L225
|
||||
if django.VERSION >= (1, 6):
|
||||
if isinstance(field, django.forms.IntegerField) and not field.localize:
|
||||
if field.widget.__class__ is django.forms.NumberInput:
|
||||
return True
|
||||
# https://github.com/django/django/blob/1.9.6/django/forms/fields.py#L261-264
|
||||
if isinstance(field, django.forms.IntegerField) and not field.localize:
|
||||
if field.widget.__class__ is django.forms.NumberInput:
|
||||
return True
|
||||
|
||||
# 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
|
||||
|
|
@ -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
|
||||
# (default : username, but could be email, or something else)
|
||||
ERROR_MESSAGE = ugettext_lazy("Please enter the correct %(username)s and password "
|
||||
"for a staff account. Note that both fields may be case-sensitive.")
|
||||
ERROR_MESSAGE = _(
|
||||
"Please enter the correct %(username)s and password "
|
||||
"for a staff account. Note that both fields may be case-sensitive."
|
||||
)
|
||||
|
||||
|
||||
class AdminAuthenticationForm(AuthenticationForm):
|
||||
|
|
@ -283,10 +282,13 @@ class AdminAuthenticationForm(AuthenticationForm):
|
|||
|
||||
"""
|
||||
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,
|
||||
initial=1, error_messages=error_messages)
|
||||
this_is_the_login_form = django.forms.BooleanField(
|
||||
widget=floppyforms.HiddenInput,
|
||||
initial=1,
|
||||
error_messages=error_messages
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
username = self.cleaned_data.get('username')
|
||||
|
|
@ -306,5 +308,17 @@ class AdminAuthenticationForm(AuthenticationForm):
|
|||
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)
|
||||
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.contrib.contenttypes.models import ContentType
|
||||
from django.db import models
|
||||
from django.db.models import signals
|
||||
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 smart_text
|
||||
from django.utils.translation import ugettext, ugettext_lazy as _
|
||||
|
||||
from . import permissions
|
||||
from .utils import quote
|
||||
|
||||
|
||||
|
|
@ -99,10 +97,3 @@ class LogEntry(models.Model):
|
|||
quote(self.object_id)
|
||||
)
|
||||
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 re
|
||||
|
||||
from django.contrib.auth import models as auth_models
|
||||
from django.contrib.contenttypes import models as contenttypes_models
|
||||
from django.db.models import get_models
|
||||
from django.contrib.auth import get_permission_codename
|
||||
from django.db.utils import DEFAULT_DB_ALIAS
|
||||
from django.apps import apps
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import router
|
||||
from django.utils import six
|
||||
|
||||
from . import utils
|
||||
from django.utils.encoding import python_2_unicode_compatible, force_text
|
||||
|
||||
|
||||
logger = logging.getLogger('djadmin2')
|
||||
|
||||
|
||||
|
|
@ -82,13 +81,13 @@ def model_permission(permission):
|
|||
assert model_class, (
|
||||
'Cannot apply model permissions on a view that does not '
|
||||
'have a `.model` or `.queryset` property.')
|
||||
|
||||
|
||||
try:
|
||||
# django 1.8+
|
||||
model_name = model_class._meta.model_name
|
||||
except AttributeError:
|
||||
model_name = model_class._meta.module_name
|
||||
|
||||
|
||||
permission_name = permission.format(
|
||||
app_label=model_class._meta.app_label,
|
||||
model_name=model_name)
|
||||
|
|
@ -363,14 +362,13 @@ class TemplatePermissionChecker(object):
|
|||
else:
|
||||
return self._view.has_permission(self._obj)
|
||||
|
||||
|
||||
def __str__(self):
|
||||
if self._view is None:
|
||||
return ''
|
||||
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.
|
||||
|
||||
|
|
@ -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
|
||||
permission.
|
||||
|
||||
Copied from ``django.contrib.auth.management.create_permissions``.
|
||||
"""
|
||||
# Is there any reason for doing this import here?
|
||||
Copied from ``https://github.com/django/django/blob/1.9.6/django/contrib/auth/management/__init__.py#L60``.
|
||||
|
||||
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
|
||||
# (content_type, (codename, name))
|
||||
searched_perms = list()
|
||||
# The codenames and ctypes that should exist.
|
||||
ctypes = set()
|
||||
for klass in app_models:
|
||||
ctype = contenttypes_models.ContentType.objects.get_for_model(klass)
|
||||
ctypes.add(ctype)
|
||||
for klass in app_config.get_models():
|
||||
# Force looking up the content types in the current database
|
||||
# before creating foreign keys to them.
|
||||
ctype = ContentType.objects.db_manager(using).get_for_model(klass)
|
||||
|
||||
opts = utils.model_options(klass)
|
||||
perm = ('view_%s' % opts.object_name.lower(), u'Can view %s' % opts.verbose_name_raw)
|
||||
ctypes.add(ctype)
|
||||
perm = (get_permission_codename('view', klass._meta), 'Can view %s' % (klass._meta.verbose_name_raw))
|
||||
searched_perms.append((ctype, perm))
|
||||
|
||||
# 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
|
||||
# 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,
|
||||
).values_list(
|
||||
"content_type", "codename"
|
||||
))
|
||||
|
||||
perms = [
|
||||
auth_models.Permission(codename=codename, name=name, content_type=ctype)
|
||||
for ctype, (codename, name) in searched_perms
|
||||
if (ctype.pk, codename) not in all_perms
|
||||
Permission(codename=codename, name=name, content_type=ct)
|
||||
for ct, (codename, name) in searched_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:
|
||||
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 django.db import models
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils import formats, timezone
|
||||
from django.utils.encoding import force_text
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
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 ..core import Admin2
|
||||
from ..actions import get_description
|
||||
|
||||
|
||||
class Thing(models.Model):
|
||||
pass
|
||||
from .models import Thing
|
||||
|
||||
|
||||
class TestAction(object):
|
||||
|
|
@ -26,24 +22,24 @@ class ActionTest(TestCase):
|
|||
self.admin2.registry[Thing].list_actions.extend([
|
||||
TestAction,
|
||||
test_function,
|
||||
])
|
||||
])
|
||||
self.assertEquals(
|
||||
get_description(
|
||||
self.admin2.registry[Thing].list_actions[0]
|
||||
),
|
||||
),
|
||||
'Delete selected items'
|
||||
)
|
||||
)
|
||||
self.assertEquals(
|
||||
get_description(
|
||||
self.admin2.registry[Thing].list_actions[1]
|
||||
),
|
||||
),
|
||||
'Test Action Class'
|
||||
)
|
||||
)
|
||||
self.assertEquals(
|
||||
get_description(
|
||||
self.admin2.registry[Thing].list_actions[2]
|
||||
),
|
||||
),
|
||||
'Test function'
|
||||
)
|
||||
)
|
||||
self.admin2.registry[Thing].list_actions.remove(TestAction)
|
||||
self.admin2.registry[Thing].list_actions.remove(test_function)
|
||||
|
|
|
|||
|
|
@ -1,25 +1,10 @@
|
|||
from django.db import models
|
||||
from django import forms
|
||||
from django.forms.formsets import formset_factory
|
||||
from django.test import TestCase
|
||||
|
||||
from ..templatetags import admin2_tags
|
||||
from ..views import IndexView
|
||||
|
||||
|
||||
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"
|
||||
from .models import TagsTestsModel
|
||||
|
||||
|
||||
class TagsTestForm(forms.Form):
|
||||
|
|
@ -89,7 +74,7 @@ class TagsTests(TestCase):
|
|||
self.assertEquals(
|
||||
admin2_tags.formset_visible_fieldlist(formset),
|
||||
[u'Visible 1', u'Visible 2']
|
||||
)
|
||||
)
|
||||
|
||||
def test_verbose_name_for(self):
|
||||
app_verbose_names = {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from django.test.client import RequestFactory
|
|||
|
||||
import floppyforms
|
||||
|
||||
import djadmin2
|
||||
from djadmin2.site import djadmin2_site
|
||||
from ..admin2 import UserAdmin2
|
||||
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ class UserAdminTest(TestCase):
|
|||
|
||||
request = self.factory.get(reverse('admin2:auth_user_create'))
|
||||
request.user = self.user
|
||||
model_admin = UserAdmin2(User, djadmin2.default)
|
||||
model_admin = UserAdmin2(User, djadmin2_site)
|
||||
view = model_admin.create_view.view.as_view(
|
||||
**model_admin.get_create_kwargs())
|
||||
response = view(request)
|
||||
|
|
@ -48,7 +48,7 @@ class UserAdminTest(TestCase):
|
|||
request = self.factory.get(
|
||||
reverse('admin2:auth_user_update', args=(self.user.pk,)))
|
||||
request.user = self.user
|
||||
model_admin = UserAdmin2(User, djadmin2.default)
|
||||
model_admin = UserAdmin2(User, djadmin2_site)
|
||||
view = model_admin.update_view.view.as_view(
|
||||
**model_admin.get_update_kwargs())
|
||||
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.sites.models import Site
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.test import TestCase
|
||||
|
||||
import djadmin2
|
||||
from ..types import ModelAdmin2
|
||||
from djadmin2.site import djadmin2_site
|
||||
from .models import SmallThing
|
||||
from ..core import Admin2
|
||||
|
||||
|
||||
class SmallThing(models.Model):
|
||||
pass
|
||||
|
||||
from ..types import ModelAdmin2
|
||||
|
||||
APP_LABEL, APP_VERBOSE_NAME = 'app_one_label', 'App One Verbose Name'
|
||||
|
||||
|
|
@ -71,4 +66,4 @@ class Admin2Test(TestCase):
|
|||
def test_default_entries(self):
|
||||
expected_default_models = (User, Group, Site)
|
||||
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 django.test import TestCase
|
||||
from django.db import models
|
||||
from django.utils.translation import activate
|
||||
from django.utils import six
|
||||
from django.utils.translation import activate
|
||||
|
||||
from .. import renderers
|
||||
|
||||
|
||||
class RendererTestModel(models.Model):
|
||||
decimal = models.DecimalField(decimal_places=5, max_digits=10)
|
||||
from .models import RendererTestModel
|
||||
|
||||
|
||||
class BooleanRendererTest(TestCase):
|
||||
|
|
@ -109,7 +105,7 @@ class NumberRendererTest(TestCase):
|
|||
self.assertEqual('42.5', out)
|
||||
|
||||
def testEndlessFloat(self):
|
||||
out = self.renderer(1.0/3, None)
|
||||
out = self.renderer(1.0 / 3, None)
|
||||
if six.PY2:
|
||||
self.assertEqual('0.333333333333', out)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
from django.db import models
|
||||
from django.test import TestCase
|
||||
from django.views.generic import View
|
||||
|
||||
from .. import views
|
||||
from ..types import ModelAdmin2, immutable_admin_factory
|
||||
from ..core import Admin2
|
||||
from .models import BigThing
|
||||
|
||||
|
||||
class ModelAdmin(object):
|
||||
|
|
@ -40,10 +39,6 @@ class ImmutableAdminFactoryTests(TestCase):
|
|||
self.immutable_admin.d
|
||||
|
||||
|
||||
class BigThing(models.Model):
|
||||
pass
|
||||
|
||||
|
||||
class ModelAdminTest(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
|
|
|||
|
|
@ -1,27 +1,9 @@
|
|||
from django.db import models
|
||||
from django.test import TestCase
|
||||
from django.utils import six
|
||||
|
||||
from .. import utils
|
||||
from ..views import IndexView
|
||||
|
||||
|
||||
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"
|
||||
from .models import UtilsTestModel
|
||||
|
||||
|
||||
class UtilsTest(TestCase):
|
||||
|
|
@ -40,7 +22,7 @@ class UtilsTest(TestCase):
|
|||
self.assertEquals(
|
||||
UtilsTestModel._meta,
|
||||
utils.model_options(UtilsTestModel)
|
||||
)
|
||||
)
|
||||
UtilsTestModel._meta.verbose_name = "Utils Test Model"
|
||||
UtilsTestModel._meta.verbose_name_plural = "Utils Test Models"
|
||||
|
||||
|
|
@ -55,7 +37,7 @@ class UtilsTest(TestCase):
|
|||
self.assertEquals(
|
||||
self.instance._meta,
|
||||
utils.model_options(self.instance)
|
||||
)
|
||||
)
|
||||
self.instance._meta.verbose_name = "Utils Test Model"
|
||||
self.instance._meta.verbose_name_plural = "Utils Test Models"
|
||||
|
||||
|
|
@ -166,8 +148,6 @@ class UtilsTest(TestCase):
|
|||
"str"
|
||||
)
|
||||
|
||||
|
||||
|
||||
def test_get_attr(self):
|
||||
class Klass(object):
|
||||
attr = "value"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
from django.test import TestCase
|
||||
from django.views.generic import View
|
||||
|
||||
from .. import views
|
||||
|
||||
|
|
|
|||
|
|
@ -1,22 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import division, absolute_import, unicode_literals
|
||||
|
||||
from collections import namedtuple
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.conf.urls import patterns, url
|
||||
from django.utils.six import with_metaclass
|
||||
from collections import namedtuple
|
||||
|
||||
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 settings
|
||||
from . import views
|
||||
from . import actions
|
||||
from . import utils
|
||||
from . import views
|
||||
from .forms import modelform_factory
|
||||
|
||||
|
||||
|
|
@ -202,7 +201,8 @@ class ModelAdmin2(with_metaclass(ModelAdminBase2)):
|
|||
def get_api_list_kwargs(self):
|
||||
kwargs = self.get_default_api_view_kwargs()
|
||||
kwargs.update({
|
||||
'paginate_by': self.list_per_page,
|
||||
'queryset': self.model.objects.all(),
|
||||
# 'paginate_by': self.list_per_page,
|
||||
})
|
||||
return kwargs
|
||||
|
||||
|
|
@ -236,11 +236,10 @@ class ModelAdmin2(with_metaclass(ModelAdminBase2)):
|
|||
name=self.get_prefixed_view_name(admin_view.name)
|
||||
)
|
||||
)
|
||||
return patterns('', *pattern_list)
|
||||
return pattern_list
|
||||
|
||||
def get_api_urls(self):
|
||||
return patterns(
|
||||
'',
|
||||
return [
|
||||
url(
|
||||
regex=r'^$',
|
||||
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()),
|
||||
name=self.get_prefixed_view_name('api_detail'),
|
||||
),
|
||||
)
|
||||
]
|
||||
|
||||
@property
|
||||
def urls(self):
|
||||
|
|
|
|||
|
|
@ -3,13 +3,12 @@ from __future__ import division, absolute_import, unicode_literals
|
|||
|
||||
from collections import defaultdict
|
||||
|
||||
from django.db.models import ProtectedError
|
||||
from django.db.models import ManyToManyRel
|
||||
from django.db.models.deletion import Collector
|
||||
from django.db.models.fields.related import ForeignObjectRel
|
||||
from django.db.models.deletion import Collector, ProtectedError
|
||||
from django.db.models.sql.constants import QUERY_TERMS
|
||||
from django.utils import six
|
||||
from django.utils.encoding import force_bytes, force_text
|
||||
|
||||
|
||||
def lookup_needs_distinct(opts, 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
|
||||
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]
|
||||
condition1 = hasattr(field, 'rel') and isinstance(field.rel, ManyToManyRel)
|
||||
condition2 = isinstance(field, ForeignObjectRel) and not field.field.unique
|
||||
return condition1 or condition2
|
||||
|
||||
lookup_fields = lookup_path.split('__')
|
||||
# Remove the last item of the lookup path if it is a query term
|
||||
if lookup_fields[-1] in QUERY_TERMS:
|
||||
lookup_fields = lookup_fields[:-1]
|
||||
# Now go through the fields (following all relations) and look for an m2m
|
||||
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):
|
||||
|
|
@ -91,10 +101,8 @@ def get_attr(obj, attr):
|
|||
and the __str__ attribute.
|
||||
"""
|
||||
if attr == '__str__':
|
||||
if six.PY2:
|
||||
value = unicode(obj)
|
||||
else:
|
||||
value = str(obj)
|
||||
from builtins import str as text
|
||||
value = text(obj)
|
||||
else:
|
||||
attribute = getattr(obj, attr)
|
||||
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
|
||||
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):
|
||||
super(NestedObjects, self).__init__(*args, **kwargs)
|
||||
self.edges = {} # {from_instance: [to_instances]}
|
||||
self.protected = set()
|
||||
self.model_count = defaultdict(int)
|
||||
self.model_objs = defaultdict(set)
|
||||
|
||||
def add_edge(self, source, target):
|
||||
self.edges.setdefault(source, []).append(target)
|
||||
|
|
@ -129,7 +136,7 @@ class NestedObjects(Collector):
|
|||
self.add_edge(getattr(obj, related_name), obj)
|
||||
else:
|
||||
self.add_edge(None, obj)
|
||||
self.model_count[obj._meta.verbose_name_plural] += 1
|
||||
self.model_objs[obj._meta.model].add(obj)
|
||||
try:
|
||||
return super(NestedObjects, self).collect(objs, source_attr=source_attr, **kwargs)
|
||||
except ProtectedError as e:
|
||||
|
|
@ -157,7 +164,6 @@ class NestedObjects(Collector):
|
|||
def nested(self, format_callback=None):
|
||||
"""
|
||||
Return the graph as a nested list.
|
||||
|
||||
"""
|
||||
seen = set()
|
||||
roots = []
|
||||
|
|
@ -183,14 +189,14 @@ def quote(s):
|
|||
This is adopted from the Django core. django-admin2 mandates that code
|
||||
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):
|
||||
return s
|
||||
res = list(s)
|
||||
for i in range(len(res)):
|
||||
c = res[i]
|
||||
if c in """:/_#?;@&=+$,"<>%\\""":
|
||||
if c in """:/_#?;@&=+$,"[]<>%\n\\""":
|
||||
res[i] = '_%02X' % ord(c)
|
||||
return ''.join(res)
|
||||
|
||||
|
|
@ -199,4 +205,4 @@ def type_str(text):
|
|||
if six.PY2:
|
||||
return force_bytes(text)
|
||||
else:
|
||||
return force_text(text)
|
||||
return force_text(text)
|
||||
|
|
|
|||
|
|
@ -116,11 +116,11 @@ class Admin2Mixin(PermissionMixin):
|
|||
return super(Admin2Mixin, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
class AdminModel2Mixin(Admin2Mixin):
|
||||
class Admin2ModelMixin(Admin2Mixin):
|
||||
model_admin = None
|
||||
|
||||
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_meta = model_options(model)
|
||||
app_verbose_names = self.model_admin.admin.app_verbose_names
|
||||
|
|
|
|||
|
|
@ -2,11 +2,12 @@
|
|||
from __future__ import division, absolute_import, unicode_literals
|
||||
|
||||
import operator
|
||||
from datetime import datetime
|
||||
from functools import reduce
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
import extra_views
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import update_session_auth_hash
|
||||
from django.contrib.auth.forms import (PasswordChangeForm,
|
||||
AdminPasswordChangeForm)
|
||||
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.translation import ugettext_lazy
|
||||
from django.views import generic
|
||||
import extra_views
|
||||
|
||||
|
||||
from . import permissions, utils
|
||||
from .forms import AdminAuthenticationForm
|
||||
from .viewmixins import Admin2Mixin, AdminModel2Mixin, Admin2ModelFormMixin
|
||||
from .filters import build_list_filter, build_date_filter
|
||||
from .forms import AdminAuthenticationForm
|
||||
from .models import LogEntry
|
||||
from .viewmixins import Admin2Mixin, Admin2ModelMixin, Admin2ModelFormMixin
|
||||
|
||||
|
||||
class AdminView(object):
|
||||
|
||||
|
|
@ -101,7 +101,7 @@ class AppIndexView(Admin2Mixin, generic.TemplateView):
|
|||
return data
|
||||
|
||||
|
||||
class ModelListView(AdminModel2Mixin, generic.ListView):
|
||||
class ModelListView(Admin2ModelMixin, generic.ListView):
|
||||
"""Context Variables
|
||||
|
||||
: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
|
||||
|
||||
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)
|
||||
|
||||
|
|
@ -233,7 +233,7 @@ class ModelListView(AdminModel2Mixin, generic.ListView):
|
|||
)
|
||||
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 queryset is None:
|
||||
queryset = self.get_queryset()
|
||||
|
|
@ -241,6 +241,7 @@ class ModelListView(AdminModel2Mixin, generic.ListView):
|
|||
self.request,
|
||||
self.model_admin,
|
||||
queryset,
|
||||
field_name
|
||||
)
|
||||
|
||||
return self._date_filter
|
||||
|
|
@ -271,38 +272,38 @@ class ModelListView(AdminModel2Mixin, generic.ListView):
|
|||
|
||||
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:
|
||||
context["previous_date"] = {
|
||||
"link": "?year=%s" % (year),
|
||||
"text": "‹ %s" % year,
|
||||
}
|
||||
|
||||
context["dates"] = self._format_days(context)
|
||||
context["dates"] = self._format_days(self.get_queryset())
|
||||
elif year:
|
||||
context["previous_date"] = {
|
||||
"link": "?",
|
||||
"text": ugettext_lazy("‹ All dates"),
|
||||
}
|
||||
|
||||
context["dates"] = self._format_months(context)
|
||||
context["dates"] = self._format_months(self.get_queryset())
|
||||
else:
|
||||
context["dates"] = self._format_years(context)
|
||||
context["dates"] = self._format_years(self.get_queryset())
|
||||
|
||||
return context
|
||||
|
||||
def _format_years(self, context):
|
||||
years = self._qs_date_or_datetime(context['object_list'], 'year')
|
||||
def _format_years(self, queryset):
|
||||
years = self._qs_date_or_datetime(queryset, 'year')
|
||||
if len(years) == 1:
|
||||
return self._format_months(context)
|
||||
return self._format_months(queryset)
|
||||
else:
|
||||
return [
|
||||
(("?year=%s" % year.strftime("%Y")), year.strftime("%Y"))
|
||||
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 [
|
||||
(
|
||||
"?year=%s&month=%s" % (
|
||||
|
|
@ -310,10 +311,10 @@ class ModelListView(AdminModel2Mixin, generic.ListView):
|
|||
),
|
||||
date.strftime("%B %Y")
|
||||
) 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 [
|
||||
(
|
||||
"?year=%s&month=%s&day=%s" % (
|
||||
|
|
@ -323,7 +324,7 @@ class ModelListView(AdminModel2Mixin, generic.ListView):
|
|||
),
|
||||
date.strftime("%B %d")
|
||||
) 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):
|
||||
|
|
@ -345,7 +346,7 @@ class ModelListView(AdminModel2Mixin, generic.ListView):
|
|||
return self.model_admin.search_fields
|
||||
|
||||
|
||||
class ModelDetailView(AdminModel2Mixin, generic.DetailView):
|
||||
class ModelDetailView(Admin2ModelMixin, generic.DetailView):
|
||||
"""Context Variables
|
||||
|
||||
:model: Type of object you are editing
|
||||
|
|
@ -363,7 +364,7 @@ class ModelDetailView(AdminModel2Mixin, generic.DetailView):
|
|||
permissions.ModelViewPermission)
|
||||
|
||||
|
||||
class ModelEditFormView(AdminModel2Mixin, Admin2ModelFormMixin,
|
||||
class ModelEditFormView(Admin2ModelMixin, Admin2ModelFormMixin,
|
||||
extra_views.UpdateWithInlinesView):
|
||||
"""Context Variables
|
||||
|
||||
|
|
@ -399,7 +400,7 @@ class ModelEditFormView(AdminModel2Mixin, Admin2ModelFormMixin,
|
|||
return response
|
||||
|
||||
|
||||
class ModelAddFormView(AdminModel2Mixin, Admin2ModelFormMixin,
|
||||
class ModelAddFormView(Admin2ModelMixin, Admin2ModelFormMixin,
|
||||
extra_views.CreateWithInlinesView):
|
||||
"""Context Variables
|
||||
|
||||
|
|
@ -435,7 +436,7 @@ class ModelAddFormView(AdminModel2Mixin, Admin2ModelFormMixin,
|
|||
return response
|
||||
|
||||
|
||||
class ModelDeleteView(AdminModel2Mixin, generic.DeleteView):
|
||||
class ModelDeleteView(Admin2ModelMixin, generic.DeleteView):
|
||||
"""Context Variables
|
||||
|
||||
:model: Type of object you are editing
|
||||
|
|
@ -461,7 +462,7 @@ class ModelDeleteView(AdminModel2Mixin, generic.DeleteView):
|
|||
opts = utils.model_options(obj)
|
||||
return '%s: %s' % (force_text(capfirst(opts.verbose_name)),
|
||||
force_text(obj))
|
||||
|
||||
|
||||
using = router.db_for_write(self.get_object()._meta.model)
|
||||
collector = utils.NestedObjects(using=using)
|
||||
collector.collect([self.get_object()])
|
||||
|
|
@ -479,7 +480,7 @@ class ModelDeleteView(AdminModel2Mixin, generic.DeleteView):
|
|||
return super(ModelDeleteView, self).delete(request, *args, **kwargs)
|
||||
|
||||
|
||||
class ModelHistoryView(AdminModel2Mixin, generic.ListView):
|
||||
class ModelHistoryView(Admin2ModelMixin, generic.ListView):
|
||||
"""Context Variables
|
||||
|
||||
:model: Type of object you are editing
|
||||
|
|
@ -541,6 +542,13 @@ class PasswordChangeView(Admin2Mixin, generic.UpdateView):
|
|||
from django.contrib.auth import get_user_model
|
||||
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):
|
||||
|
||||
default_template_name = 'auth/password_change_done.html'
|
||||
|
|
|
|||
|
|
@ -33,17 +33,17 @@ Add djadmin2 urls to your URLconf:
|
|||
.. code-block:: python
|
||||
|
||||
# urls.py
|
||||
from django.conf.urls import patterns, include
|
||||
from django.conf.urls import include
|
||||
|
||||
import djadmin2
|
||||
|
||||
djadmin2.default.autodiscover()
|
||||
|
||||
|
||||
urlpatterns = patterns(
|
||||
urlpatterns = [
|
||||
...
|
||||
url(r'^admin2/', include(djadmin2.default.urls)),
|
||||
)
|
||||
]
|
||||
|
||||
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*':
|
||||
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import division, absolute_import, unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext_lazy, pgettext_lazy
|
||||
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.actions import BaseListAction
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,21 +3,22 @@ from __future__ import division, absolute_import, unicode_literals
|
|||
|
||||
from django.utils.translation import ugettext_lazy
|
||||
|
||||
import djadmin2
|
||||
from djadmin2 import renderers
|
||||
from djadmin2.actions import DeleteSelectedAction
|
||||
|
||||
# Import your custom models
|
||||
from djadmin2.site import djadmin2_site
|
||||
from djadmin2.types import Admin2TabularInline, ModelAdmin2
|
||||
from .actions import (CustomPublishAction, PublishAllItemsAction,
|
||||
unpublish_items, unpublish_all_items)
|
||||
from .models import Post, Comment
|
||||
|
||||
|
||||
class CommentInline(djadmin2.Admin2TabularInline):
|
||||
class CommentInline(Admin2TabularInline):
|
||||
model = Comment
|
||||
|
||||
|
||||
class PostAdmin(djadmin2.ModelAdmin2):
|
||||
class PostAdmin(ModelAdmin2):
|
||||
list_actions = [
|
||||
DeleteSelectedAction, CustomPublishAction,
|
||||
PublishAllItemsAction, unpublish_items,
|
||||
|
|
@ -34,7 +35,7 @@ class PostAdmin(djadmin2.ModelAdmin2):
|
|||
ordering = ["-published_date", "title",]
|
||||
|
||||
|
||||
class CommentAdmin(djadmin2.ModelAdmin2):
|
||||
class CommentAdmin(ModelAdmin2):
|
||||
search_fields = ('body', '=post__title')
|
||||
list_filter = ['post', ]
|
||||
actions_on_top = True
|
||||
|
|
@ -42,12 +43,12 @@ class CommentAdmin(djadmin2.ModelAdmin2):
|
|||
actions_selection_counter = False
|
||||
|
||||
# Register the blog app with a verbose name
|
||||
djadmin2.default.register_app_verbose_name(
|
||||
djadmin2_site.register_app_verbose_name(
|
||||
'blog',
|
||||
ugettext_lazy('My Blog')
|
||||
)
|
||||
|
||||
# Register each model with the admin
|
||||
djadmin2.default.register(Post, PostAdmin)
|
||||
djadmin2.default.register(Comment, CommentAdmin)
|
||||
djadmin2_site.register(Post, PostAdmin)
|
||||
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>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
|
@ -7,8 +7,8 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<!-- Bootstrap -->
|
||||
{% block css %}
|
||||
<link href="{{ STATIC_URL }}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.min.css" %}" rel="stylesheet" media="screen">
|
||||
<link href="{% static "themes/bootstrap/css/bootstrap-custom.css" %}" rel="stylesheet" media="screen">
|
||||
{% endblock css %}
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -19,8 +19,8 @@
|
|||
|
||||
{% block javascript %}
|
||||
<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 src="{{ STATIC_URL }}themes/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script>window.jQuery || document.write('<script src="{% static "themes/bootstrap/js/jquery.min.js" %}">\x3C/script>')</script>
|
||||
<script src="{% static "themes/bootstrap/js/bootstrap.min.js" %}"></script>
|
||||
{% endblock javascript %}
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
|
||||
from django.contrib.auth.models import AnonymousUser, User
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
from django.utils.encoding import force_text
|
||||
import json
|
||||
|
||||
|
||||
from djadmin2 import apiviews
|
||||
from djadmin2 import default
|
||||
from djadmin2 import ModelAdmin2
|
||||
from djadmin2.site import djadmin2_site
|
||||
from djadmin2.types import ModelAdmin2
|
||||
from ..models import Post
|
||||
|
||||
|
||||
|
|
@ -24,21 +25,21 @@ class APITestCase(TestCase):
|
|||
self.user.save()
|
||||
|
||||
def get_model_admin(self, model):
|
||||
return ModelAdmin2(model, default)
|
||||
return ModelAdmin2(model, djadmin2_site)
|
||||
|
||||
|
||||
class IndexAPIViewTest(APITestCase):
|
||||
def test_response_ok(self):
|
||||
request = self.factory.get(reverse('admin2:api_index'))
|
||||
request.user = self.user
|
||||
view = apiviews.IndexAPIView.as_view(**default.get_api_index_kwargs())
|
||||
view = apiviews.IndexAPIView.as_view(**djadmin2_site.get_api_index_kwargs())
|
||||
response = view(request)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_view_permission(self):
|
||||
request = self.factory.get(reverse('admin2:api_index'))
|
||||
request.user = AnonymousUser()
|
||||
view = apiviews.IndexAPIView.as_view(**default.get_api_index_kwargs())
|
||||
view = apiviews.IndexAPIView.as_view(**djadmin2_site.get_api_index_kwargs())
|
||||
self.assertRaises(PermissionDenied, view, request)
|
||||
|
||||
|
||||
|
|
@ -71,7 +72,7 @@ class ListCreateAPIViewTest(APITestCase):
|
|||
response.render()
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn('"__unicode__": "Foo"', force_text(response.content))
|
||||
self.assertIn('"__unicode__":"Foo"', force_text(response.content))
|
||||
|
||||
def test_pagination(self):
|
||||
request = self.factory.get(reverse('admin2:blog_post_api_list'))
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# vim:fenc=utf-8
|
||||
|
||||
import django_filters
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from djadmin2 import filters as djadmin2_filters
|
||||
from djadmin2.types import ModelAdmin2
|
||||
from ..models import Post
|
||||
|
||||
import djadmin2
|
||||
import djadmin2.filters as djadmin2_filters
|
||||
|
||||
import django_filters
|
||||
|
||||
class ListFilterBuilderTest(TestCase):
|
||||
|
||||
|
|
@ -18,11 +17,11 @@ class ListFilterBuilderTest(TestCase):
|
|||
self.rf = RequestFactory()
|
||||
|
||||
def test_filter_building(self):
|
||||
class PostAdminSimple(djadmin2.ModelAdmin2):
|
||||
class PostAdminSimple(ModelAdmin2):
|
||||
list_filter = ['published', ]
|
||||
|
||||
|
||||
class PostAdminWithFilterInstances(djadmin2.ModelAdmin2):
|
||||
class PostAdminWithFilterInstances(ModelAdmin2):
|
||||
list_filter = [
|
||||
django_filters.BooleanFilter(name='published'),
|
||||
]
|
||||
|
|
@ -33,7 +32,7 @@ class ListFilterBuilderTest(TestCase):
|
|||
fields = ['published']
|
||||
|
||||
|
||||
class PostAdminWithFilterSetInst(djadmin2.ModelAdmin2):
|
||||
class PostAdminWithFilterSetInst(ModelAdmin2):
|
||||
list_filter = FS
|
||||
|
||||
Post.objects.create(title="post_1_title", body="body")
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import floppyforms
|
||||
from django import forms
|
||||
from django.test import TestCase
|
||||
|
||||
import floppyforms
|
||||
|
||||
from djadmin2.forms import floppify_widget, floppify_form, modelform_factory
|
||||
from ..models import Post
|
||||
|
||||
|
|
@ -103,7 +102,6 @@ class GetFloppyformWidgetTest(TestCase):
|
|||
floppyforms.widgets.HiddenInput)
|
||||
|
||||
widget = forms.widgets.HiddenInput()
|
||||
widget.is_hidden = False
|
||||
self.assertExpectWidget(
|
||||
widget,
|
||||
floppyforms.widgets.HiddenInput,
|
||||
|
|
@ -162,7 +160,7 @@ class GetFloppyformWidgetTest(TestCase):
|
|||
forms.DateInput(),
|
||||
floppyforms.DateInput)
|
||||
|
||||
widget = forms.widgets.DateInput(format='DATE_FORMAT')
|
||||
widget = forms.widgets.DateInput(format='%Y-%m-%d')
|
||||
self.assertExpectWidget(
|
||||
widget,
|
||||
floppyforms.widgets.DateInput,
|
||||
|
|
@ -311,13 +309,13 @@ class GetFloppyformWidgetTest(TestCase):
|
|||
floppyforms.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)
|
||||
self.assertTrue(isinstance(
|
||||
new_widget.widgets[0], floppyforms.widgets.DateInput))
|
||||
self.assertTrue(isinstance(
|
||||
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')
|
||||
|
||||
def test_splithiddendatetime_widget(self):
|
||||
|
|
@ -327,13 +325,13 @@ class GetFloppyformWidgetTest(TestCase):
|
|||
floppyforms.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)
|
||||
self.assertTrue(isinstance(
|
||||
new_widget.widgets[0], floppyforms.widgets.DateInput))
|
||||
self.assertTrue(isinstance(
|
||||
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[1].format, 'TIME_FORMAT')
|
||||
self.assertEqual(new_widget.widgets[1].is_hidden, True)
|
||||
|
|
@ -489,13 +487,13 @@ class FieldWidgetTest(TestCase):
|
|||
self.assertTrue(isinstance(widget, floppyforms.widgets.SlugInput))
|
||||
self.assertEqual(widget.input_type, 'text')
|
||||
|
||||
def test_ipaddress_field(self):
|
||||
def test_genericipaddress_field(self):
|
||||
class MyForm(forms.ModelForm):
|
||||
ipaddress = forms.IPAddressField()
|
||||
ipaddress = forms.GenericIPAddressField()
|
||||
|
||||
form_class = modelform_factory(model=Post, form=MyForm, exclude=[])
|
||||
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')
|
||||
|
||||
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 djadmin2.utils import NestedObjects
|
||||
|
||||
from ..models import Count, Event, EventGuide, Guest, Location
|
||||
from ..models import Count, Event, EventGuide
|
||||
|
||||
|
||||
class NestedObjectsTests(TestCase):
|
||||
|
|
@ -66,7 +65,8 @@ class NestedObjectsTests(TestCase):
|
|||
Check that the nested collector doesn't query for DO_NOTHING objects.
|
||||
"""
|
||||
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])
|
||||
with self.assertNumQueries(2):
|
||||
# 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.core.urlresolvers import reverse
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.template import Template, Context
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
|
||||
import djadmin2
|
||||
from djadmin2 import ModelAdmin2
|
||||
from djadmin2.permissions import TemplatePermissionChecker
|
||||
|
||||
from blog.models import Post
|
||||
from djadmin2.site import djadmin2_site
|
||||
from djadmin2.types import ModelAdmin2
|
||||
|
||||
|
||||
class TemplatePermissionTest(TestCase):
|
||||
|
|
@ -26,7 +26,7 @@ class TemplatePermissionTest(TestCase):
|
|||
return template.render(context)
|
||||
|
||||
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.user = self.user
|
||||
permissions = TemplatePermissionChecker(request, model_admin)
|
||||
|
|
@ -48,8 +48,8 @@ class TemplatePermissionTest(TestCase):
|
|||
codename='add_post')
|
||||
self.user.user_permissions.add(post_add_permission)
|
||||
# invalidate the users permission cache
|
||||
if hasattr(self.user, '_perm_cache'):
|
||||
del self.user._perm_cache
|
||||
self.user = get_object_or_404(User, pk=self.user.id)
|
||||
request.user = self.user
|
||||
|
||||
result = self.render('{{ permissions.has_add_permission }}', context)
|
||||
self.assertEqual(result, 'True')
|
||||
|
|
@ -61,7 +61,7 @@ class TemplatePermissionTest(TestCase):
|
|||
codename='add_post')
|
||||
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.user = self.user
|
||||
permissions = TemplatePermissionChecker(request, model_admin)
|
||||
|
|
@ -89,8 +89,8 @@ class TemplatePermissionTest(TestCase):
|
|||
self.assertEqual(result, '')
|
||||
|
||||
def test_admin_binding(self):
|
||||
user_admin = djadmin2.default.get_admin_by_name('auth_user')
|
||||
post_admin = djadmin2.default.get_admin_by_name('blog_post')
|
||||
user_admin = djadmin2_site.get_admin_by_name('auth_user')
|
||||
post_admin = djadmin2_site.get_admin_by_name('blog_post')
|
||||
request = self.factory.get(reverse('admin2:auth_user_index'))
|
||||
request.user = self.user
|
||||
permissions = TemplatePermissionChecker(request, user_admin)
|
||||
|
|
@ -121,10 +121,11 @@ class TemplatePermissionTest(TestCase):
|
|||
content_type__app_label='blog',
|
||||
content_type__model='post',
|
||||
codename='add_post')
|
||||
|
||||
self.user.user_permissions.add(post_add_permission)
|
||||
# invalidate the users permission cache
|
||||
if hasattr(self.user, '_perm_cache'):
|
||||
del self.user._perm_cache
|
||||
self.user = get_object_or_404(User, pk=self.user.id)
|
||||
request.user = self.user
|
||||
|
||||
result = self.render(
|
||||
'{% load admin2_tags %}'
|
||||
|
|
@ -155,8 +156,8 @@ class TemplatePermissionTest(TestCase):
|
|||
self.assertEqual(result, '')
|
||||
|
||||
def test_view_binding(self):
|
||||
user_admin = djadmin2.default.get_admin_by_name('auth_user')
|
||||
post_admin = djadmin2.default.get_admin_by_name('blog_post')
|
||||
user_admin = djadmin2_site.get_admin_by_name('auth_user')
|
||||
post_admin = djadmin2_site.get_admin_by_name('blog_post')
|
||||
request = self.factory.get(reverse('admin2:auth_user_index'))
|
||||
request.user = self.user
|
||||
permissions = TemplatePermissionChecker(request, user_admin)
|
||||
|
|
@ -203,8 +204,8 @@ class TemplatePermissionTest(TestCase):
|
|||
self.user.user_permissions.add(user_change_permission)
|
||||
|
||||
# invalidate the users permission cache
|
||||
if hasattr(self.user, '_perm_cache'):
|
||||
del self.user._perm_cache
|
||||
self.user = get_object_or_404(User, pk=self.user.id)
|
||||
request.user = self.user
|
||||
|
||||
result = self.render(
|
||||
'{% load admin2_tags %}'
|
||||
|
|
@ -235,7 +236,7 @@ class TemplatePermissionTest(TestCase):
|
|||
self.assertEqual(result, '1True2False34True')
|
||||
|
||||
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.user = self.user
|
||||
permissions = TemplatePermissionChecker(request, model_admin)
|
||||
|
|
@ -264,8 +265,8 @@ class TemplatePermissionTest(TestCase):
|
|||
codename='add_post')
|
||||
self.user.user_permissions.add(post_add_permission)
|
||||
# invalidate the users permission cache
|
||||
if hasattr(self.user, '_perm_cache'):
|
||||
del self.user._perm_cache
|
||||
self.user = get_object_or_404(User, pk=self.user.id)
|
||||
request.user = self.user
|
||||
|
||||
# object level permission are not supported by default. So this will
|
||||
# return ``False``.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
|
|
@ -515,18 +516,19 @@ class TestAuthViews(TestCase):
|
|||
def test_change_password_for_myself(self):
|
||||
self.client.login(username=self.user.username,
|
||||
password='password')
|
||||
|
||||
request = self.client.post(reverse('admin2:password_change',
|
||||
kwargs={'pk': self.user.pk}),
|
||||
{'old_password': 'password',
|
||||
'new_password1': 'user',
|
||||
'new_password2': 'user'})
|
||||
'new_password1': 'new_password',
|
||||
'new_password2': 'new_password'})
|
||||
self.assertRedirects(request, reverse('admin2:password_change_done'))
|
||||
self.client.logout()
|
||||
|
||||
self.assertFalse(self.client.login(username=self.user.username,
|
||||
password='password'))
|
||||
self.assertTrue(self.client.login(username=self.user.username,
|
||||
password='user'))
|
||||
password='new_password'))
|
||||
|
||||
def test_change_password(self):
|
||||
self.client.login(username=self.user.username,
|
||||
|
|
@ -538,8 +540,7 @@ class TestAuthViews(TestCase):
|
|||
|
||||
request = self.client.post(reverse('admin2:password_change',
|
||||
kwargs={'pk': new_user.pk}),
|
||||
{'old_password': 'new_user',
|
||||
'password1': 'new_user_password',
|
||||
{'password1': 'new_user_password',
|
||||
'password2': 'new_user_password'})
|
||||
self.assertRedirects(request, reverse('admin2:password_change_done'))
|
||||
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
|
||||
|
||||
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
|
||||
TEMPLATE_DEBUG = DEBUG
|
||||
|
||||
ADMINS = (
|
||||
# ('Your Name', 'your_email@example.com'),
|
||||
)
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
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 = {
|
||||
'default': {
|
||||
'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:
|
||||
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
|
||||
# although not all choices may be available on all operating systems.
|
||||
# In a Windows environment this must be set to your system time zone.
|
||||
TIME_ZONE = 'America/Chicago'
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'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'
|
||||
|
||||
TIME_ZONE = 'UTC'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_L10N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
SITE_ID = 1
|
||||
|
||||
# If you set this to False, Django will make some optimizations so as not
|
||||
# to load the internationalization machinery.
|
||||
USE_I18N = True
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/1.9/howto/static-files/
|
||||
|
||||
# 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/'
|
||||
|
||||
# Additional locations of static files
|
||||
|
|
@ -73,94 +142,12 @@ STATICFILES_DIRS = (
|
|||
os.path.join(BASE_DIR, "static"),
|
||||
)
|
||||
|
||||
# List of finder classes that know how to find static files in
|
||||
# various locations.
|
||||
STATICFILES_FINDERS = (
|
||||
'django.contrib.staticfiles.finders.FileSystemFinder',
|
||||
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
|
||||
# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
|
||||
)
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
||||
MEDIA_URL = "/media/"
|
||||
|
||||
# Make this unique, and don't share it with anybody.
|
||||
SECRET_KEY = '*ymubzn8p_s7vrm%jsqvr6$qnea_5mcp(ao0z-yh1q0gro!0g1'
|
||||
ADMIN2_THEME_DIRECTORY = "djadmin2theme_default"
|
||||
|
||||
# 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,
|
||||
},
|
||||
}
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
|
||||
'PAGE_SIZE': 10
|
||||
}
|
||||
|
||||
|
||||
ADMIN2_THEME_DIRECTORY = "djadmin2theme_default"
|
||||
|
|
@ -1,18 +1,21 @@
|
|||
from django.conf.urls import patterns, include, url
|
||||
from django.contrib import admin
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from blog.views import BlogListView, BlogDetailView
|
||||
from django.conf import settings
|
||||
from django.conf.urls import include, url
|
||||
from django.conf.urls.static import static
|
||||
from django.contrib import admin
|
||||
|
||||
from djadmin2.site import djadmin2_site
|
||||
|
||||
admin.autodiscover()
|
||||
djadmin2_site.autodiscover()
|
||||
|
||||
import djadmin2
|
||||
|
||||
djadmin2.default.autodiscover()
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^admin2/', include(djadmin2.default.urls)),
|
||||
urlpatterns = [
|
||||
url(r'^admin2/', include(djadmin2_site.urls)),
|
||||
url(r'^admin/', include(admin.site.urls)),
|
||||
url(r'^blog/', BlogListView.as_view(template_name="blog/blog_list.html"), name='blog_list'),
|
||||
url(r'^blog/detail(?P<pk>\d+)/$', BlogDetailView.as_view(template_name="blog/blog_detail.html"), name='blog_detail'),
|
||||
url(r'^$', BlogListView.as_view(template_name="blog/home.html"), name='home'),
|
||||
)
|
||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,32 +1,16 @@
|
|||
"""
|
||||
WSGI config for example project.
|
||||
|
||||
This module contains the WSGI application used by Django's development server
|
||||
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.
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/1.9/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
# We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks
|
||||
# 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"
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
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()
|
||||
|
||||
# Apply WSGI middleware here.
|
||||
# from helloworld.wsgi import HelloWorldApplication
|
||||
# application = HelloWorldApplication(application)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import division, absolute_import, unicode_literals
|
||||
|
||||
import djadmin2
|
||||
|
||||
from djadmin2.site import djadmin2_site
|
||||
from .models import CaptionedFile, UncaptionedFile
|
||||
|
||||
|
||||
djadmin2.default.register(CaptionedFile)
|
||||
djadmin2.default.register(UncaptionedFile)
|
||||
djadmin2_site.register(CaptionedFile)
|
||||
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
|
||||
class CaptionedFile(models.Model):
|
||||
caption = models.CharField(max_length=200, verbose_name=_('caption'))
|
||||
publication = models.FileField(
|
||||
upload_to='media', verbose_name=_('Uploaded File'))
|
||||
publication = models.FileField(upload_to='captioned-files', verbose_name=_('Uploaded File'))
|
||||
|
||||
def __str__(self):
|
||||
return self.caption
|
||||
|
|
@ -22,11 +21,10 @@ class CaptionedFile(models.Model):
|
|||
|
||||
@python_2_unicode_compatible
|
||||
class UncaptionedFile(models.Model):
|
||||
publication = models.FileField(
|
||||
upload_to='media', verbose_name=_('Uploaded File'))
|
||||
publication = models.FileField(upload_to='uncaptioned-files', verbose_name=_('Uploaded File'))
|
||||
|
||||
def __str__(self):
|
||||
return self.publication
|
||||
return self.publication.name
|
||||
|
||||
class Meta:
|
||||
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 django.test import TestCase
|
||||
from files.models import CaptionedFile
|
||||
|
||||
|
||||
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.core.urlresolvers import reverse
|
||||
from django.test import TestCase, Client
|
||||
from django.utils import timezone
|
||||
from django.utils.encoding import force_text
|
||||
|
||||
from ..models import CaptionedFile
|
||||
|
||||
from os import path
|
||||
|
||||
fixture_dir = path.join(path.abspath(path.dirname(__file__)), 'fixtures')
|
||||
fixture_file = path.join(fixture_dir, 'pubtest.txt')
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
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
|
||||
|
||||
|
||||
class ChoiceInline(djadmin2.Admin2TabularInline):
|
||||
class ChoiceInline(Admin2TabularInline):
|
||||
model = Choice
|
||||
extra = 3
|
||||
|
||||
|
||||
class PollAdmin(djadmin2.ModelAdmin2):
|
||||
class PollAdmin(ModelAdmin2):
|
||||
fieldsets = [
|
||||
(None, {'fields': ['question']}),
|
||||
('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),
|
||||
|
|
@ -23,4 +23,4 @@ class PollAdmin(djadmin2.ModelAdmin2):
|
|||
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
|
||||
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
from django.test import TestCase
|
||||
from django.utils import timezone
|
||||
|
||||
from polls.models import Poll
|
||||
from polls.models import Choice
|
||||
from polls.models import Poll
|
||||
|
||||
|
||||
class PollTestCase(TestCase):
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
django-extra-views>=0.6.5
|
||||
django-braces>=1.3.0
|
||||
djangorestframework<=2.4.4
|
||||
django-floppyforms<=1.2
|
||||
django-filter<0.12.0
|
||||
djangorestframework>=3.3.3
|
||||
django-floppyforms>=1.6.2
|
||||
django-filter>=0.13.0
|
||||
django-crispy-forms>=1.3.2
|
||||
django-debug-toolbar>=0.9.4
|
||||
pytz==2014.7
|
||||
future>=0.15.2
|
||||
pytz==2016.4
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
-rrequirements.txt
|
||||
flake8==2.5.4
|
||||
pytest
|
||||
pytest-django
|
||||
11
setup.py
11
setup.py
|
|
@ -127,14 +127,15 @@ setup(
|
|||
include_package_data=True,
|
||||
#test_suite='runtests.runtests',
|
||||
install_requires=[
|
||||
'django>=1.6.0',
|
||||
'django>=1.8.0',
|
||||
'django-extra-views>=0.6.5',
|
||||
'django-braces>=1.3.0',
|
||||
'djangorestframework<=2.4.4',
|
||||
'django-floppyforms<=1.2',
|
||||
'django-filter<0.12.0',
|
||||
'djangorestframework>=3.3.3',
|
||||
'django-floppyforms>=1.6.2',
|
||||
'django-filter>=0.13.0',
|
||||
'django-crispy-forms>=1.3.2',
|
||||
'pytz==2014.7'
|
||||
'pytz==2014.7',
|
||||
'future>=0.15.2',
|
||||
],
|
||||
extras_require={
|
||||
'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]
|
||||
envlist = py27-dj1.6.x, py33-dj1.6.x, py34-dj1.6.x, pypy-dj1.6.x,
|
||||
pypy3-dj1.6.x,
|
||||
py27-dj1.7.x, py33-dj1.7.x, py34-dj1.7.x, pypy-dj1.7.x,
|
||||
pypy3-dj1.7.x,
|
||||
py27-dj1.8.x, py33-dj1.8.x, py34-dj1.8.x, pypy-dj1.8.x,
|
||||
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
|
||||
envlist =
|
||||
py27-{1.8,1.9,master},
|
||||
py33-{1.8},
|
||||
py34-{1.8,1.9,master},
|
||||
py35-{1.8,1.9,master},
|
||||
|
||||
[testenv]
|
||||
commands = py.test []
|
||||
deps = -rrequirements_test.txt
|
||||
commands =
|
||||
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=
|
||||
DJANGO_SETTINGS_MODULE = example.settings
|
||||
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