Added support for queryset() overrides on admin classes and inline admin classes.

Updated version to 1.4.5.
Updated README with explanation of requirements for overriding queryset() on inline models.
Added extra models to sample project to demonstrate sortable models with custom querysets.
Improved JavaScript of sortables to be more efficient with better comparison checking.
Fixed highlighting of stacked inlines on sort finish.
master
Brandon Taylor 2013-04-27 22:58:02 -04:00
parent b6e68fa367
commit 014f6d1660
16 changed files with 477 additions and 68 deletions

View File

@ -137,6 +137,47 @@ There are also generic equivalents that you can inherit from:
"""Your generic inline options go here""" """Your generic inline options go here"""
### Overriding `queryset()`
django-admin-sortable now supports custom queryset overrides on admin models
and inline models in Django admin!
If you're providing an override of a SortableAdmin or Sortable inline model,
you don't need to do anything extra. django-admin-sortable will automatically
honor your queryset.
Have a look at the WidgetAdmin class in the sample project for an example of
an admin class with a custom `queryset()` override.
#### Overriding `queryset()` for an inline model
This is a special case, which requires a few lines of extra code to properly
determine the sortability of your model. Example:
# add this import to your admin.py
from adminsortable.utils import get_is_sortable
class ComponentInline(SortableStackedInline):
model = Component
def queryset(self, request):
qs = super(ComponentInline, self).queryset(request).filter(
title__icontains='foo')
# You'll need to add these lines to determine if your model
# is sortable once we hit the change_form() for the parent model.
if get_is_sortable(qs):
self.model.is_sortable = True
else:
self.model.is_sortable = False
return qs
If you override the queryset of an inline, the number of objects present
may change, and adminsortable won't be able to automatically determine
if the inline model is sortable from here, which is why we have to set the
`is_sortable` property of the model in this method.
*** IMPORTANT *** *** IMPORTANT ***
With stacked inline models, their height can dynamically increase, With stacked inline models, their height can dynamically increase,
which can cause sortable stacked inlines to not behave as expected. which can cause sortable stacked inlines to not behave as expected.
@ -162,9 +203,10 @@ ordering on top of that just seemed a little much in my opinion.
django-admin-sortable is currently used in production. django-admin-sortable is currently used in production.
### What's new in 1.4.4? ### What's new in 1.4.5?
- Decided to go with the simplest approach to add the sorting urls to inlines - Support for queryset overrides!
for Django <= 1.4 and Django 1.5.x support - More efficient JavaScript in sortables
- Fixed highlight effect for stacked inlines on sort finish
### Future ### Future

View File

@ -1,4 +1,4 @@
VERSION = (1, 4, 4) # following PEP 386 VERSION = (1, 4, 5) # following PEP 386
DEV_N = None DEV_N = None

View File

