Minor modifications for Django 1.5.x and 1.4.x backward-compatibility.

Added new sample project.
Improved documentation.
Refactored CSS selector for inlines that are sortable.
master
Brandon Taylor 2013-03-10 20:53:05 -04:00
parent ac2cecf291
commit a54e81434f
39 changed files with 561 additions and 3702 deletions

View File

@ -1,6 +1,15 @@
import json
from django import VERSION as DJANGO_VERSION
DJANGO_MINOR_VERSION = DJANGO_VERSION[1]
from django.conf import settings
from django.conf.urls.defaults import patterns, url
if DJANGO_MINOR_VERSION < 5:
from django.conf.urls.defaults import patterns, url
else:
from django.conf.urls import patterns, url
from django.contrib.admin import ModelAdmin, TabularInline, StackedInline
from django.contrib.admin.options import InlineModelAdmin
from django.contrib.contenttypes.models import ContentType
@ -17,15 +26,17 @@ STATIC_URL = settings.STATIC_URL
class SortableAdmin(ModelAdmin):
"""
Admin class to add template overrides and context objects to enable drag-and-drop
ordering.
Admin class to add template overrides and context objects to enable
drag-and-drop ordering.
"""
ordering = ('order', 'id')
sortable_change_list_with_sort_link_template = 'adminsortable/change_list_with_sort_link.html'
sortable_change_list_with_sort_link_template = \
'adminsortable/change_list_with_sort_link.html'
sortable_change_form_template = 'adminsortable/change_form.html'
sortable_change_list_template = 'adminsortable/change_list.html'
sortable_javascript_includes_template = 'adminsortable/shared/javascript_includes.html'
sortable_javascript_includes_template = \
'adminsortable/shared/javascript_includes.html'
class Meta:
abstract = True
@ -54,25 +65,30 @@ class SortableAdmin(ModelAdmin):
def get_urls(self):
urls = super(SortableAdmin, self).get_urls()
admin_urls = patterns('',
# this view changes the order
url(r'^sorting/do-sorting/(?P<model_type_id>\d+)/$',
self.admin_site.admin_view(self.do_sorting_view),
name='{0}_do_sorting'.format(self.model._meta.app_label)), # this view changes the order
name='{0}_do_sorting'.format(self.model._meta.app_label)),
# this view shows a link to the drag-and-drop view
url(r'^sort/$', self.admin_site.admin_view(self.sort_view),
name='{0}_sort'.format(self.model._meta.app_label)), # this view shows a link to the drag-and-drop view
name='{0}_sort'.format(self.model._meta.app_label)),
)
return admin_urls + urls
def sort_view(self, request):
"""
Custom admin view that displays the objects as a list whose sort order can be
changed via drag-and-drop.
Custom admin view that displays the objects as a list whose sort
order can be changed via drag-and-drop.
"""
opts = self.model._meta
has_perm = request.user.has_perm('{0}.{1}'.format(opts.app_label, opts.get_change_permission()))
has_perm = request.user.has_perm('{0}.{1}'.format(opts.app_label,
opts.get_change_permission()))
objects = self.model.objects.all()
# Determine if we need to regroup objects relative to a foreign key specified on the
# model class that is extending Sortable.
# Determine if we need to regroup objects relative to a
# foreign key specified on the model class that is extending Sortable.
# Legacy support for 'sortable_by' defined as a model property
sortable_by_property = getattr(self.model, 'sortable_by', None)
@ -80,19 +96,24 @@ class SortableAdmin(ModelAdmin):
sortable_by_fk = self._get_sortable_foreign_key()
if sortable_by_property:
# backwards compatibility for < 1.1.1, where sortable_by was a classmethod instead of a property
# backwards compatibility for < 1.1.1, where sortable_by was a
# classmethod instead of a property
try:
sortable_by_class, sortable_by_expression = sortable_by_property()
sortable_by_class, sortable_by_expression = \
sortable_by_property()
except (TypeError, ValueError):
sortable_by_class = self.model.sortable_by
sortable_by_expression = sortable_by_class.__name__.lower()
sortable_by_class_display_name = sortable_by_class._meta.verbose_name_plural
sortable_by_class_display_name = sortable_by_class._meta \
.verbose_name_plural
sortable_by_class_is_sortable = sortable_by_class.is_sortable()
elif sortable_by_fk:
# get sortable by properties from the SortableForeignKey field - supported in 1.3+
sortable_by_class_display_name = sortable_by_fk.rel.to._meta.verbose_name_plural
# get sortable by properties from the SortableForeignKey
# field - supported in 1.3+
sortable_by_class_display_name = sortable_by_fk.rel.to \
._meta.verbose_name_plural
sortable_by_class = sortable_by_fk.rel.to
sortable_by_expression = sortable_by_fk.name.lower()
try:
@ -102,13 +123,14 @@ class SortableAdmin(ModelAdmin):
else:
# model is not sortable by another model
sortable_by_class = sortable_by_expression = sortable_by_class_display_name = \
sortable_by_class_is_sortable = None
sortable_by_class = sortable_by_expression = \
sortable_by_class_display_name = \
sortable_by_class_is_sortable = None
if sortable_by_property or sortable_by_fk:
# Order the objects by the property they are sortable by, then by the order, otherwise the regroup
# template tag will not show the objects correctly as
# shown in https://docs.djangoproject.com/en/1.3/ref/templates/builtins/#regroup
# Order the objects by the property they are sortable by,
#then by the order, otherwise the regroup
# template tag will not show the objects correctly
objects = objects.order_by(sortable_by_expression, 'order')
try:
@ -117,7 +139,8 @@ class SortableAdmin(ModelAdmin):
verbose_name_plural = opts.verbose_name_plural
context = {
'title': 'Drag and drop %s to change display order' % capfirst(verbose_name_plural),
'title': 'Drag and drop {0} to change display order'.format(
capfirst(verbose_name_plural)),
'opts': opts,
'app_label': opts.app_label,
'has_perm': has_perm,
@ -126,47 +149,59 @@ class SortableAdmin(ModelAdmin):
'sortable_by_class': sortable_by_class,
'sortable_by_class_is_sortable': sortable_by_class_is_sortable,
'sortable_by_class_display_name': sortable_by_class_display_name,
'sortable_javascript_includes_template': self.sortable_javascript_includes_template
'sortable_javascript_includes_template':
self.sortable_javascript_includes_template
}
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.
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)
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):
if self.has_sortable_tabular_inlines or self.has_sortable_stacked_inlines:
if self.has_sortable_tabular_inlines or \
self.has_sortable_stacked_inlines:
self.change_form_template = self.sortable_change_form_template
extra_context = {
'sortable_javascript_includes_template': self.sortable_javascript_includes_template,
'has_sortable_tabular_inlines': self.has_sortable_tabular_inlines,
'has_sortable_stacked_inlines': self.has_sortable_stacked_inlines
'sortable_javascript_includes_template':
self.sortable_javascript_includes_template,
'has_sortable_tabular_inlines':
self.has_sortable_tabular_inlines,
'has_sortable_stacked_inlines':
self.has_sortable_stacked_inlines
}
return super(SortableAdmin, self).change_view(request, object_id, extra_context=extra_context)
return super(SortableAdmin, self).change_view(request, object_id,
extra_context=extra_context)
@csrf_exempt
def do_sorting_view(self, request, model_type_id=None):
"""
This view sets the ordering of the objects for the model type and primary keys
passed in. It must be an Ajax POST.
This view sets the ordering of the objects for the model type
and primary keys passed in. It must be an Ajax POST.
"""
if request.is_ajax() and request.method == 'POST':
try:
indexes = map(str, request.POST.get('indexes', []).split(','))
klass = ContentType.objects.get(id=model_type_id).model_class()
objects_dict = dict([(str(obj.pk), obj) for obj in klass.objects.filter(pk__in=indexes)])
objects_dict = dict([(str(obj.pk), obj) for obj in
klass.objects.filter(pk__in=indexes)])
if '-order' in klass._meta.ordering: # desc order
start_object = max(objects_dict.values(), key=lambda x: getattr(x, 'order'))
start_index = getattr(start_object, 'order') or len(indexes)
start_object = max(objects_dict.values(),
key=lambda x: getattr(x, 'order'))
start_index = getattr(start_object, 'order') \
or len(indexes)
step = -1
else: # 'order' is default, asc order
start_object = min(objects_dict.values(), key=lambda x: getattr(x, 'order'))
start_object = min(objects_dict.values(),
key=lambda x: getattr(x, 'order'))
start_index = getattr(start_object, 'order') or 0
step = 1
@ -180,7 +215,8 @@ class SortableAdmin(ModelAdmin):
pass
else:
response = {'objects_sorted': False}
return HttpResponse(json.dumps(response, ensure_ascii=False), mimetype='application/json')
return HttpResponse(json.dumps(response, ensure_ascii=False),
mimetype='application/json')
class SortableInlineBase(InlineModelAdmin):
@ -188,8 +224,8 @@ class SortableInlineBase(InlineModelAdmin):
super(SortableInlineBase, self).__init__(*args, **kwargs)
if not issubclass(self.model, Sortable):
raise Warning(u'Models that are specified in SortableTabluarInline and SortableStackedInline '
'must inherit from Sortable')
raise Warning(u'Models that are specified in SortableTabluarInline'
' and SortableStackedInline must inherit from Sortable')
self.is_sortable = self.model.is_sortable()

