Merge pull request #168 from jaap3/modernize

Remove fallbacks for Django < 1.8
master
Brandon Taylor 2017-03-21 06:38:17 -04:00 committed by GitHub
commit 2ec2722c67
12 changed files with 105 additions and 369 deletions

View File

@ -3,24 +3,11 @@ import json
from django import VERSION from django import VERSION
from django.conf import settings from django.conf import settings
try:
from django.conf.urls import url from django.conf.urls import url
except ImportError:
# Django < 1.4
from django.conf.urls.defaults import url
from django.contrib.admin import ModelAdmin, TabularInline, StackedInline from django.contrib.admin import ModelAdmin, TabularInline, StackedInline
from django.contrib.admin.options import InlineModelAdmin from django.contrib.admin.options import InlineModelAdmin
try:
from django.contrib.contenttypes.admin import (GenericStackedInline, from django.contrib.contenttypes.admin import (GenericStackedInline,
GenericTabularInline) GenericTabularInline)
except:
# Django < 1.7
from django.contrib.contenttypes.generic import (GenericStackedInline,
GenericTabularInline)
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.http import HttpResponse, Http404 from django.http import HttpResponse, Http404
@ -51,13 +38,7 @@ class SortableAdminBase(object):
its sort order can be changed. This view adds a link to the its sort order can be changed. This view adds a link to the
object_tools block to take people to the view to change the sorting. object_tools block to take people to the view to change the sorting.
""" """
if get_is_sortable(self.get_queryset(request)):
try:
qs_method = getattr(self, 'get_queryset', self.queryset)
except AttributeError:
qs_method = self.get_queryset
if get_is_sortable(qs_method(request)):
self.change_list_template = \ self.change_list_template = \
self.sortable_change_list_with_sort_link_template self.sortable_change_list_with_sort_link_template
self.is_sortable = True self.is_sortable = True
@ -101,12 +82,7 @@ class SortableAdmin(SortableAdminBase, ModelAdmin):
def get_urls(self): def get_urls(self):
urls = super(SortableAdmin, self).get_urls() urls = super(SortableAdmin, self).get_urls()
opts = self.model._meta info = self.model._meta.app_label, self.model._meta.model_name
try:
info = opts.app_label, opts.model_name
except AttributeError:
# Django < 1.7
info = opts.app_label, opts.model_name
# this ajax view changes the order of instances of the model type # this ajax view changes the order of instances of the model type
admin_do_sorting_url = url( admin_do_sorting_url = url(
@ -150,11 +126,7 @@ class SortableAdmin(SortableAdminBase, ModelAdmin):
pass pass
# Apply any sort filters to create a subset of sortable objects # Apply any sort filters to create a subset of sortable objects
try: objects = self.get_queryset(request).filter(**filters)
qs_method = getattr(self, 'get_queryset', self.queryset)
except AttributeError:
qs_method = self.get_queryset
objects = qs_method(request).filter(**filters)
# Determine if we need to regroup objects relative to a # Determine if we need to regroup objects relative to a
# foreign key specified on the model class that is extending Sortable. # foreign key specified on the model class that is extending Sortable.
@ -169,6 +141,10 @@ class SortableAdmin(SortableAdminBase, ModelAdmin):
for field in self.model._meta.fields: for field in self.model._meta.fields:
if isinstance(field, SortableForeignKey): if isinstance(field, SortableForeignKey):
try:
sortable_by_fk = field.remote_field.model
except AttributeError:
# Django < 1.9
sortable_by_fk = field.rel.to sortable_by_fk = field.rel.to
sortable_by_field_name = field.name.lower() sortable_by_field_name = field.name.lower()
sortable_by_class_is_sortable = sortable_by_fk.objects.count() >= 2 sortable_by_class_is_sortable = sortable_by_fk.objects.count() >= 2
@ -206,9 +182,6 @@ class SortableAdmin(SortableAdminBase, ModelAdmin):
try: try:
order_field_name = opts.model._meta.ordering[0] order_field_name = opts.model._meta.ordering[0]
except (AttributeError, IndexError):
# for Django 1.5.x
order_field_name = opts.ordering[0]
except (AttributeError, IndexError): except (AttributeError, IndexError):
order_field_name = 'order' order_field_name = 'order'
@ -328,27 +301,18 @@ class SortableInlineBase(SortableAdminBase, InlineModelAdmin):
' (or Sortable for legacy implementations)') ' (or Sortable for legacy implementations)')
def get_queryset(self, request): def get_queryset(self, request):
if VERSION < (1, 6):
qs = super(SortableInlineBase, self).queryset(request)
else:
qs = super(SortableInlineBase, self).get_queryset(request) qs = super(SortableInlineBase, self).get_queryset(request)
if get_is_sortable(qs): if get_is_sortable(qs):
self.model.is_sortable = True self.model.is_sortable = True
else: else:
self.model.is_sortable = False self.model.is_sortable = False
return qs return qs
if VERSION < (1, 6):
queryset = get_queryset
class SortableTabularInline(TabularInline, SortableInlineBase): class SortableTabularInline(TabularInline, SortableInlineBase):
"""Custom template that enables sorting for tabular inlines""" """Custom template that enables sorting for tabular inlines"""
if VERSION >= (1, 10): if VERSION >= (1, 10):
template = 'adminsortable/edit_inline/tabular-1.10.x.html' template = 'adminsortable/edit_inline/tabular-1.10.x.html'
elif VERSION < (1, 6):
template = 'adminsortable/edit_inline/tabular-1.5.x.html'
else: else:
template = 'adminsortable/edit_inline/tabular.html' template = 'adminsortable/edit_inline/tabular.html'
@ -357,8 +321,6 @@ class SortableStackedInline(StackedInline, SortableInlineBase):
"""Custom template that enables sorting for stacked inlines""" """Custom template that enables sorting for stacked inlines"""
if VERSION >= (1, 10): if VERSION >= (1, 10):
template = 'adminsortable/edit_inline/stacked-1.10.x.html' template = 'adminsortable/edit_inline/stacked-1.10.x.html'
elif VERSION < (1, 6):
template = 'adminsortable/edit_inline/stacked-1.5.x.html'
else: else:
template = 'adminsortable/edit_inline/stacked.html' template = 'adminsortable/edit_inline/stacked.html'
@ -367,8 +329,6 @@ class SortableGenericTabularInline(GenericTabularInline, SortableInlineBase):
"""Custom template that enables sorting for tabular inlines""" """Custom template that enables sorting for tabular inlines"""
if VERSION >= (1, 10): if VERSION >= (1, 10):
template = 'adminsortable/edit_inline/tabular-1.10.x.html' template = 'adminsortable/edit_inline/tabular-1.10.x.html'
elif VERSION < (1, 6):
template = 'adminsortable/edit_inline/tabular-1.5.x.html'
else: else:
template = 'adminsortable/edit_inline/tabular.html' template = 'adminsortable/edit_inline/tabular.html'
@ -377,7 +337,5 @@ class SortableGenericStackedInline(GenericStackedInline, SortableInlineBase):
"""Custom template that enables sorting for stacked inlines""" """Custom template that enables sorting for stacked inlines"""
if VERSION >= (1, 10): if VERSION >= (1, 10):
template = 'adminsortable/edit_inline/stacked-1.10.x.html' template = 'adminsortable/edit_inline/stacked-1.10.x.html'
elif VERSION < (1, 6):
template = 'adminsortable/edit_inline/stacked-1.5.x.html'
else: else:
template = 'adminsortable/edit_inline/stacked.html' template = 'adminsortable/edit_inline/stacked.html'