@ -21,13 +21,30 @@ from django.shortcuts import render
from django.template.defaultfilters import capfirst from django.template.defaultfilters import capfirst
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from adminsortable.utils import get_is_sortable
from adminsortable.fields import SortableForeignKey from adminsortable.fields import SortableForeignKey
from adminsortable.models import Sortable from adminsortable.models import Sortable
STATIC_URL = settings.STATIC_URL STATIC_URL = settings.STATIC_URL
class SortableAdmin(ModelAdmin): class SortableAdminBase(object):
def changelist_view(self, request, extra_context=None):
"""
If the model that inherits Sortable has more than one object,
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.
"""
if get_is_sortable(self.queryset(request)):
self.change_list_template = \
self.sortable_change_list_with_sort_link_template
self.is_sortable = True
return super(SortableAdminBase, self).changelist_view(request,
extra_context=extra_context)
class SortableAdmin(SortableAdminBase, ModelAdmin):
""" """
Admin class to add template overrides and context objects to enable Admin class to add template overrides and context objects to enable
drag-and-drop ordering. drag-and-drop ordering.
@ -52,18 +69,20 @@ class SortableAdmin(ModelAdmin):
break break
return sortable_foreign_key return sortable_foreign_key
def __init__(self, *args, **kwargs): # def __init__(self, *args, **kwargs):
super(SortableAdmin, self).__init__(*args, **kwargs) # super(SortableAdmin, self).__init__(*args, **kwargs)
self.has_sortable_tabular_inlines = False # self.has_sortable_tabular_inlines = False
self.has_sortable_stacked_inlines = False # self.has_sortable_stacked_inlines = False
for klass in self.inlines: # for klass in self.inlines:
if issubclass(klass, SortableTabularInline): # print type(klass)
if klass.model.is_sortable(): # is_sortable = get_is_sortable(
self.has_sortable_tabular_inlines = True # klass.model._default_manager.get_query_set())
if issubclass(klass, SortableStackedInline): # print is_sortable
if klass.model.is_sortable(): # if issubclass(klass, SortableTabularInline) and is_sortable:
self.has_sortable_stacked_inlines = True # self.has_sortable_tabular_inlines = True
# if issubclass(klass, SortableStackedInline) and is_sortable:
# self.has_sortable_stacked_inlines = True
def get_urls(self): def get_urls(self):
urls = super(SortableAdmin, self).get_urls() urls = super(SortableAdmin, self).get_urls()
@ -88,7 +107,8 @@ class SortableAdmin(ModelAdmin):
opts = self.model._meta opts = self.model._meta
has_perm = request.user.has_perm('{0}.{1}'.format(opts.app_label, has_perm = request.user.has_perm('{0}.{1}'.format(opts.app_label,
opts.get_change_permission())) opts.get_change_permission()))
objects = self.model.objects.all()
objects = self.queryset(request)
# 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.
@ -97,6 +117,7 @@ class SortableAdmin(ModelAdmin):
# `sortable_by` defined as a SortableForeignKey # `sortable_by` defined as a SortableForeignKey
sortable_by_fk = self._get_sortable_foreign_key() sortable_by_fk = self._get_sortable_foreign_key()
sortable_by_class_is_sortable = get_is_sortable(objects)
if sortable_by_property: if sortable_by_property:
# backwards compatibility for < 1.1.1, where sortable_by was a # backwards compatibility for < 1.1.1, where sortable_by was a
@ -110,7 +131,6 @@ class SortableAdmin(ModelAdmin):
sortable_by_class_display_name = sortable_by_class._meta \ sortable_by_class_display_name = sortable_by_class._meta \
.verbose_name_plural .verbose_name_plural
sortable_by_class_is_sortable = sortable_by_class.is_sortable()
elif sortable_by_fk: elif sortable_by_fk:
# get sortable by properties from the SortableForeignKey # get sortable by properties from the SortableForeignKey
@ -119,10 +139,6 @@ class SortableAdmin(ModelAdmin):
._meta.verbose_name_plural ._meta.verbose_name_plural
sortable_by_class = sortable_by_fk.rel.to sortable_by_class = sortable_by_fk.rel.to
sortable_by_expression = sortable_by_fk.name.lower() sortable_by_expression = sortable_by_fk.name.lower()
try:
sortable_by_class_is_sortable = sortable_by_class.is_sortable()
except AttributeError:
sortable_by_class_is_sortable = False
else: else:
# model is not sortable by another model # model is not sortable by another model
@ -132,7 +148,7 @@ class SortableAdmin(ModelAdmin):
if sortable_by_property or sortable_by_fk: if sortable_by_property or sortable_by_fk:
# Order the objects by the property they are sortable by, # Order the objects by the property they are sortable by,
#then by the order, otherwise the regroup # then by the order, otherwise the regroup
# template tag will not show the objects correctly # template tag will not show the objects correctly
objects = objects.order_by(sortable_by_expression, 'order') objects = objects.order_by(sortable_by_expression, 'order')
@ -157,19 +173,17 @@ class SortableAdmin(ModelAdmin):
} }
return render(request, self.sortable_change_list_template, context) return render(request, self.sortable_change_list_template, context)
def changelist_view(self, request, extra_context=None):
"""
If the model that inherits Sortable has more than one object,
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.
"""
if self.model.is_sortable():
self.change_list_template = \
self.sortable_change_list_with_sort_link_template
return super(SortableAdmin, self).changelist_view(request,
extra_context=extra_context)
def change_view(self, request, object_id, extra_context=None): def change_view(self, request, object_id, extra_context=None):
self.has_sortable_tabular_inlines = False
self.has_sortable_stacked_inlines = False
for klass in self.inlines:
is_sortable = klass.model.is_sortable
if issubclass(klass, SortableTabularInline) and is_sortable:
self.has_sortable_tabular_inlines = True
if issubclass(klass, SortableStackedInline) and is_sortable:
self.has_sortable_stacked_inlines = True
if self.has_sortable_tabular_inlines or \ if self.has_sortable_tabular_inlines or \
self.has_sortable_stacked_inlines: self.has_sortable_stacked_inlines:
self.change_form_template = self.sortable_change_form_template self.change_form_template = self.sortable_change_form_template
@ -222,7 +236,7 @@ class SortableAdmin(ModelAdmin):
mimetype='application/json') mimetype='application/json')
class SortableInlineBase(InlineModelAdmin): class SortableInlineBase(SortableAdminBase, InlineModelAdmin):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(SortableInlineBase, self).__init__(*args, **kwargs) super(SortableInlineBase, self).__init__(*args, **kwargs)
@ -230,7 +244,13 @@ class SortableInlineBase(InlineModelAdmin):
raise Warning(u'Models that are specified in SortableTabluarInline' raise Warning(u'Models that are specified in SortableTabluarInline'
' and SortableStackedInline must inherit from Sortable') ' and SortableStackedInline must inherit from Sortable')
self.is_sortable = self.model.is_sortable() def queryset(self, request):
qs = super(SortableInlineBase, self).queryset(request)
if get_is_sortable(qs):
self.model.is_sortable = True
else:
self.model.is_sortable = False
return qs
class SortableTabularInline(SortableInlineBase, TabularInline): class SortableTabularInline(SortableInlineBase, TabularInline):