View File

@ -1,3 +1,3 @@
.tabular .sortable:not(.add-row) {
.sortable.has_original {
cursor: move;
}

View File

@ -27,11 +27,11 @@
{% block content_title %}
<h1>
{% if sort_type %}
{% blocktrans with opts.verbose_name_plural|capfirst as model %}Drag and drop {{ sort_type }} {{ model }} to change their order.{% endblocktrans %}
{% else %}
{% blocktrans with opts.verbose_name_plural|capfirst as model %}Drag and drop {{ model }} to change their order.{% endblocktrans %}
{% endif %}
{% if sort_type %}
{% blocktrans with opts.verbose_name_plural|capfirst as model %}Drag and drop {{ sort_type }} {{ model }} to change their order.{% endblocktrans %}
{% else %}
{% blocktrans with opts.verbose_name_plural|capfirst as model %}Drag and drop {{ model }} to change their order.{% endblocktrans %}
{% endif %}
</h1>
{% if sortable_by_class.is_sortable %}
<p>

View File

@ -1,10 +1,10 @@
{% load i18n adminmedia adminsortable_tags %}
{% load i18n admin_modify adminsortable_tags %}
<div class="inline-group" id="{{ inline_admin_formset.formset.prefix }}-group">
<h2>{{ inline_admin_formset.opts.verbose_name_plural|title }} {% if inline_admin_formset.opts.is_sortable %} - 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 forloop.last %} empty-form last-related{% endif %}" id="{{ inline_admin_formset.formset.prefix }}-{% if not forloop.last %}{{ forloop.counter0 }}{% else %}empty{% endif %}">
{% 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><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 %}
@ -16,7 +16,7 @@
{% 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 admin:admin_do_sorting inline_admin_form.original.model_type_id %}" />
<input type="hidden" name="admin_sorting_url" value="{% get_do_sorting_url inline_admin_form.original %}" />
{% endif %}
</div>{% endfor %}
</div>

View File

@ -40,7 +40,7 @@
{% endfor %}
{% endspaceless %}
{% if inline_admin_form.original %}
<input type="hidden" name="admin_sorting_url" value="{% url admin:admin_do_sorting inline_admin_form.original.model_type_id %}" />
<input type="hidden" name="admin_sorting_url" value="{% get_do_sorting_url inline_admin_form.original %}" />
{% endif %}
</td>
{% for fieldset in inline_admin_form %}

View File

@ -1,4 +1,5 @@
from django import template
from django import VERSION as DJANGO_VERSION
from django.core.urlresolvers import reverse
register = template.Library()
@ -38,5 +39,5 @@ def render_object_rep(context, obj,
@register.simple_tag(takes_context=False)
def get_do_sorting_url(obj):
return reverse('admin:%s_do_sorting' % obj._meta.app_label,
kwargs={'model_type_id': obj.model_type_id()})
return reverse('admin:{0}_do_sorting'.format(obj._meta.app_label),
kwargs={'model_type_id': obj.model_type_id()})

View File

@ -1,14 +0,0 @@
Thanks for download django-admin-sortable.
I hope it can help you out as much on your project(s) as it does on mine :)
The only url patterns that are enabled for this project are /admin
The username/password is: admin/admin
I have not yet upgraded this sample application to use Django 1.5.
Please run this sample_project in a virtualenv and install dependencies
using the supplied requirements.txt via pip.
If you still need help, please contact me at: brandon@iambrandontaylor.com
Kind regards,
Brandon Taylor

View File

@ -1,6 +1,7 @@
from django.contrib import admin
from adminsortable.admin import SortableAdmin, SortableTabularInline, SortableStackedInline
from adminsortable.admin import (SortableAdmin, SortableTabularInline,
SortableStackedInline)
from app.models import Category, Project, Credit, Note

View File

@ -1,136 +0,0 @@
[
{
"pk": 1,
"model": "app.category",
"fields": {
"order": 2,
"title": "Category 1"
}
},
{
"pk": 2,
"model": "app.category",
"fields": {
"order": 1,
"title": "Category 2"
}
},
{
"pk": 3,
"model": "app.category",
"fields": {
"order": 3,
"title": "Category 3"
}
},
{
"pk": 1,
"model": "app.project",
"fields": {
"category": 1,
"description": "Sample category 1",
"order": 1,
"title": "Sample Project 1"
}
},
{
"pk": 2,
"model": "app.project",
"fields": {
"category": 1,
"description": "Another sample project.",
"order": 2,
"title": "Sample Project 2"
}
},
{
"pk": 3,
"model": "app.project",
"fields": {
"category": 3,
"description": "Yest another sample project.",
"order": 3,
"title": "Sample Project 3"
}
},
{
"pk": 1,
"model": "app.credit",
"fields": {
"project": 1,
"first_name": "Bob",
"last_name": "Smith",
"order": 2
}
},
{
"pk": 2,
"model": "app.credit",
"fields": {
"project": 1,
"first_name": "Sally",
"last_name": "Smith",
"order": 1
}
},
{
"pk": 3,
"model": "app.credit",
"fields": {
"project": 1,
"first_name": "Johnny",
"last_name": "Smith",
"order": 3
}
},
{
"pk": 4,
"model": "app.credit",
"fields": {
"project": 3,
"first_name": "Sally Ann",
"last_name": "Smith",
"order": 4
}
},
{
"pk": 5,
"model": "app.credit",
"fields": {
"project": 3,
"first_name": "George",
"last_name": "Smith",
"order": 5
}
},
{
"pk": 6,
"model": "app.credit",
"fields": {
"project": 2,
"first_name": "Bob",
"last_name": "Smith",
"order": 6
}
},
{
"pk": 7,
"model": "app.credit",
"fields": {
"project": 2,
"first_name": "George",
"last_name": "Smith",
"order": 7
}
},
{
"pk": 8,
"model": "app.credit",
"fields": {
"project": 2,
"first_name": "Sally",
"last_name": "Smith",
"order": 8
}
}
]