View File

@ -7,14 +7,4 @@ class SortableForeignKey(ForeignKey):
This field replaces previous functionality where `sortable_by` was This field replaces previous functionality where `sortable_by` was
defined as a model property that specified another model class. defined as a model property that specified another model class.
""" """
def south_field_triple(self):
try:
from south.modelsinspector import introspector
cls_name = '{0}.{1}'.format(
self.__class__.__module__,
self.__class__.__name__)
args, kwargs = introspector(self)
return cls_name, args, kwargs
except ImportError:
pass pass

View File

@ -1,92 +0,0 @@
{% load i18n admin_modify adminsortable_tags admin_urls %}
{% load static from staticfiles %}
<div class="inline-group" id="{{ inline_admin_formset.formset.prefix }}-group">
<h2>{{ inline_admin_formset.opts.verbose_name_plural|title }} {% if inline_admin_formset.formset.initial_form_count > 1 %} - {% trans "drag and drop to change order" %}{% endif %}</h2>
{{ inline_admin_formset.formset.management_form }}
{{ inline_admin_formset.formset.non_form_errors }}
{% for inline_admin_form in inline_admin_formset %}<div class="inline-related{% if inline_admin_form.original %} has_original{% endif %}{% if forloop.last %} empty-form last-related{% endif %}" id="{{ inline_admin_formset.formset.prefix }}-{% if not forloop.last %}{{ forloop.counter0 }}{% else %}empty{% endif %}">
<h3>
{% if inline_admin_form.original %}
{% with initial_forms_count=inline_admin_formset.formset.management_form.INITIAL_FORMS.value %}
<i class="fa fa-{% if forloop.first %}sort-desc{% elif forloop.counter == initial_forms_count %}sort-asc{% else %}sort{% endif %}"></i>
{% endwith %}
{% endif %}
<b>{{ inline_admin_formset.opts.verbose_name|title }}:</b>&nbsp;<span class="inline_label">{% if inline_admin_form.original %}{{ inline_admin_form.original }}{% else %}#{{ forloop.counter }}{% endif %}</span>
{% if inline_admin_form.show_url %}<a href="../../../r/{{ inline_admin_form.original_content_type_id }}/{{ inline_admin_form.original.id }}/">{% trans "View on site" %}</a>{% endif %}
{% if inline_admin_formset.formset.can_delete and inline_admin_form.original %}<span class="delete">{{ inline_admin_form.deletion_field.field }} {{ inline_admin_form.deletion_field.label_tag }}</span>{% endif %}
</h3>
{% if inline_admin_form.form.non_field_errors %}{{ inline_admin_form.form.non_field_errors }}{% endif %}
{% for fieldset in inline_admin_form %}
{% include "admin/includes/fieldset.html" %}
{% endfor %}
{% if inline_admin_form.has_auto_field %}{{ inline_admin_form.pk_field.field }}{% endif %}
{{ inline_admin_form.fk_field.field }}
{% if inline_admin_form.original %}
<input type="hidden" name="admin_sorting_url" value="{% url opts|admin_urlname:'do_sorting' inline_admin_form.original.model_type_id %}" />
{% endif %}
</div>{% endfor %}
</div>
<script type="text/javascript">
(function($) {
$(document).ready(function() {
var rows = "#{{ inline_admin_formset.formset.prefix }}-group .inline-related";
var updateInlineLabel = function(row) {
$(rows).find(".inline_label").each(function(i) {
var count = i + 1;
$(this).html($(this).html().replace(/(#\d+)/g, "#" + count));
});
}
var reinitDateTimeShortCuts = function() {
// Reinitialize the calendar and clock widgets by force, yuck.
if (typeof DateTimeShortcuts != "undefined") {
$(".datetimeshortcuts").remove();
DateTimeShortcuts.init();
}
}
var updateSelectFilter = function() {
// If any SelectFilter widgets were added, instantiate a new instance.
if (typeof SelectFilter != "undefined"){
$(".selectfilter").each(function(index, value){
var namearr = value.name.split('-');
SelectFilter.init(value.id, namearr[namearr.length-1], false, "{% static 'admin/' %}");
});
$(".selectfilterstacked").each(function(index, value){
var namearr = value.name.split('-');
SelectFilter.init(value.id, namearr[namearr.length-1], true, "{% static 'admin/' %}");
});
}
}
var initPrepopulatedFields = function(row) {
row.find('.prepopulated_field').each(function() {
var field = $(this);
var input = field.find('input, select, textarea');
var dependency_list = input.data('dependency_list') || [];
var dependencies = [];
$.each(dependency_list, function(i, field_name) {
dependencies.push('#' + row.find(field_name).find('input, select, textarea').attr('id'));
});
if (dependencies.length) {
input.prepopulate(dependencies, input.attr('maxlength'));
}
});
}
$(rows).formset({
prefix: "{{ inline_admin_formset.formset.prefix }}",
addText: "{% blocktrans with inline_admin_formset.opts.verbose_name|title as verbose_name %}Add another {{ verbose_name }}{% endblocktrans %}",
formCssClass: "dynamic-{{ inline_admin_formset.formset.prefix }}",
deleteCssClass: "inline-deletelink",
deleteText: "{% trans "Remove" %}",
emptyCssClass: "empty-form",
removed: updateInlineLabel,
added: (function(row) {
initPrepopulatedFields(row);
reinitDateTimeShortCuts();
updateSelectFilter();
updateInlineLabel(row);
})
});
});
})(django.jQuery);
</script>

View File

@ -1,136 +0,0 @@
{% load i18n admin_modify adminsortable_tags admin_urls %}
{% load static from staticfiles %}
<div class="inline-group" id="{{ inline_admin_formset.formset.prefix }}-group">
<div class="tabular inline-related {% if forloop.last %}last-related{% endif %}">
{{ inline_admin_formset.formset.management_form }}
<fieldset class="module">
<h2>{{ inline_admin_formset.opts.verbose_name_plural|capfirst }} {% if inline_admin_formset.formset.initial_form_count > 1 %} - {% trans "drag and drop to change order" %}{% endif %}</h2>
{{ inline_admin_formset.formset.non_form_errors }}
<table>
<thead><tr>
{% for field in inline_admin_formset.fields %}
{% if not field.widget.is_hidden %}
<th{% if forloop.first %} colspan="2"{% endif %}{% if field.required %} class="required"{% endif %}>{{ field.label|capfirst }}</th>
{% endif %}
{% endfor %}
{% if inline_admin_formset.formset.can_delete %}<th>{% trans "Delete?" %}</th>{% endif %}
</tr></thead>
<tbody>
{% for inline_admin_form in inline_admin_formset %}
{% if inline_admin_form.form.non_field_errors %}
<tr><td colspan="{{ inline_admin_form|cell_count }}">{{ inline_admin_form.form.non_field_errors }}</td></tr>
{% endif %}
<tr class="{% cycle "row1" "row2" %} {% if inline_admin_form.original or inline_admin_form.show_url %}has_original{% endif %}{% if forloop.last %} empty-form{% endif %}"
id="{{ inline_admin_formset.formset.prefix }}-{% if not forloop.last %}{{ forloop.counter0 }}{% else %}empty{% endif %}">
<td class="original">
{% if inline_admin_form.original or inline_admin_form.show_url %}<p>
{% with initial_forms_count=inline_admin_form.formset.management_form.INITIAL_FORMS.value %}
<i class="fa fa-{% if forloop.first %}sort-desc{% elif forloop.counter == initial_forms_count %}sort-asc{% else %}sort{% endif %}"></i>
{% endwith %}
{% if inline_admin_form.original %} {{ inline_admin_form.original }}{% endif %}
{% if inline_admin_form.show_url %}<a href="../../../r/{{ inline_admin_form.original_content_type_id }}/{{ inline_admin_form.original.id }}/">{% trans "View on site" %}</a>{% endif %}
</p>{% endif %}
{% if inline_admin_form.has_auto_field %}{{ inline_admin_form.pk_field.field }}{% endif %}
{{ inline_admin_form.fk_field.field }}
{% spaceless %}
{% for fieldset in inline_admin_form %}
{% for line in fieldset %}
{% for field in line %}
{% if field.is_hidden %} {{ field.field }} {% endif %}
{% endfor %}
{% endfor %}
{% endfor %}
{% endspaceless %}
{% if inline_admin_form.original %}
<input type="hidden" name="admin_sorting_url" value="{% url opts|admin_urlname:'do_sorting' inline_admin_form.original.model_type_id %}" />
{% endif %}
</td>
{% for fieldset in inline_admin_form %}
{% for line in fieldset %}
{% for field in line %}
<td class="{{ field.field.name }}">
{% if field.is_readonly %}
<p>{{ field.contents }}</p>
{% else %}
{{ field.field.errors.as_ul }}
{{ field.field }}
{% endif %}
</td>
{% endfor %}
{% endfor %}
{% endfor %}
{% if inline_admin_formset.formset.can_delete %}
<td class="delete">{% if inline_admin_form.original %}{{ inline_admin_form.deletion_field.field }}{% endif %}</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</fieldset>
</div>
</div>
<script type="text/javascript">
(function($) {
$(document).ready(function($) {
var rows = "#{{ inline_admin_formset.formset.prefix }}-group .tabular.inline-related tbody tr";
var alternatingRows = function(row) {
$(rows).not(".add-row").removeClass("row1 row2")
.filter(":even").addClass("row1").end()
.filter(rows + ":odd").addClass("row2");
}
var reinitDateTimeShortCuts = function() {
// Reinitialize the calendar and clock widgets by force
if (typeof DateTimeShortcuts != "undefined") {
$(".datetimeshortcuts").remove();
DateTimeShortcuts.init();
}
}
var updateSelectFilter = function() {
// If any SelectFilter widgets are a part of the new form,
// instantiate a new SelectFilter instance for it.
if (typeof SelectFilter != "undefined"){
$(".selectfilter").each(function(index, value){
var namearr = value.name.split('-');
SelectFilter.init(value.id, namearr[namearr.length-1], false, "{% static 'admin/' %}");
});
$(".selectfilterstacked").each(function(index, value){
var namearr = value.name.split('-');
SelectFilter.init(value.id, namearr[namearr.length-1], true, "{% static 'admin/' %}");
});
}
}
var initPrepopulatedFields = function(row) {
row.find('.prepopulated_field').each(function() {
var field = $(this);
var input = field.find('input, select, textarea');
var dependency_list = input.data('dependency_list') || [];
var dependencies = [];
$.each(dependency_list, function(i, field_name) {
dependencies.push('#' + row.find(field_name).find('input, select, textarea').attr('id'));
});
if (dependencies.length) {
input.prepopulate(dependencies, input.attr('maxlength'));
}
});
}
$(rows).formset({
prefix: "{{ inline_admin_formset.formset.prefix }}",
addText: "{% blocktrans with inline_admin_formset.opts.verbose_name|title as verbose_name %}Add another {{ verbose_name }}{% endblocktrans %}",
formCssClass: "dynamic-{{ inline_admin_formset.formset.prefix }}",
deleteCssClass: "inline-deletelink",
deleteText: "{% trans "Remove" %}",
emptyCssClass: "empty-form",
removed: alternatingRows,
added: (function(row) {
initPrepopulatedFields(row);
reinitDateTimeShortCuts();
updateSelectFilter();
alternatingRows(row);
})
});
});
})(django.jQuery);
</script>

View File

@ -2,11 +2,6 @@ from itertools import groupby
import django import django
from django import template from django import template
try:
from django import TemplateSyntaxError
except ImportError:
#support for django 1.3
from django.template.base import TemplateSyntaxError
register = template.Library() register = template.Library()
@ -64,14 +59,15 @@ def dynamic_regroup(parser, token):
""" """
firstbits = token.contents.split(None, 3) firstbits = token.contents.split(None, 3)
if len(firstbits) != 4: if len(firstbits) != 4:
raise TemplateSyntaxError("'regroup' tag takes five arguments") raise template.TemplateSyntaxError("'regroup' tag takes five arguments")
target = parser.compile_filter(firstbits[1]) target = parser.compile_filter(firstbits[1])
if firstbits[2] != 'by': if firstbits[2] != 'by':
raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'") raise template.TemplateSyntaxError(
"second argument to 'regroup' tag must be 'by'")
lastbits_reversed = firstbits[3][::-1].split(None, 2) lastbits_reversed = firstbits[3][::-1].split(None, 2)
if lastbits_reversed[1][::-1] != 'as': if lastbits_reversed[1][::-1] != 'as':
raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must" raise template.TemplateSyntaxError(
" be 'as'") "next-to-last argument to 'regroup' tag must be 'as'")
expression = lastbits_reversed[2][::-1] expression = lastbits_reversed[2][::-1]
var_name = lastbits_reversed[0][::-1] var_name = lastbits_reversed[0][::-1]