View File

@ -26,14 +26,11 @@ class Sortable(models.Model):
`save` the override of save increments the last/highest value of `save` the override of save increments the last/highest value of
order by 1 order by 1
Override `sortable_by` method to make your model be sortable by a
foreign key field. Set `sortable_by` to the class specified in the
foreign key relationship.
""" """
order = models.PositiveIntegerField(editable=False, default=1, order = models.PositiveIntegerField(editable=False, default=1,
db_index=True) db_index=True)
is_sortable = False
# legacy support # legacy support
sortable_by = None sortable_by = None
@ -42,14 +39,18 @@ class Sortable(models.Model):
abstract = True abstract = True
ordering = ['order'] ordering = ['order']
@classmethod # @classmethod
def is_sortable(cls): # def determine_if_sortable(cls):
try: # try:
max_order = cls.objects.aggregate( # max_order = cls.objects.aggregate(
models.Max('order'))['order__max'] # models.Max('order'))['order__max']
except (TypeError, IndexError): # except (TypeError, IndexError):
max_order = 0 # max_order = 0
return True if max_order > 1 else False
# if max_order > 1:
# cls.is_sortable = True
# else:
# cls.is_sortable = False
@classmethod @classmethod
def model_type_id(cls): def model_type_id(cls):

View File

@ -6,7 +6,7 @@ jQuery(function($){
items : 'li', items : 'li',
stop : function(event, ui) stop : function(event, ui)
{ {
var indexes = Array(); var indexes = [];
ui.item.parent().children('li').each(function(i) ui.item.parent().children('li').each(function(i)
{ {
indexes.push($(this).find(':hidden[name="pk"]').val()); indexes.push($(this).find(':hidden[name="pk"]').val());
@ -17,5 +17,7 @@ jQuery(function($){
data: { indexes: indexes.join(',') } data: { indexes: indexes.join(',') }
}); });
} }
}).click(function(e){
e.preventDefault();
}); });
}); });

View File