View File

@ -30,7 +30,7 @@ class SortableTestCase(TestCase):
self.factory = RequestFactory()
self.user_raw_password = 'admin'
self.user = User.objects.create_user('admin', 'admin@admin.com',
self.user_raw_password)
self.user_raw_password)
self.user.is_staff = True
self.user.is_superuser = True
self.user.save()
@ -40,7 +40,8 @@ class SortableTestCase(TestCase):
return category
def test_new_user_is_authenticated(self):
self.assertEqual(self.user.is_authenticated(), True, 'User is not authenticated')
self.assertEqual(self.user.is_authenticated(), True,
'User is not authenticated')
def test_new_user_is_staff(self):
self.assertEqual(self.user.is_staff, True, 'User is not staff')
@ -52,13 +53,13 @@ class SortableTestCase(TestCase):
"""
self.create_category()
self.assertFalse(Category.is_sortable(),
'Category only has one record. It should not be sortable.')
'Category only has one record. It should not be sortable.')
def test_is_sortable(self):
self.create_category()
self.create_category(title='Category 2')
self.assertTrue(Category.is_sortable(),
'Category has more than one record. It should be sortable.')
'Category has more than one record. It should be sortable.')
def test_save_order_incremented(self):
category1 = self.create_category()
@ -68,9 +69,11 @@ class SortableTestCase(TestCase):
self.assertEqual(category2.order, 2, 'Category 2 order should be 2.')
def test_adminsortable_change_list_view(self):
self.client.login(username=self.user.username, password=self.user_raw_password)
self.client.login(username=self.user.username,
password=self.user_raw_password)
response = self.client.get('/admin/app/category/sort/')
self.assertEquals(response.status_code, httplib.OK, 'Unable to reach sort view.')
self.assertEquals(response.status_code, httplib.OK,
'Unable to reach sort view.')
def make_test_categories(self):
category1 = self.create_category()
@ -86,32 +89,39 @@ class SortableTestCase(TestCase):
return {'indexes': ','.join([str(c.id) for c in categories])}
def test_adminsortable_changelist_templates(self):
logged_in = self.client.login(username=self.user.username, password=self.user_raw_password)
logged_in = self.client.login(username=self.user.username,
password=self.user_raw_password)
self.assertTrue(logged_in, 'User is not logged in')
response = self.client.get(reverse('admin:app_sort'))
self.assertEqual(response.status_code, httplib.OK, u'Admin sort request failed.')
self.assertEqual(response.status_code, httplib.OK,
'Admin sort request failed.')
#assert adminsortable change list templates are used
template_names = [t.name for t in response.templates]
self.assertTrue('adminsortable/change_list.html' in template_names,
u'adminsortable/change_list.html was not rendered')
self.assertTrue('adminsortable/shared/javascript_includes.html' in template_names,
u'JavaScript includes for adminsortable change list were not rendered')
'adminsortable/change_list.html was not rendered')
self.assertTrue('adminsortable/shared/javascript_includes.html'
in template_names,
'JavaScript includes for adminsortable change list '
'were not rendered')
def test_adminsortable_change_list_sorting_fails_if_not_ajax(self):
logged_in = self.client.login(username=self.user.username, password=self.user_raw_password)
logged_in = self.client.login(username=self.user.username,
password=self.user_raw_password)
self.assertTrue(logged_in, 'User is not logged in')
category1, category2, category3 = self.make_test_categories()
#make a normal POST
response = self.client.post(self.get_sorting_url(),
data=self.get_category_indexes(category1, category2, category3))
data=self.get_category_indexes(category1, category2, category3))
content = json.loads(response.content)
self.assertFalse(content.get('objects_sorted'), u'Objects should not have been sorted. An ajax post is required.')
self.assertFalse(content.get('objects_sorted'),
'Objects should not have been sorted. An ajax post is required.')
def test_adminsortable_change_list_sorting_successful(self):
logged_in = self.client.login(username=self.user.username, password=self.user_raw_password)
logged_in = self.client.login(username=self.user.username,
password=self.user_raw_password)
self.assertTrue(logged_in, 'User is not logged in')
#make categories
@ -119,10 +129,11 @@ class SortableTestCase(TestCase):
#make an Ajax POST
response = self.client.post(self.get_sorting_url(),
data=self.get_category_indexes(category3, category2, category1),
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
data=self.get_category_indexes(category3, category2, category1),
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
content = json.loads(response.content)
self.assertTrue(content.get('objects_sorted'), u'Objects should have been sorted.')
self.assertTrue(content.get('objects_sorted'),
'Objects should have been sorted.')
#assert order is correct
categories = Category.objects.all()
@ -130,11 +141,17 @@ class SortableTestCase(TestCase):
cat2 = categories[1]
cat3 = categories[2]
self.assertEqual(cat1.order, 1, u'First category returned should have order == 1')
self.assertEqual(cat1.pk, 3, u'Category ID 3 should have been first in queryset')
self.assertEqual(cat1.order, 1,
'First category returned should have order == 1')
self.assertEqual(cat1.pk, 3,
'Category ID 3 should have been first in queryset')
self.assertEqual(cat2.order, 2, u'Second category returned should have order == 2')
self.assertEqual(cat2.pk, 2, u'Category ID 2 should have been second in queryset')
self.assertEqual(cat2.order, 2,
'Second category returned should have order == 2')
self.assertEqual(cat2.pk, 2,
'Category ID 2 should have been second in queryset')
self.assertEqual(cat3.order, 3, u'Third category returned should have order == 3')
self.assertEqual(cat3.pk, 1, u'Category ID 1 should have been third in queryset')
self.assertEqual(cat3.order, 3,
'Third category returned should have order == 3')
self.assertEqual(cat3.pk, 1,
'Category ID 1 should have been third in queryset')

File diff suppressed because it is too large Load Diff

View File

@ -1,2 +0,0 @@
VERSION = (1, 0, 1)
__version__ = '1.0.1'

View File

@ -1,15 +0,0 @@
import os, os.path as osp
from django.conf import settings
from django.db.models import get_apps
def get_app_resource(app, path):
apps = get_apps()
for x in apps:
app_dir = osp.dirname(x.__file__)
if app == x.__name__.split('.')[-2]:
resource_dir = osp.join(app_dir, "resources")
asset = osp.join(resource_dir, path)
if osp.exists(asset):
return asset
continue

View File

@ -1,35 +0,0 @@
from django.core.management.base import LabelCommand, CommandError
from django.db.models import get_apps
import os, os.path as osp
import shutil
class Command(LabelCommand):
args = ''
label = 'directory'
def handle(self, *labels, **options):
if not labels or len(labels) > 1:
raise CommandError('Enter one directory name.')
label = labels[0]
final_dest = osp.join(os.getcwd(), label)
if osp.exists(final_dest):
raise CommandError('Directory already exists')
os.mkdir(final_dest)
apps = get_apps()
for x in apps:
app_dir = osp.dirname(x.__file__)
module = x.__name__
app = module.split('.')[-2]
if app == 'admin': continue
media_dir = osp.join(app_dir, "media", app)
if not osp.isdir(media_dir):
media_dir = osp.join(app_dir, "media")
if osp.exists(media_dir):
print "copy", media_dir, '->', osp.join(final_dest, app)
shutil.copytree(media_dir, osp.join(final_dest, app))

View File

@ -1,53 +0,0 @@
from django.core.management.base import NoArgsCommand
from django.db.models import get_apps
class Command(NoArgsCommand):
help = """
Removes all symlinks in MEDIA_ROOT and then scans all installed applications for a media folder to symlink to MEDIA_ROOT.
If installed app has a media folder, it first attempts to symlink the contents
ie: app/media/app_name -> MEDIA_ROOT/app_name
If the symlink name already exists, it assumes the media directory is not subfoldered and attempts:
ie: app/media -> MEDIA_ROOT/app_name"""
def handle_noargs(self, **options):
from django.conf import settings
import os
media_path = settings.MEDIA_ROOT
print "creating symlinks for app media under %s" % media_path
for d in os.listdir(media_path):
path = os.path.join(media_path, d)
if os.path.islink(path):
os.remove(os.path.join(path))
print " - removed %s" % path
apps = get_apps()
for app in apps:
app_file = app.__file__
if os.path.splitext(app_file)[0].endswith('/__init__'):
# models are an folder, go one level up
app_file = os.path.dirname(app_file)
app_path = os.path.dirname(app_file)
if 'media' in os.listdir(app_path) and os.path.isdir(os.path.join(app_path,'media')):
module = app.__name__
app_name = module.split('.')[-2]
app_media = os.path.join(app_path, "media", app_name)
if not os.path.isdir(app_media):
app_media = os.path.join(app_path, "media")
try:
os.symlink(app_media, os.path.join(media_path, app_name))
print " + added %s as %s" % (os.path.join(app_media), os.path.join(media_path, app_name))
except OSError, e:
if e.errno == 17:
pass
print " o skipping %s" % app_media
else:
raise
# try:
# os.symlink(app_media, os.path.join(media_path,app.split('.')[-1]))
# print " + added %s as %s" % (app_media, os.path.join(media_path, app.split('.')[-1]))

View File

@ -1,38 +0,0 @@
from md5 import md5
from django.conf import settings
from django.core.cache import cache
from django.core.urlresolvers import reverse
from appmedia.BeautifulSoup import BeautifulSoup, Tag
boundary = '*#*'
class ReplaceAssets(object):
def process_response(self, request, response):
if response['Content-Type'].startswith('text/html') and settings.CACHE_BACKEND not in ['', None, 'dummy:///']:
soup = BeautifulSoup(response.content)
head = soup.head
#[script.extract() for script in head.findAll(lambda x: x.name == 'script' and 'src' in dict(x.attrs) and x['src'].startswith(settings.MEDIA_URL) )]
#[css.extract() for css in head.findAll(lambda x: x.name == 'link' and 'href' in dict(x.attrs) and x['href'].startswith(settings.MEDIA_URL) )]
scripts = head.findAll(lambda x: x.name == 'script' and 'src' in dict(x.attrs) and x['src'].startswith(settings.MEDIA_URL) )
css = head.findAll(lambda x: x.name == 'link' and 'href' in dict(x.attrs) and x['href'].startswith(settings.MEDIA_URL) )
script_sources = [x['src'] for x in scripts]
new_script = md5(boundary.join(script_sources)).hexdigest()
cache.set(new_script, script_sources)
[x.extract() for x in scripts]
css_sources = [x['href'] for x in css]
new_css = md5(boundary.join(css_sources)).hexdigest()
cache.set(new_css, css_sources)
[x.extract() for x in css]
tag = Tag(soup, "script", [("type", "text/javascript"), ("src", reverse('cached_asset', kwargs={'asset':new_script+".js"}) )])
head.insert(0, tag)
tag = Tag(soup, "link", [("type", "text/css"), ("href", reverse('cached_asset', kwargs={'asset':new_css+".css"})), ('rel', 'stylesheet')])
head.insert(0, tag)
response.content = soup.prettify()
return response

View File

@ -1,3 +0,0 @@
from django.db import models
# Create your models here.

View File

@ -1,7 +0,0 @@
from django.conf.urls.defaults import *
from django.conf import settings
urlpatterns = patterns('',
url(r'^cached-asset/(?P<asset>.*)$', 'appmedia.views.serve_cached_asset', name="cached_asset"),
(r'^(?P<app>[^/]*)/(?P<path>.*)$', 'appmedia.views.serve'),
)

View File

@ -1,81 +0,0 @@
import os, os.path as osp
from django.conf import settings
from django.views.static import serve as django_serve
from django.views.decorators.cache import cache_page
from django.db.models import get_apps
from django.core.cache import cache
from django.http import Http404, HttpResponse
def serve(request, app, path, show_indexes=True):
if request.method == 'GET':
apps = get_apps()
for x in apps:
app_dir = osp.dirname(x.__file__)
module = x.__name__
if app == module.split('.')[-2]: #we get the models module here
if app_dir.endswith("models"):
# this can happen only in case when models are an directory
app_dir = osp.split(app_dir)[0]
media_dir = osp.join(app_dir, "media", app)
if not osp.isdir(media_dir):
media_dir = osp.join(app_dir, "media")
asset = osp.join(media_dir, path)
if osp.exists(asset):
return django_serve(request, path, document_root=media_dir, show_indexes=show_indexes)
#continue
return django_serve(request, app+"/"+path, document_root=settings.MEDIA_ROOT, show_indexes=show_indexes)
elif request.method == 'POST':
data = request.POST.get("data", "")
apps = get_apps()
for x in apps:
app_dir = osp.dirname(x.__file__)
module = x.__name__
if app == module.split('.')[-2]: #we get the models module here
media_dir = osp.join(app_dir, "media")
asset = osp.join(media_dir, path)
if osp.exists(asset):
f = file(asset, 'w')
for line in data.split('\n'):
line.strip()
line = line[:-1]
if line:
selector, datap = line.split('{')
print >>f, selector, '{'
datap.strip()
lines = datap.split(';')
if lines:
print >>f, " "+";\n ".join(lines)
print >>f, '}\n'
f.close()
return django_serve(request, path, document_root=media_dir, show_indexes=show_indexes)
continue
def get_file(path):
app = path.split('/')[2]
path = "/".join(path.split('/')[3:])
apps = get_apps()
for x in apps:
app_dir = osp.dirname(x.__file__)
module = x.__name__
if app == module.split('.')[-2]: #we get the models module here
media_dir = osp.join(app_dir, "media")
asset = osp.join(media_dir, path)
if osp.exists(asset):
print osp.join(media_dir, path)
return osp.join(media_dir, path)
return osp.join(settings.MEDIA_ROOT, app+"/"+path)
@cache_page(24*60*60)
def serve_cached_asset(request, asset):
name, ext = asset.split('.')
files = cache.get(name)
if ext == 'js':
response = HttpResponse("\n".join([file(get_file(path)).read() for path in files]), mimetype="text/javascript")
return response
elif ext == 'css':
response = HttpResponse("\n".join([file(get_file(path)).read() for path in files]), mimetype="text/css")
return response
raise Http404()

View File

@ -1,14 +1,10 @@
#!/usr/bin/env python
from django.core.management import execute_manager
import imp
try:
imp.find_module('settings') # Assumed to be in the same directory.
except ImportError:
import sys
sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__)
sys.exit(1)
import settings
import os
import sys
if __name__ == "__main__":
execute_manager(settings)
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sample_project.settings")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)

View File

@ -0,0 +1 @@
django==1.5

View File

@ -1,14 +1,11 @@
import os
def map_path(directory_name):
return os.path.join(os.path.dirname(__file__), directory_name).replace('\\', '/')
# Django settings for test_project project.
from utils import map_path
DEBUG = True
SERVE_STATIC_MEDIA = True
TEMPLATE_DEBUG = DEBUG
ADMINS = (
# ('Your Name', 'your_email@example.com'),
)
MANAGERS = ADMINS
@ -16,21 +13,23 @@ MANAGERS = ADMINS
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': 'adminsortable.sqlite',
'NAME': map_path('database/test_project.sqlite'),
# The following settings are not used with sqlite3:
'USER': '',
'PASSWORD': '',
'HOST': '',
'PORT': '',
'PORT': ''
}
}
# 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.
# On Unix systems, a value of None will cause Django to use the same
# timezone as the operating system.
# If running in a Windows environment this must be set to the same as your
# system time zone.
# In a Windows environment this must be set to your system time zone.
TIME_ZONE = 'America/Chicago'
# Language code for this installation. All choices can be found here:
@ -44,33 +43,31 @@ SITE_ID = 1
USE_I18N = False
# If you set this to False, Django will not format dates, numbers and
# calendars according to the current locale
# 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: "/home/media/media.lawrence.com/media/"
MEDIA_ROOT = map_path('static')
# 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://media.lawrence.com/media/", "http://example.com/media/"
MEDIA_URL = '/static/'
# 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: "/home/media/media.lawrence.com/static/"
# Example: "/var/www/example.com/static/"
STATIC_ROOT = ''
# URL prefix for static files.
# Example: "http://media.lawrence.com/static/"
# Example: "http://example.com/static/", "http://static.example.com/"
STATIC_URL = '/static/'
# URL prefix for admin static files -- CSS, JavaScript and images.
# Make sure to use a trailing slash.
# Examples: "http://foo.com/static/admin/", "/static/admin/".
ADMIN_MEDIA_PREFIX = '/media/'
# Additional locations of static files
STATICFILES_DIRS = (
# Put strings here, like "/home/html/static" or "C:/www/django/static".
@ -87,7 +84,7 @@ STATICFILES_FINDERS = (
)
# Make this unique, and don't share it with anybody.
SECRET_KEY = 'uz-di2#4pzf77@9-+hh&lyypgg#--zk%$%l7p7h385#4u7ra98'
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.
TEMPLATE_LOADERS = (
@ -97,27 +94,25 @@ TEMPLATE_LOADERS = (
)
MIDDLEWARE_CLASSES = (
'django.middleware.gzip.GZipMiddleware',
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
)
TEMPLATE_CONTEXT_PROCESSORS = (
'django.contrib.auth.context_processors.auth',
'django.core.context_processors.i18n',
'django.core.context_processors.request',
'django.core.context_processors.media',
'django.core.context_processors.static',
'django.contrib.messages.context_processors.messages',
# Uncomment the next line for simple clickjacking protection:
'django.middleware.clickjacking.XFrameOptionsMiddleware',
)
ROOT_URLCONF = 'sample_project.urls'
# Python dotted path to the WSGI application used by Django's runserver.
WSGI_APPLICATION = 'sample_project.wsgi.application'
TEMPLATE_DIRS = (
map_path('templates'),
# 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 = (
@ -126,27 +121,33 @@ INSTALLED_APPS = (
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
# Uncomment the next line to enable the admin:
'django.contrib.admin',
# Uncomment the next line to enable admin documentation:
'django.contrib.admindocs',
'appmedia',
'south',
'adminsortable',
'app',
)
# 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.
# 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'
}
},

View File

@ -0,0 +1,18 @@
from django.conf.urls import patterns, include, url
# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
# Examples:
# url(r'^$', 'test_project.views.home', name='home'),
# url(r'^test_project/', include('test_project.foo.urls')),
# Uncomment the admin/doc line below to enable admin documentation:
url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
# Uncomment the next line to enable the admin:
url(r'^admin/', include(admin.site.urls)),
)

View File

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

View File

@ -0,0 +1,32 @@
"""
WSGI config for test_project 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.
"""
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"] = "test_project.settings"
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_project.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)

View File

@ -1,48 +0,0 @@
#sortable ul
{
-webkit-padding-start: 0;
padding-left: 0;
margin-top: 0.75em;
}
#sortable ul ul
{
margin-left: 1em;
}
#sortable ul li,
#sortable ul li a
{
cursor: move;
}
#sortable ul li
{
overflow: auto;
margin-left: 0;
display: block;
}
#sortable .sortable
{
list-style: none;
font-weight: 800;
margin-bottom: 0.75em;
}
#sortable .sortable.single,
#sortable ul ul a
{
font-weight: 400;
}
#sortable ul ul a
{
color: #5B80B2;
margin-bottom: 0;
}
.sortable a:hover
{
color: #003366;
}

View File

@ -1,4 +0,0 @@
.sortable
{
cursor: move;
}

View File

@ -1,4 +0,0 @@
.tabular .sortable
{
cursor: move;
}

View File

@ -1,21 +0,0 @@
jQuery(function($){
$('.sortable').sortable({
axis : 'y',
containment : 'parent',
tolerance : 'pointer',
items : 'li',
stop : function(event, ui)
{
var indexes = Array();
ui.item.parent().children('li').each(function(i)
{
indexes.push($(this).find(':hidden[name="pk"]').val());
});
$.ajax({
url: ui.item.find('a.admin_sorting_url').attr('href'),
type: 'POST',
data: { indexes: indexes.join(',') }
});
}
});
});

View File

@ -1,33 +0,0 @@
jQuery(function($){
if ($(':hidden[name="admin_sorting_url"]').length > 0)
{
var sortable_inline_rows = $('.inline-group .inline-related');
sortable_inline_rows.addClass('sortable');
$('.inline-group').sortable({
axis : 'y',
containment : 'parent',
tolerance : 'pointer',
items : '.inline-related',
stop : function(event, ui)
{
var indexes = Array();
ui.item.parent().children('.inline-related').each(function(i)
{
index_value = $(this).find(':hidden[name$="-id"]').val();
if (index_value != "" && index_value != undefined)
indexes.push(index_value);
});
$.ajax({
url: ui.item.parent().find(':hidden[name="admin_sorting_url"]').val(),
type: 'POST',
data: { indexes : indexes.join(',') },
success: function()
{
ui.item.effect('highlight', {}, 1000);
}
});
}
});
}
});

View File

@ -1,37 +0,0 @@
jQuery(function($){
if ($(':hidden[name="admin_sorting_url"]').length > 0)
{
var tabular_inline_rows = $('.tabular table tbody tr');
tabular_inline_rows.addClass('sortable');
$('.tabular.inline-related').sortable({
axis : 'y',
containment : 'parent',
tolerance : 'pointer',
items : 'tr',
stop : function(event, ui)
{
var indexes = Array();
ui.item.parent().children('tr').each(function(i)
{
index_value = $(this).find('.original :hidden:first').val();
if (index_value != "" && index_value != undefined)
indexes.push(index_value);
});
$.ajax({
url: ui.item.parent().find(':hidden[name="admin_sorting_url"]').val(),
type: 'POST',
data: { indexes : indexes.join(',') },
success: function()
{
//highlight sorted row, then re-stripe table
ui.item.effect('highlight', {}, 1000);
tabular_inline_rows.removeClass('row1 row2');
$('.tabular table tbody tr:odd').addClass('row2');
$('.tabular table tbody tr:even').addClass('row1');
}
});
}
});
}
});

View File

@ -1,763 +0,0 @@
/*
* jQuery UI Effects 1.8.16
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Effects/
*/
;jQuery.effects || (function($, undefined) {
$.effects = {};
/******************************************************************************/
/****************************** COLOR ANIMATIONS ******************************/
/******************************************************************************/
// override the animation for color styles
$.each(['backgroundColor', 'borderBottomColor', 'borderLeftColor',
'borderRightColor', 'borderTopColor', 'borderColor', 'color', 'outlineColor'],
function(i, attr) {
$.fx.step[attr] = function(fx) {
if (!fx.colorInit) {
fx.start = getColor(fx.elem, attr);
fx.end = getRGB(fx.end);
fx.colorInit = true;
}
fx.elem.style[attr] = 'rgb(' +
Math.max(Math.min(parseInt((fx.pos * (fx.end[0] - fx.start[0])) + fx.start[0], 10), 255), 0) + ',' +
Math.max(Math.min(parseInt((fx.pos * (fx.end[1] - fx.start[1])) + fx.start[1], 10), 255), 0) + ',' +
Math.max(Math.min(parseInt((fx.pos * (fx.end[2] - fx.start[2])) + fx.start[2], 10), 255), 0) + ')';
};
});
// Color Conversion functions from highlightFade
// By Blair Mitchelmore
// http://jquery.offput.ca/highlightFade/
// Parse strings looking for color tuples [255,255,255]
function getRGB(color) {
var result;
// Check if we're already dealing with an array of colors
if ( color && color.constructor == Array && color.length == 3 )
return color;
// Look for rgb(num,num,num)
if (result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color))
return [parseInt(result[1],10), parseInt(result[2],10), parseInt(result[3],10)];
// Look for rgb(num%,num%,num%)
if (result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(color))
return [parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55];
// Look for #a0b1c2
if (result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(color))
return [parseInt(result[1],16), parseInt(result[2],16), parseInt(result[3],16)];
// Look for #fff
if (result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(color))
return [parseInt(result[1]+result[1],16), parseInt(result[2]+result[2],16), parseInt(result[3]+result[3],16)];
// Look for rgba(0, 0, 0, 0) == transparent in Safari 3
if (result = /rgba\(0, 0, 0, 0\)/.exec(color))
return colors['transparent'];
// Otherwise, we're most likely dealing with a named color
return colors[$.trim(color).toLowerCase()];
}
function getColor(elem, attr) {
var color;
do {
color = $.curCSS(elem, attr);
// Keep going until we find an element that has color, or we hit the body
if ( color != '' && color != 'transparent' || $.nodeName(elem, "body") )
break;
attr = "backgroundColor";
} while ( elem = elem.parentNode );
return getRGB(color);
};
// Some named colors to work with
// From Interface by Stefan Petre
// http://interface.eyecon.ro/
var colors = {
aqua:[0,255,255],
azure:[240,255,255],
beige:[245,245,220],
black:[0,0,0],
blue:[0,0,255],
brown:[165,42,42],
cyan:[0,255,255],
darkblue:[0,0,139],
darkcyan:[0,139,139],
darkgrey:[169,169,169],
darkgreen:[0,100,0],
darkkhaki:[189,183,107],
darkmagenta:[139,0,139],
darkolivegreen:[85,107,47],
darkorange:[255,140,0],
darkorchid:[153,50,204],
darkred:[139,0,0],
darksalmon:[233,150,122],
darkviolet:[148,0,211],
fuchsia:[255,0,255],
gold:[255,215,0],
green:[0,128,0],
indigo:[75,0,130],
khaki:[240,230,140],
lightblue:[173,216,230],
lightcyan:[224,255,255],
lightgreen:[144,238,144],
lightgrey:[211,211,211],
lightpink:[255,182,193],
lightyellow:[255,255,224],
lime:[0,255,0],
magenta:[255,0,255],
maroon:[128,0,0],
navy:[0,0,128],
olive:[128,128,0],
orange:[255,165,0],
pink:[255,192,203],
purple:[128,0,128],
violet:[128,0,128],
red:[255,0,0],
silver:[192,192,192],
white:[255,255,255],
yellow:[255,255,0],
transparent: [255,255,255]
};
/******************************************************************************/
/****************************** CLASS ANIMATIONS ******************************/
/******************************************************************************/
var classAnimationActions = ['add', 'remove', 'toggle'],
shorthandStyles = {
border: 1,
borderBottom: 1,
borderColor: 1,
borderLeft: 1,
borderRight: 1,
borderTop: 1,
borderWidth: 1,
margin: 1,
padding: 1
};
function getElementStyles() {
var style = document.defaultView
? document.defaultView.getComputedStyle(this, null)
: this.currentStyle,
newStyle = {},
key,
camelCase;
// webkit enumerates style porperties
if (style && style.length && style[0] && style[style[0]]) {
var len = style.length;
while (len--) {
key = style[len];
if (typeof style[key] == 'string') {
camelCase = key.replace(/\-(\w)/g, function(all, letter){
return letter.toUpperCase();
});
newStyle[camelCase] = style[key];
}
}
} else {
for (key in style) {
if (typeof style[key] === 'string') {
newStyle[key] = style[key];
}
}
}
return newStyle;
}
function filterStyles(styles) {
var name, value;
for (name in styles) {
value = styles[name];
if (
// ignore null and undefined values
value == null ||
// ignore functions (when does this occur?)
$.isFunction(value) ||
// shorthand styles that need to be expanded
name in shorthandStyles ||
// ignore scrollbars (break in IE)
(/scrollbar/).test(name) ||
// only colors or values that can be converted to numbers
(!(/color/i).test(name) && isNaN(parseFloat(value)))
) {
delete styles[name];
}
}
return styles;
}
function styleDifference(oldStyle, newStyle) {
var diff = { _: 0 }, // http://dev.jquery.com/ticket/5459
name;
for (name in newStyle) {
if (oldStyle[name] != newStyle[name]) {
diff[name] = newStyle[name];
}
}
return diff;
}
$.effects.animateClass = function(value, duration, easing, callback) {
if ($.isFunction(easing)) {
callback = easing;
easing = null;
}
return this.queue(function() {
var that = $(this),
originalStyleAttr = that.attr('style') || ' ',
originalStyle = filterStyles(getElementStyles.call(this)),
newStyle,
className = that.attr('class');
$.each(classAnimationActions, function(i, action) {
if (value[action]) {
that[action + 'Class'](value[action]);
}
});
newStyle = filterStyles(getElementStyles.call(this));
that.attr('class', className);
that.animate(styleDifference(originalStyle, newStyle), {
queue: false,
duration: duration,
easing: easing,
complete: function() {
$.each(classAnimationActions, function(i, action) {
if (value[action]) { that[action + 'Class'](value[action]); }
});
// work around bug in IE by clearing the cssText before setting it
if (typeof that.attr('style') == 'object') {
that.attr('style').cssText = '';
that.attr('style').cssText = originalStyleAttr;
} else {
that.attr('style', originalStyleAttr);
}
if (callback) { callback.apply(this, arguments); }
$.dequeue( this );
}
});
});
};
$.fn.extend({
_addClass: $.fn.addClass,
addClass: function(classNames, speed, easing, callback) {
return speed ? $.effects.animateClass.apply(this, [{ add: classNames },speed,easing,callback]) : this._addClass(classNames);
},
_removeClass: $.fn.removeClass,
removeClass: function(classNames,speed,easing,callback) {
return speed ? $.effects.animateClass.apply(this, [{ remove: classNames },speed,easing,callback]) : this._removeClass(classNames);
},
_toggleClass: $.fn.toggleClass,
toggleClass: function(classNames, force, speed, easing, callback) {
if ( typeof force == "boolean" || force === undefined ) {
if ( !speed ) {
// without speed parameter;
return this._toggleClass(classNames, force);
} else {
return $.effects.animateClass.apply(this, [(force?{add:classNames}:{remove:classNames}),speed,easing,callback]);
}
} else {
// without switch parameter;
return $.effects.animateClass.apply(this, [{ toggle: classNames },force,speed,easing]);
}
},
switchClass: function(remove,add,speed,easing,callback) {
return $.effects.animateClass.apply(this, [{ add: add, remove: remove },speed,easing,callback]);
}
});
/******************************************************************************/
/*********************************** EFFECTS **********************************/
/******************************************************************************/
$.extend($.effects, {
version: "1.8.16",
// Saves a set of properties in a data storage
save: function(element, set) {
for(var i=0; i < set.length; i++) {
if(set[i] !== null) element.data("ec.storage."+set[i], element[0].style[set[i]]);
}
},
// Restores a set of previously saved properties from a data storage
restore: function(element, set) {
for(var i=0; i < set.length; i++) {
if(set[i] !== null) element.css(set[i], element.data("ec.storage."+set[i]));
}
},
setMode: function(el, mode) {
if (mode == 'toggle') mode = el.is(':hidden') ? 'show' : 'hide'; // Set for toggle
return mode;
},
getBaseline: function(origin, original) { // Translates a [top,left] array into a baseline value
// this should be a little more flexible in the future to handle a string & hash
var y, x;
switch (origin[0]) {
case 'top': y = 0; break;
case 'middle': y = 0.5; break;
case 'bottom': y = 1; break;
default: y = origin[0] / original.height;
};
switch (origin[1]) {
case 'left': x = 0; break;
case 'center': x = 0.5; break;
case 'right': x = 1; break;
default: x = origin[1] / original.width;
};
return {x: x, y: y};
},
// Wraps the element around a wrapper that copies position properties
createWrapper: function(element) {
// if the element is already wrapped, return it
if (element.parent().is('.ui-effects-wrapper')) {
return element.parent();
}
// wrap the element
var props = {
width: element.outerWidth(true),
height: element.outerHeight(true),
'float': element.css('float')
},
wrapper = $('<div></div>')
.addClass('ui-effects-wrapper')
.css({
fontSize: '100%',
background: 'transparent',
border: 'none',
margin: 0,
padding: 0
}),
active = document.activeElement;
element.wrap(wrapper);
// Fixes #7595 - Elements lose focus when wrapped.
if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {
$( active ).focus();
}
wrapper = element.parent(); //Hotfix for jQuery 1.4 since some change in wrap() seems to actually loose the reference to the wrapped element
// transfer positioning properties to the wrapper
if (element.css('position') == 'static') {
wrapper.css({ position: 'relative' });
element.css({ position: 'relative' });
} else {
$.extend(props, {
position: element.css('position'),
zIndex: element.css('z-index')
});
$.each(['top', 'left', 'bottom', 'right'], function(i, pos) {
props[pos] = element.css(pos);
if (isNaN(parseInt(props[pos], 10))) {
props[pos] = 'auto';
}
});
element.css({position: 'relative', top: 0, left: 0, right: 'auto', bottom: 'auto' });
}
return wrapper.css(props).show();
},
removeWrapper: function(element) {
var parent,
active = document.activeElement;
if (element.parent().is('.ui-effects-wrapper')) {
parent = element.parent().replaceWith(element);
// Fixes #7595 - Elements lose focus when wrapped.
if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {
$( active ).focus();
}
return parent;
}
return element;
},
setTransition: function(element, list, factor, value) {
value = value || {};
$.each(list, function(i, x){
unit = element.cssUnit(x);
if (unit[0] > 0) value[x] = unit[0] * factor + unit[1];
});
return value;
}
});
function _normalizeArguments(effect, options, speed, callback) {
// shift params for method overloading
if (typeof effect == 'object') {
callback = options;
speed = null;
options = effect;
effect = options.effect;
}
if ($.isFunction(options)) {
callback = options;
speed = null;
options = {};
}
if (typeof options == 'number' || $.fx.speeds[options]) {
callback = speed;
speed = options;
options = {};
}
if ($.isFunction(speed)) {
callback = speed;
speed = null;
}
options = options || {};
speed = speed || options.duration;
speed = $.fx.off ? 0 : typeof speed == 'number'
? speed : speed in $.fx.speeds ? $.fx.speeds[speed] : $.fx.speeds._default;
callback = callback || options.complete;
return [effect, options, speed, callback];
}
function standardSpeed( speed ) {
// valid standard speeds
if ( !speed || typeof speed === "number" || $.fx.speeds[ speed ] ) {
return true;
}
// invalid strings - treat as "normal" speed
if ( typeof speed === "string" && !$.effects[ speed ] ) {
return true;
}
return false;
}
$.fn.extend({
effect: function(effect, options, speed, callback) {
var args = _normalizeArguments.apply(this, arguments),
// TODO: make effects take actual parameters instead of a hash
args2 = {
options: args[1],
duration: args[2],
callback: args[3]
},
mode = args2.options.mode,
effectMethod = $.effects[effect];
if ( $.fx.off || !effectMethod ) {
// delegate to the original method (e.g., .show()) if possible
if ( mode ) {
return this[ mode ]( args2.duration, args2.callback );
} else {
return this.each(function() {
if ( args2.callback ) {
args2.callback.call( this );
}
});
}
}
return effectMethod.call(this, args2);
},
_show: $.fn.show,
show: function(speed) {
if ( standardSpeed( speed ) ) {
return this._show.apply(this, arguments);
} else {
var args = _normalizeArguments.apply(this, arguments);
args[1].mode = 'show';
return this.effect.apply(this, args);
}
},
_hide: $.fn.hide,
hide: function(speed) {
if ( standardSpeed( speed ) ) {
return this._hide.apply(this, arguments);
} else {
var args = _normalizeArguments.apply(this, arguments);
args[1].mode = 'hide';
return this.effect.apply(this, args);
}
},
// jQuery core overloads toggle and creates _toggle
__toggle: $.fn.toggle,
toggle: function(speed) {
if ( standardSpeed( speed ) || typeof speed === "boolean" || $.isFunction( speed ) ) {
return this.__toggle.apply(this, arguments);
} else {
var args = _normalizeArguments.apply(this, arguments);
args[1].mode = 'toggle';
return this.effect.apply(this, args);
}
},
// helper functions
cssUnit: function(key) {
var style = this.css(key), val = [];
$.each( ['em','px','%','pt'], function(i, unit){
if(style.indexOf(unit) > 0)
val = [parseFloat(style), unit];
});
return val;
}
});
/******************************************************************************/
/*********************************** EASING ***********************************/
/******************************************************************************/
/*
* jQuery Easing v1.3 - http://gsgd.co.uk/sandbox/jquery/easing/
*
* Uses the built in easing capabilities added In jQuery 1.1
* to offer multiple easing options
*
* TERMS OF USE - jQuery Easing
*
* Open source under the BSD License.
*
* Copyright 2008 George McGinley Smith
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* Neither the name of the author nor the names of contributors may be used to endorse
* or promote products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
// t: current time, b: begInnIng value, c: change In value, d: duration
$.easing.jswing = $.easing.swing;
$.extend($.easing,
{
def: 'easeOutQuad',
swing: function (x, t, b, c, d) {
//alert($.easing.default);
return $.easing[$.easing.def](x, t, b, c, d);
},
easeInQuad: function (x, t, b, c, d) {
return c*(t/=d)*t + b;
},
easeOutQuad: function (x, t, b, c, d) {
return -c *(t/=d)*(t-2) + b;
},
easeInOutQuad: function (x, t, b, c, d) {
if ((t/=d/2) < 1) return c/2*t*t + b;
return -c/2 * ((--t)*(t-2) - 1) + b;
},
easeInCubic: function (x, t, b, c, d) {
return c*(t/=d)*t*t + b;
},
easeOutCubic: function (x, t, b, c, d) {
return c*((t=t/d-1)*t*t + 1) + b;
},
easeInOutCubic: function (x, t, b, c, d) {
if ((t/=d/2) < 1) return c/2*t*t*t + b;
return c/2*((t-=2)*t*t + 2) + b;
},
easeInQuart: function (x, t, b, c, d) {
return c*(t/=d)*t*t*t + b;
},
easeOutQuart: function (x, t, b, c, d) {
return -c * ((t=t/d-1)*t*t*t - 1) + b;
},
easeInOutQuart: function (x, t, b, c, d) {
if ((t/=d/2) < 1) return c/2*t*t*t*t + b;
return -c/2 * ((t-=2)*t*t*t - 2) + b;
},
easeInQuint: function (x, t, b, c, d) {
return c*(t/=d)*t*t*t*t + b;
},
easeOutQuint: function (x, t, b, c, d) {
return c*((t=t/d-1)*t*t*t*t + 1) + b;
},
easeInOutQuint: function (x, t, b, c, d) {
if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b;
return c/2*((t-=2)*t*t*t*t + 2) + b;
},
easeInSine: function (x, t, b, c, d) {
return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
},
easeOutSine: function (x, t, b, c, d) {
return c * Math.sin(t/d * (Math.PI/2)) + b;
},
easeInOutSine: function (x, t, b, c, d) {
return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
},
easeInExpo: function (x, t, b, c, d) {
return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
},
easeOutExpo: function (x, t, b, c, d) {
return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
},
easeInOutExpo: function (x, t, b, c, d) {
if (t==0) return b;
if (t==d) return b+c;
if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
},
easeInCirc: function (x, t, b, c, d) {
return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b;
},
easeOutCirc: function (x, t, b, c, d) {
return c * Math.sqrt(1 - (t=t/d-1)*t) + b;
},
easeInOutCirc: function (x, t, b, c, d) {
if ((t/=d/2) < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b;
return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b;
},
easeInElastic: function (x, t, b, c, d) {
var s=1.70158;var p=0;var a=c;
if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3;
if (a < Math.abs(c)) { a=c; var s=p/4; }
else var s = p/(2*Math.PI) * Math.asin (c/a);
return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
},
easeOutElastic: function (x, t, b, c, d) {
var s=1.70158;var p=0;var a=c;
if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3;
if (a < Math.abs(c)) { a=c; var s=p/4; }
else var s = p/(2*Math.PI) * Math.asin (c/a);
return a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b;
},
easeInOutElastic: function (x, t, b, c, d) {
var s=1.70158;var p=0;var a=c;
if (t==0) return b; if ((t/=d/2)==2) return b+c; if (!p) p=d*(.3*1.5);
if (a < Math.abs(c)) { a=c; var s=p/4; }
else var s = p/(2*Math.PI) * Math.asin (c/a);
if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b;
},
easeInBack: function (x, t, b, c, d, s) {
if (s == undefined) s = 1.70158;
return c*(t/=d)*t*((s+1)*t - s) + b;
},
easeOutBack: function (x, t, b, c, d, s) {
if (s == undefined) s = 1.70158;
return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
},
easeInOutBack: function (x, t, b, c, d, s) {
if (s == undefined) s = 1.70158;
if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b;
return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b;
},
easeInBounce: function (x, t, b, c, d) {
return c - $.easing.easeOutBounce (x, d-t, 0, c, d) + b;
},
easeOutBounce: function (x, t, b, c, d) {
if ((t/=d) < (1/2.75)) {
return c*(7.5625*t*t) + b;
} else if (t < (2/2.75)) {
return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b;
} else if (t < (2.5/2.75)) {
return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b;
} else {
return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b;
}
},
easeInOutBounce: function (x, t, b, c, d) {
if (t < d/2) return $.easing.easeInBounce (x, t*2, 0, c, d) * .5 + b;
return $.easing.easeOutBounce (x, t*2-d, 0, c, d) * .5 + c*.5 + b;
}
});
/*
*
* TERMS OF USE - EASING EQUATIONS
*
* Open source under the BSD License.
*
* Copyright 2001 Robert Penner
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* Neither the name of the author nor the names of contributors may be used to endorse
* or promote products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
})(jQuery);

View File

@ -1,9 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>AdminSortable Test Project</title>
</head>
<body>
</body>
</html>

View File

@ -1,16 +0,0 @@
from django.conf import settings
from django.conf.urls.defaults import patterns, include
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
(r'^admin/doc/', include('django.contrib.admindocs.urls')),
(r'^admin/', include(admin.site.urls)),
)
if settings.SERVE_STATIC_MEDIA:
urlpatterns += patterns('',
(r'^' + settings.MEDIA_URL.lstrip('/'), include('appmedia.urls')),
) + urlpatterns