View File

@ -26,8 +26,8 @@ class ComponentInline(SortableStackedInline):
# ) # )
model = Component model = Component
def queryset(self, request): def get_queryset(self, request):
qs = super(ComponentInline, self).queryset( qs = super(ComponentInline, self).get_queryset(
request).exclude(title__icontains='2') request).exclude(title__icontains='2')
if get_is_sortable(qs): if get_is_sortable(qs):
self.model.is_sortable = True self.model.is_sortable = True
@ -37,14 +37,14 @@ class ComponentInline(SortableStackedInline):
class WidgetAdmin(SortableAdmin): class WidgetAdmin(SortableAdmin):
def queryset(self, request): def get_queryset(self, request):
""" """
A simple example demonstrating that adminsortable works even in A simple example demonstrating that adminsortable works even in
situations where you need to filter the queryset in admin. Here, situations where you need to filter the queryset in admin. Here,
we are just filtering out `widget` instances with an pk higher we are just filtering out `widget` instances with an pk higher
than 3 than 3
""" """
qs = super(WidgetAdmin, self).queryset(request) qs = super(WidgetAdmin, self).get_queryset(request)
return qs.filter(id__lte=3) return qs.filter(id__lte=3)
inlines = [ComponentInline] inlines = [ComponentInline]

View File