@ -10,12 +10,14 @@ jQuery(function($){
items : '.inline-related', items : '.inline-related',
stop : function(event, ui) stop : function(event, ui)
{ {
var indexes = Array(); var indexes = [];
ui.item.parent().children('.inline-related').each(function(i) ui.item.parent().children('.inline-related').each(function(i)
{ {
index_value = $(this).find(':hidden[name$="-id"]').val(); var index_value = $(this).find(':hidden[name$="-id"]').val();
if (index_value != "" && index_value != undefined) if (index_value !== "" && index_value !== undefined)
{
indexes.push(index_value); indexes.push(index_value);
}
}); });
$.ajax({ $.ajax({
@ -24,7 +26,7 @@ jQuery(function($){
data: { indexes : indexes.join(',') }, data: { indexes : indexes.join(',') },
success: function() success: function()
{ {
ui.item.effect('highlight', {}, 1000); ui.item.find('.form-row').effect('highlight', {}, 1000);
} }
}); });
} }

View File

@ -10,12 +10,14 @@ jQuery(function($){
items : 'tr:not(.add-row)', items : 'tr:not(.add-row)',
stop : function(event, ui) stop : function(event, ui)
{ {
var indexes = Array(); var indexes = [];
ui.item.parent().children('tr').each(function(i) ui.item.parent().children('tr').each(function(i)
{ {
index_value = $(this).find('.original :hidden:first').val(); var index_value = $(this).find('.original :hidden:first').val();
if (index_value != "" && index_value != undefined) if (index_value !== '' && index_value !== undefined)
{
indexes.push(index_value); indexes.push(index_value);
}
}); });
$.ajax({ $.ajax({

View File

@ -0,0 +1,4 @@
def get_is_sortable(objects):
if len(objects) > 1:
return True
return False

View File

@ -2,12 +2,42 @@ from django.contrib import admin
from adminsortable.admin import (SortableAdmin, SortableTabularInline, from adminsortable.admin import (SortableAdmin, SortableTabularInline,
SortableStackedInline, SortableGenericStackedInline) SortableStackedInline, SortableGenericStackedInline)
from app.models import Category, Project, Credit, Note, GenericNote from adminsortable.utils import get_is_sortable
from app.models import (Category, Widget, Project, Credit, Note, GenericNote,
Component)
admin.site.register(Category, SortableAdmin) admin.site.register(Category, SortableAdmin)
class ComponentInline(SortableStackedInline):
model = Component
def queryset(self, request):
qs = super(ComponentInline, self).queryset(request).exclude(title__icontains='2')
if get_is_sortable(qs):
self.model.is_sortable = True
else:
self.model.is_sortable = False
return qs
class WidgetAdmin(SortableAdmin):
def queryset(self, request):
"""
A simple example demonstrating that adminsortable works even in
situations where you need to filter the queryset in admin. Here,
we are just filtering out `widget` instances with an pk higher
than 3
"""
qs = super(WidgetAdmin, self).queryset(request)
return qs.filter(id__lte=3)
inlines = [ComponentInline]
admin.site.register(Widget, WidgetAdmin)
class CreditInline(SortableTabularInline): class CreditInline(SortableTabularInline):
model = Credit model = Credit

View File

@ -0,0 +1,123 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'Category'
db.create_table(u'app_category', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('order', self.gf('django.db.models.fields.PositiveIntegerField')(default=1, db_index=True)),
('title', self.gf('django.db.models.fields.CharField')(max_length=50)),
))
db.send_create_signal(u'app', ['Category'])
# Adding model 'Project'
db.create_table(u'app_project', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('order', self.gf('django.db.models.fields.PositiveIntegerField')(default=1, db_index=True)),
('title', self.gf('django.db.models.fields.CharField')(max_length=50)),
('category', self.gf('adminsortable.fields.SortableForeignKey')(to=orm['app.Category'])),
('description', self.gf('django.db.models.fields.TextField')()),
))
db.send_create_signal(u'app', ['Project'])
# Adding model 'Credit'
db.create_table(u'app_credit', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('order', self.gf('django.db.models.fields.PositiveIntegerField')(default=1, db_index=True)),
('project', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['app.Project'])),
('first_name', self.gf('django.db.models.fields.CharField')(max_length=30)),
('last_name', self.gf('django.db.models.fields.CharField')(max_length=30)),
))
db.send_create_signal(u'app', ['Credit'])
# Adding model 'Note'
db.create_table(u'app_note', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('order', self.gf('django.db.models.fields.PositiveIntegerField')(default=1, db_index=True)),
('project', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['app.Project'])),
('text', self.gf('django.db.models.fields.CharField')(max_length=100)),
))
db.send_create_signal(u'app', ['Note'])
# Adding model 'GenericNote'
db.create_table(u'app_genericnote', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('order', self.gf('django.db.models.fields.PositiveIntegerField')(default=1, db_index=True)),
('title', self.gf('django.db.models.fields.CharField')(max_length=50)),
('content_type', self.gf('django.db.models.fields.related.ForeignKey')(related_name='generic_notes', to=orm['contenttypes.ContentType'])),
('object_id', self.gf('django.db.models.fields.PositiveIntegerField')()),
))
db.send_create_signal(u'app', ['GenericNote'])
def backwards(self, orm):
# Deleting model 'Category'
db.delete_table(u'app_category')
# Deleting model 'Project'
db.delete_table(u'app_project')
# Deleting model 'Credit'
db.delete_table(u'app_credit')
# Deleting model 'Note'
db.delete_table(u'app_note')
# Deleting model 'GenericNote'
db.delete_table(u'app_genericnote')
models = {
u'app.category': {
'Meta': {'ordering': "['order']", 'object_name': 'Category'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'app.credit': {
'Meta': {'ordering': "['order']", 'object_name': 'Credit'},
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30'}),
'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['app.Project']"})
},
u'app.genericnote': {
'Meta': {'ordering': "['order']", 'object_name': 'GenericNote'},
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'generic_notes'", 'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'app.note': {
'Meta': {'ordering': "['order']", 'object_name': 'Note'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['app.Project']"}),
'text': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
u'app.project': {
'Meta': {'ordering': "['order']", 'object_name': 'Project'},
'category': ('adminsortable.fields.SortableForeignKey', [], {'to': u"orm['app.Category']"}),
'description': ('django.db.models.fields.TextField', [], {}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
}
}
complete_apps = ['app']

View File

@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'Widget'
db.create_table(u'app_widget', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('order', self.gf('django.db.models.fields.PositiveIntegerField')(default=1, db_index=True)),
('title', self.gf('django.db.models.fields.CharField')(max_length=50)),
))
db.send_create_signal(u'app', ['Widget'])
def backwards(self, orm):
# Deleting model 'Widget'
db.delete_table(u'app_widget')
models = {
u'app.category': {
'Meta': {'ordering': "['order']", 'object_name': 'Category'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'app.credit': {
'Meta': {'ordering': "['order']", 'object_name': 'Credit'},
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30'}),
'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['app.Project']"})
},
u'app.genericnote': {
'Meta': {'ordering': "['order']", 'object_name': 'GenericNote'},
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'generic_notes'", 'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'app.note': {
'Meta': {'ordering': "['order']", 'object_name': 'Note'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['app.Project']"}),
'text': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
u'app.project': {
'Meta': {'ordering': "['order']", 'object_name': 'Project'},
'category': ('adminsortable.fields.SortableForeignKey', [], {'to': u"orm['app.Category']"}),
'description': ('django.db.models.fields.TextField', [], {}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'app.widget': {
'Meta': {'ordering': "['order']", 'object_name': 'Widget'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
}
}
complete_apps = ['app']

View File

@ -0,0 +1,86 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'Component'
db.create_table(u'app_component', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('order', self.gf('django.db.models.fields.PositiveIntegerField')(default=1, db_index=True)),
('title', self.gf('django.db.models.fields.CharField')(max_length=50)),
('widget', self.gf('adminsortable.fields.SortableForeignKey')(to=orm['app.Widget'])),
))
db.send_create_signal(u'app', ['Component'])
def backwards(self, orm):
# Deleting model 'Component'
db.delete_table(u'app_component')
models = {
u'app.category': {
'Meta': {'ordering': "['order']", 'object_name': 'Category'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'app.component': {
'Meta': {'ordering': "['order']", 'object_name': 'Component'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
'widget': ('adminsortable.fields.SortableForeignKey', [], {'to': u"orm['app.Widget']"})
},
u'app.credit': {
'Meta': {'ordering': "['order']", 'object_name': 'Credit'},
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30'}),
'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['app.Project']"})
},
u'app.genericnote': {
'Meta': {'ordering': "['order']", 'object_name': 'GenericNote'},
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'generic_notes'", 'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'app.note': {
'Meta': {'ordering': "['order']", 'object_name': 'Note'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['app.Project']"}),
'text': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
u'app.project': {
'Meta': {'ordering': "['order']", 'object_name': 'Project'},
'category': ('adminsortable.fields.SortableForeignKey', [], {'to': u"orm['app.Category']"}),
'description': ('django.db.models.fields.TextField', [], {}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'app.widget': {
'Meta': {'ordering': "['order']", 'object_name': 'Widget'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
}
}
complete_apps = ['app']

View File

@ -16,7 +16,7 @@ class SimpleModel(models.Model):
return self.title return self.title
#a model that is sortable # A model that is sortable
class Category(SimpleModel, Sortable): class Category(SimpleModel, Sortable):
class Meta(Sortable.Meta): class Meta(Sortable.Meta):
""" """
@ -27,8 +27,17 @@ class Category(SimpleModel, Sortable):
verbose_name_plural = 'Categories' verbose_name_plural = 'Categories'
#a model that is sortable relative to a foreign key that is also sortable # A model with an override of its queryset for admin
#uses SortableForeignKey field. Works with versions 1.3+ class Widget(SimpleModel, Sortable):
class Meta(Sortable.Meta):
pass
def __unicode__(self):
return self.title
# A model that is sortable relative to a foreign key that is also sortable
# uses SortableForeignKey field. Works with versions 1.3+
class Project(SimpleModel, Sortable): class Project(SimpleModel, Sortable):
class Meta(Sortable.Meta): class Meta(Sortable.Meta):
pass pass
@ -37,7 +46,7 @@ class Project(SimpleModel, Sortable):
description = models.TextField() description = models.TextField()
#registered as a tabular inline on `Project` # Registered as a tabular inline on `Project`
class Credit(Sortable): class Credit(Sortable):
class Meta(Sortable.Meta): class Meta(Sortable.Meta):
pass pass
@ -50,7 +59,7 @@ class Credit(Sortable):
return '{0} {1}'.format(self.first_name, self.last_name) return '{0} {1}'.format(self.first_name, self.last_name)
#registered as a stacked inline on `Project` # Registered as a stacked inline on `Project`
class Note(Sortable): class Note(Sortable):
class Meta(Sortable.Meta): class Meta(Sortable.Meta):
pass pass
@ -62,7 +71,7 @@ class Note(Sortable):
return self.text return self.text
#a generic bound model # A generic bound model
class GenericNote(SimpleModel, Sortable): class GenericNote(SimpleModel, Sortable):
content_type = models.ForeignKey(ContentType, content_type = models.ForeignKey(ContentType,
verbose_name=u"Content type", related_name="generic_notes") verbose_name=u"Content type", related_name="generic_notes")
@ -75,3 +84,14 @@ class GenericNote(SimpleModel, Sortable):
def __unicode__(self): def __unicode__(self):
return u'{0} : {1}'.format(self.title, self.content_object) return u'{0} : {1}'.format(self.title, self.content_object)
# An model registered as an inline that has a custom queryset
class Component(SimpleModel, Sortable):
class Meta(Sortable.Meta):
pass
widget = SortableForeignKey(Widget)
def __unicode__(self):
return self.title

View File

@ -122,13 +122,12 @@ INSTALLED_APPS = (
'django.contrib.sites', 'django.contrib.sites',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
# Uncomment the next line to enable the admin:
'django.contrib.admin', 'django.contrib.admin',
# Uncomment the next line to enable admin documentation:
'django.contrib.admindocs', 'django.contrib.admindocs',
'adminsortable', 'adminsortable',
'app', 'app',
'south',
) )
# A sample logging configuration. The only tangible logging # A sample logging configuration. The only tangible logging