@ -1,10 +1,4 @@
from django import VERSION
if VERSION < (1, 9):
from django.contrib.contenttypes.generic import GenericForeignKey
else:
from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.db import models from django.db import models
from django.utils.encoding import python_2_unicode_compatible from django.utils.encoding import python_2_unicode_compatible
@ -51,7 +45,7 @@ class Project(SimpleModel, SortableMixin):
class Meta: class Meta:
ordering = ['order'] ordering = ['order']
category = SortableForeignKey(Category) category = SortableForeignKey(Category, on_delete=models.CASCADE)
description = models.TextField() description = models.TextField()
order = models.PositiveIntegerField(default=0, editable=False) order = models.PositiveIntegerField(default=0, editable=False)
@ -63,7 +57,7 @@ class Credit(SortableMixin):
class Meta: class Meta:
ordering = ['order'] ordering = ['order']
project = models.ForeignKey(Project) project = models.ForeignKey(Project, on_delete=models.CASCADE)
first_name = models.CharField(max_length=30, help_text="Given name") first_name = models.CharField(max_length=30, help_text="Given name")
last_name = models.CharField(max_length=30, help_text="Family name") last_name = models.CharField(max_length=30, help_text="Family name")
@ -79,7 +73,7 @@ class Note(SortableMixin):
class Meta: class Meta:
ordering = ['order'] ordering = ['order']
project = models.ForeignKey(Project) project = models.ForeignKey(Project, on_delete=models.CASCADE)
text = models.CharField(max_length=100) text = models.CharField(max_length=100)
order = models.PositiveIntegerField(default=0, editable=False) order = models.PositiveIntegerField(default=0, editable=False)
@ -91,7 +85,7 @@ class Note(SortableMixin):
# Registered as a tabular inline on `Project` which can't be sorted # Registered as a tabular inline on `Project` which can't be sorted
@python_2_unicode_compatible @python_2_unicode_compatible
class NonSortableCredit(models.Model): class NonSortableCredit(models.Model):
project = models.ForeignKey(Project) project = models.ForeignKey(Project, on_delete=models.CASCADE)
first_name = models.CharField(max_length=30, help_text="Given name") first_name = models.CharField(max_length=30, help_text="Given name")
last_name = models.CharField(max_length=30, help_text="Family name") last_name = models.CharField(max_length=30, help_text="Family name")
@ -102,7 +96,7 @@ class NonSortableCredit(models.Model):
# Registered as a stacked inline on `Project` which can't be sorted # Registered as a stacked inline on `Project` which can't be sorted
@python_2_unicode_compatible @python_2_unicode_compatible
class NonSortableNote(models.Model): class NonSortableNote(models.Model):
project = models.ForeignKey(Project) project = models.ForeignKey(Project, on_delete=models.CASCADE)
text = models.CharField(max_length=100) text = models.CharField(max_length=100)
def __str__(self): def __str__(self):
@ -112,7 +106,7 @@ class NonSortableNote(models.Model):
# A generic bound model # A generic bound model
@python_2_unicode_compatible @python_2_unicode_compatible
class GenericNote(SimpleModel, SortableMixin): class GenericNote(SimpleModel, SortableMixin):
content_type = models.ForeignKey(ContentType, content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE,
verbose_name=u"Content type", related_name="generic_notes") verbose_name=u"Content type", related_name="generic_notes")
object_id = models.PositiveIntegerField(u"Content id") object_id = models.PositiveIntegerField(u"Content id")
content_object = GenericForeignKey(ct_field='content_type', content_object = GenericForeignKey(ct_field='content_type',
@ -133,7 +127,7 @@ class Component(SimpleModel, SortableMixin):
class Meta: class Meta:
ordering = ['order'] ordering = ['order']
widget = SortableForeignKey(Widget) widget = SortableForeignKey(Widget, on_delete=models.CASCADE)
order = models.PositiveIntegerField(default=0, editable=False) order = models.PositiveIntegerField(default=0, editable=False)
@ -183,7 +177,8 @@ class SortableCategoryWidget(SimpleModel, SortableMixin):
verbose_name = 'Sortable Category Widget' verbose_name = 'Sortable Category Widget'
verbose_name_plural = 'Sortable Category Widgets' verbose_name_plural = 'Sortable Category Widgets'
non_sortable_category = SortableForeignKey(NonSortableCategory) non_sortable_category = SortableForeignKey(
NonSortableCategory, on_delete=models.CASCADE)
order = models.PositiveIntegerField(default=0, editable=False) order = models.PositiveIntegerField(default=0, editable=False)
@ -197,7 +192,8 @@ class SortableNonInlineCategory(SimpleModel, SortableMixin):
that is *not* sortable, and is also not defined as an inline of the that is *not* sortable, and is also not defined as an inline of the
SortableForeignKey field.""" SortableForeignKey field."""
non_sortable_category = SortableForeignKey(NonSortableCategory) non_sortable_category = SortableForeignKey(
NonSortableCategory, on_delete=models.CASCADE)
order = models.PositiveIntegerField(default=0, editable=False) order = models.PositiveIntegerField(default=0, editable=False)
@ -229,7 +225,7 @@ class CustomWidget(SortableMixin, SimpleModel):
@python_2_unicode_compatible @python_2_unicode_compatible
class CustomWidgetComponent(SortableMixin, SimpleModel): class CustomWidgetComponent(SortableMixin, SimpleModel):
custom_widget = models.ForeignKey(CustomWidget) custom_widget = models.ForeignKey(CustomWidget, on_delete=models.CASCADE)
# custom field for ordering # custom field for ordering
widget_order = models.PositiveIntegerField(default=0, db_index=True, widget_order = models.PositiveIntegerField(default=0, db_index=True,

View File

@ -1,16 +1,13 @@
try: try:
import httplib import httplib # Python 2
except ImportError: except ImportError:
import http.client as httplib import http.client as httplib # Python 3
from django import VERSION
if VERSION > (1, 8):
import uuid
import json import json
import uuid
import django
from django import VERSION
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db import models from django.db import models
from django.test import TestCase from django.test import TestCase
@ -33,7 +30,6 @@ class TestSortableModel(SortableMixin):
return self.title return self.title
if VERSION > (1, 8):
class TestNonAutoFieldModel(SortableMixin): class TestNonAutoFieldModel(SortableMixin):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
order = models.PositiveIntegerField(editable=False, db_index=True) order = models.PositiveIntegerField(editable=False, db_index=True)
@ -79,8 +75,12 @@ class SortableTestCase(TestCase):
return category return category
def test_new_user_is_authenticated(self): def test_new_user_is_authenticated(self):
if django.VERSION < (1, 10):
self.assertEqual(self.user.is_authenticated(), True, self.assertEqual(self.user.is_authenticated(), True,
'User is not authenticated') 'User is not authenticated')
else:
self.assertEqual(self.user.is_authenticated, True,
'User is not authenticated')
def test_new_user_is_staff(self): def test_new_user_is_staff(self):
self.assertEqual(self.user.is_staff, True, 'User is not staff') self.assertEqual(self.user.is_staff, True, 'User is not staff')
@ -114,7 +114,7 @@ class SortableTestCase(TestCase):
self.client.login(username=self.user.username, self.client.login(username=self.user.username,
password=self.user_raw_password) password=self.user_raw_password)
response = self.client.get('/admin/app/category/sort/') response = self.client.get('/admin/app/category/sort/')
self.assertEquals(response.status_code, httplib.OK, self.assertEqual(response.status_code, httplib.OK,
'Unable to reach sort view.') 'Unable to reach sort view.')
def make_test_categories(self): def make_test_categories(self):
@ -257,7 +257,7 @@ class SortableTestCase(TestCase):
self.client.login(username=self.user.username, self.client.login(username=self.user.username,
password=self.user_raw_password) password=self.user_raw_password)
response = self.client.get('/admin/app/project/sort/') response = self.client.get('/admin/app/project/sort/')
self.assertEquals(response.status_code, httplib.OK, self.assertEqual(response.status_code, httplib.OK,
'Unable to reach sort view.') 'Unable to reach sort view.')
def test_adminsortable_change_list_view_permission_denied(self): def test_adminsortable_change_list_view_permission_denied(self):
@ -267,7 +267,7 @@ class SortableTestCase(TestCase):
self.client.login(username=self.staff.username, self.client.login(username=self.staff.username,
password=self.staff_raw_password) password=self.staff_raw_password)
response = self.client.get('/admin/app/project/sort/') response = self.client.get('/admin/app/project/sort/')
self.assertEquals(response.status_code, httplib.FORBIDDEN, self.assertEqual(response.status_code, httplib.FORBIDDEN,
'Sort view must be forbidden.') 'Sort view must be forbidden.')
def test_adminsortable_inline_changelist_success(self): def test_adminsortable_inline_changelist_success(self):
@ -317,8 +317,5 @@ class SortableTestCase(TestCase):
self.assertEqual(notes, expected_notes) self.assertEqual(notes, expected_notes)
def test_save_non_auto_field_model(self): def test_save_non_auto_field_model(self):
if VERSION > (1, 8):
model = TestNonAutoFieldModel() model = TestNonAutoFieldModel()
model.save() model.save()
else:
pass

View File

@ -1,6 +1,8 @@
# Django settings for test_project project. # Django settings for test_project project.
import os import os
import django
def map_path(directory_name): def map_path(directory_name):
return os.path.join(os.path.dirname(__file__), return os.path.join(os.path.dirname(__file__),
@ -8,7 +10,6 @@ def map_path(directory_name):
DEBUG = True DEBUG = True
TEMPLATE_DEBUG = DEBUG
ADMINS = ( ADMINS = (
# ('Your Name', 'your_email@example.com'), # ('Your Name', 'your_email@example.com'),
@ -91,35 +92,31 @@ STATICFILES_FINDERS = (
# Make this unique, and don't share it with anybody. # Make this unique, and don't share it with anybody.
SECRET_KEY = '8**a!c8$1x)p@j2pj0yq!*v+dzp24g*$918ws#x@k+gf%0%rct' SECRET_KEY = '8**a!c8$1x)p@j2pj0yq!*v+dzp24g*$918ws#x@k+gf%0%rct'
# List of callables that know how to import templates from various sources. MIDDLEWARE = [
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
)
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
# Uncomment the next line for simple clickjacking protection: # Uncomment the next line for simple clickjacking protection:
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
) ]
if django.VERSION < (1, 10):
MIDDLEWARE_CLASSES = MIDDLEWARE
ROOT_URLCONF = 'sample_project.urls' ROOT_URLCONF = 'sample_project.urls'
# Python dotted path to the WSGI application used by Django's runserver. # Python dotted path to the WSGI application used by Django's runserver.
WSGI_APPLICATION = 'sample_project.wsgi.application' WSGI_APPLICATION = 'sample_project.wsgi.application'
TEMPLATE_DIRS = (
map_path('templates'),
)
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', 'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': TEMPLATE_DIRS, 'DIRS': [
map_path('templates')
],
'APP_DIRS': True, 'APP_DIRS': True,
'OPTIONS': { 'OPTIONS': {
'context_processors': [ 'context_processors': [

View File

@ -14,5 +14,5 @@ urlpatterns = [
url(r'^admin/doc/', include('django.contrib.admindocs.urls')), url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
# Uncomment the next line to enable the admin: # Uncomment the next line to enable the admin:
url(r'^admin/', include(admin.site.urls)), url(r'^admin/', admin.site.urls),
] ]

View File

@ -1,6 +0,0 @@
import os
def map_path(directory_name):
return os.path.join(os.path.dirname(__file__),
'../' + directory_name).replace('\\', '/')

36
tox.ini 100644
View File

@ -0,0 +1,36 @@
[tox]
envlist = django{1.8,1.9,1.10,1.11}-{py27,py34,py35},coverage
[testenv]
deps =
coverage
django1.8: Django>=1.8,<1.9
django1.9: Django>=1.9,<1.10
django1.10: Django>=1.10,<1.11
django1.11: Django>=1.11a1,<1.12
whitelist_externals = cd
setenv =
PYTHONPATH = {toxinidir}/sample_project
PYTHONWARNINGS = module
PYTHONDONTWRITEBYTECODE = 1
commands =
coverage run -p sample_project/manage.py test app
[testenv:coverage]
deps = coverage
skip_install = true
commands =
coverage combine
coverage report
coverage html
[coverage:run]
branch = True
parallel = True
source =
adminsortable
sample_project
[coverage:report]
exclude_lines =
if __name__ == .__main__.: