Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge pull request #2 from django-extensions/master

Latest
  • Loading branch information...
commit bae0c10cfc36771bc80720323a44a3b8dadcb821 2 parents 93fc3a5 + 7f2d542
@pombredanne authored
Showing with 817 additions and 543 deletions.
  1. +1 −0  .gitignore
  2. +1 −1  MANIFEST.in
  3. +1 −1  README.rst
  4. +1 −1  django_extensions/__init__.py
  5. +18 −13 django_extensions/admin/widgets.py
  6. +11 −9 django_extensions/db/fields/__init__.py
  7. +4 −1 django_extensions/db/fields/json.py
  8. +7 −2 django_extensions/db/models.py
  9. +5 −2 django_extensions/management/commands/clean_pyc.py
  10. +76 −64 django_extensions/management/commands/dumpscript.py
  11. +46 −8 django_extensions/management/commands/mail_debug.py
  12. +8 −3 django_extensions/management/commands/notes.py
  13. +50 −8 django_extensions/management/commands/runserver_plus.py
  14. +25 −54 django_extensions/management/commands/shell_plus.py
  15. +73 −7 django_extensions/management/commands/sync_media_s3.py
  16. +25 −7 django_extensions/management/commands/validate_templates.py
  17. +6 −0 django_extensions/management/notebook_extension.py
  18. +56 −0 django_extensions/management/shells.py
  19. +30 −0 django_extensions/management/utils.py
  20. 0  django_extensions/{media → static}/django_extensions/css/jquery.autocomplete.css
  21. 0  django_extensions/{media → static}/django_extensions/img/indicator.gif
  22. +24 −21 django_extensions/{media → static}/django_extensions/js/jquery.ajaxQueue.js
  23. +65 −62 django_extensions/{media → static}/django_extensions/js/jquery.autocomplete.js
  24. +2 −2 django_extensions/{media → static}/django_extensions/js/jquery.bgiframe.min.js
  25. 0  django_extensions/{media → static}/django_extensions/js/jquery.js
  26. +3 −3 django_extensions/templates/django_extensions/widgets/foreignkey_searchinput.html
  27. +9 −3 django_extensions/templatetags/truncate_letters.py
  28. +1 −1  django_extensions/tests/__init__.py
  29. +89 −0 django_extensions/tests/fields.py
  30. +27 −0 django_extensions/tests/test_dumpscript.py
  31. +0 −57 django_extensions/tests/test_print_settings.py
  32. +82 −0 django_extensions/utils/validatingtemplatetags.py
  33. +2 −2 docs/conf.py
  34. +28 −0 docs/dumpscript.rst
  35. +9 −5 docs/graph_models.rst
  36. +2 −2 docs/installation_instructions.rst
  37. +29 −0 docs/shell_plus.rst
  38. 0  example_project/example_project/__init__.py
  39. +0 −149 example_project/example_project/settings.py
  40. +0 −17 example_project/example_project/urls.py
  41. +0 −28 example_project/example_project/wsgi.py
  42. +0 −10 example_project/manage.py
  43. +1 −0  run_tests.py
View
1  .gitignore
@@ -8,3 +8,4 @@ docs/_build
docs/_static
*.egg-info
.tox
+*.bak
View
2  MANIFEST.in
@@ -1,4 +1,4 @@
recursive-include django_extensions/conf *.tmpl
recursive-include django_extensions/templates *.html
-recursive-include django_extensions/media *
+recursive-include django_extensions/static *
recursive-include docs *
View
2  README.rst
@@ -12,7 +12,7 @@ The easiest way to figure out what Django Extensions are all about is to watch t
Getting It
==========
-You can get Django Extentions by using pip or easy_install::
+You can get Django Extensions by using pip or easy_install::
$ pip install django-extensions
or
View
2  django_extensions/__init__.py
@@ -1,5 +1,5 @@
-VERSION = (0, 9, 1, 'beta')
+VERSION = (1, 0, 2, 'pre')
# Dynamically calculate the version based on VERSION tuple
if len(VERSION) > 2 and VERSION[2] is not None:
View
31 django_extensions/admin/widgets.py
@@ -1,5 +1,7 @@
-from django import forms, VERSION
+import django
+from django import forms
from django.conf import settings
+from django.contrib.admin.sites import site
from django.utils.safestring import mark_safe
from django.utils.text import truncate_words
from django.template.loader import render_to_string
@@ -16,16 +18,16 @@ class ForeignKeySearchInput(ForeignKeyRawIdWidget):
# Set this to the patch of the search view
search_path = '../foreignkey_autocomplete/'
- class Media:
- css = {
- 'all': ('django_extensions/css/jquery.autocomplete.css',)
- }
- js = (
- 'django_extensions/js/jquery.js',
- 'django_extensions/js/jquery.bgiframe.min.js',
- 'django_extensions/js/jquery.ajaxQueue.js',
- 'django_extensions/js/jquery.autocomplete.js',
- )
+ def _media(self):
+ js_files = ['django_extensions/js/jquery.bgiframe.min.js',
+ 'django_extensions/js/jquery.ajaxQueue.js',
+ 'django_extensions/js/jquery.autocomplete.js']
+ if django.get_version() < "1.3":
+ js_files.append('django_extensions/js/jquery.js')
+ return forms.Media(css={'all': ('django_extensions/css/jquery.autocomplete.css',)},
+ js=js_files)
+
+ media = property(_media)
def label_for_value(self, value):
key = self.rel.get_related_field().name
@@ -34,7 +36,10 @@ def label_for_value(self, value):
def __init__(self, rel, search_fields, attrs=None):
self.search_fields = search_fields
- super(ForeignKeySearchInput, self).__init__(rel, attrs)
+ if django.get_version() >= "1.4":
+ super(ForeignKeySearchInput, self).__init__(rel, site, attrs)
+ else:
+ super(ForeignKeySearchInput, self).__init__(rel, attrs)
def render(self, name, value, attrs=None):
if attrs is None:
@@ -73,7 +78,7 @@ def render(self, name, value, attrs=None):
'app_label': app_label,
'label': label,
'name': name,
- 'pre_django_14': (VERSION[:2]<(1,4)),
+ 'pre_django_14': (django.VERSION[:2]<(1,4)),
}
output.append(render_to_string(self.widget_template or (
'django_extensions/widgets/%s/%s/foreignkey_searchinput.html' % (app_label, model_name),
View
20 django_extensions/db/fields/__init__.py
@@ -82,15 +82,11 @@ def create_slug(self, model_instance, add):
slug = self.separator.join(map(slug_for_field, self._populate_from))
next = 2
else:
- # get slug from the current model instance and calculate next
- # step from its number, clean-up
- slug = self._slug_strip(getattr(model_instance, self.attname))
- next = slug.split(self.separator)[-1]
- if next.isdigit() and not self.allow_duplicates:
- slug = self.separator.join(slug.split(self.separator)[:-1])
- next = int(next)
- else:
- next = 2
+ # get slug from the current model instance
+ slug = getattr(model_instance, self.attname)
+ # model_instance is being modified, and overwrite is False,
+ # so instead of doing anything, just return the current slug
+ return slug
# strip slug depending on max_length attribute of the slug field
# and clean-up
@@ -220,6 +216,7 @@ class UUIDField(CharField):
def __init__(self, verbose_name=None, name=None, auto=True, version=1, node=None, clock_seq=None, namespace=None, **kwargs):
kwargs['max_length'] = 36
if auto:
+ self.empty_strings_allowed = False
kwargs['blank'] = True
kwargs.setdefault('editable', False)
self.auto = auto
@@ -269,6 +266,11 @@ def pre_save(self, model_instance, add):
value = unicode(self.create_uuid())
setattr(model_instance, self.attname, value)
return value
+
+ def formfield(self, **kwargs):
+ if self.auto:
+ return None
+ super(UUIDField, self).formfield(**kwargs)
def south_field_triple(self):
"Returns a suitable description of this field for South."
View
5 django_extensions/db/fields/json.py
@@ -65,8 +65,11 @@ class JSONField(models.TextField):
__metaclass__ = models.SubfieldBase
def __init__(self, *args, **kwargs):
- if 'default' not in kwargs:
+ default = kwargs.get('default')
+ if not default:
kwargs['default'] = '{}'
+ elif isinstance(default, (list, dict)):
+ kwargs['default'] = dumps(default)
models.TextField.__init__(self, *args, **kwargs)
def to_python(self, value):
View
9 django_extensions/db/models.py
@@ -1,12 +1,17 @@
"""
Django Extensions abstract base model classes.
"""
-import datetime
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django_extensions.db.fields import (ModificationDateTimeField,
CreationDateTimeField, AutoSlugField)
+try:
+ from django.utils.timezone import now as datetime_now
+except ImportError:
+ import datetime
+ datetime_now = datetime.datetime.now
+
class TimeStampedModel(models.Model):
""" TimeStampedModel
@@ -71,5 +76,5 @@ class Meta:
def save(self, *args, **kwargs):
if not self.activate_date:
- self.activate_date = datetime.datetime.now()
+ self.activate_date = datetime_now()
super(ActivatorModel, self).save(*args, **kwargs)
View
7 django_extensions/management/commands/clean_pyc.py
@@ -22,14 +22,17 @@ def handle_noargs(self, **options):
if not project_root:
project_root = get_project_root()
exts = options.get("optimize", False) and [".pyc", ".pyo"] or [".pyc"]
- verbose = int(options.get("verbosity", 1)) > 1
+ verbose = int(options.get("verbosity", 1))
+
+ if verbose > 1:
+ print "Project Root: %s" % project_root
for root, dirs, files in os.walk(project_root):
for file in files:
ext = os.path.splitext(file)[1]
if ext in exts:
full_path = _j(root, file)
- if verbose:
+ if verbose > 1:
print full_path
os.remove(full_path)
View
140 django_extensions/management/commands/dumpscript.py
@@ -97,7 +97,8 @@ def handle(self, *app_labels, **options):
context = {}
# Create a dumpscript object and let it format itself as a string
- print Script(models=models, context=context)
+ self.stdout.write(str(Script(models=models, context=context, stdout=self.stdout, stderr=self.stderr)))
+ self.stdout.write("\n")
def get_models(app_labels):
@@ -141,15 +142,20 @@ class Code(object):
in this class.
"""
- def __init__(self):
- self.imports = {}
- self.indent = -1
+ def __init__(self, indent=-1, stdout=None, stderr=None):
+
+ if not stdout: stdout = sys.stdout
+ if not stderr: stderr= sys.stderr
+
+ self.indent = indent
+ self.stdout = stdout
+ self.stderr = stderr
def __str__(self):
""" Returns a string representation of this script.
"""
if self.imports:
- sys.stderr.write(repr(self.import_lines))
+ self.stderr.write(repr(self.import_lines))
return flatten_blocks([""] + self.import_lines + [""] + self.lines, num_indents=self.indent)
else:
return flatten_blocks(self.lines, num_indents=self.indent)
@@ -167,11 +173,12 @@ def get_import_lines(self):
class ModelCode(Code):
" Produces a python script that can recreate data for a given model class. "
- def __init__(self, model, context={}):
+ def __init__(self, model, context={},stdout=None, stderr=None):
+ super(ModelCode,self).__init__(indent=0,stdout=stdout, stderr=stderr)
self.model = model
self.context = context
self.instances = []
- self.indent = 0
+
def get_imports(self):
""" Returns a dictionary of import statements, with the variable being
@@ -187,7 +194,7 @@ def get_lines(self):
code = []
for counter, item in enumerate(self.model._default_manager.all()):
- instance = InstanceCode(instance=item, id=counter + 1, context=self.context)
+ instance = InstanceCode(instance=item, id=counter + 1, context=self.context, stdout=self.stdout, stderr=self.stderr)
self.instances.append(instance)
if instance.waiting_list:
code += instance.lines
@@ -206,9 +213,12 @@ def get_lines(self):
class InstanceCode(Code):
" Produces a python script that can recreate data for a given model instance. "
- def __init__(self, instance, id, context={}):
+ def __init__(self, instance, id, context={}, stdout=None, stderr=None):
""" We need the instance in question and an id """
+ super(InstanceCode,self).__init__(indent=0,stdout=stdout, stderr=stderr)
+ self.imports = {}
+
self.instance = instance
self.model = self.instance.__class__
self.context = context
@@ -216,9 +226,6 @@ def __init__(self, instance, id, context={}):
self.skip_me = None
self.instantiated = False
- self.indent = 0
- self.imports = {}
-
self.waiting_list = list(self.model._meta.fields)
self.many_to_many_waiting_list = {}
@@ -404,16 +411,66 @@ def get_many_to_many_lines(self, force=False):
class Script(Code):
" Produces a complete python script that can recreate data for the given apps. "
- def __init__(self, models, context={}):
- self.models = models
- self.context = context
+ def __init__(self, models, context={}, stdout=None, stderr=None):
- self.indent = -1
+ super(Script,self).__init__(stdout=stdout, stderr=stderr)
self.imports = {}
+ self.models = models
+ self.context = context
+
self.context["__avaliable_models"] = set(models)
self.context["__extra_imports"] = {}
+
+ def _queue_models(self, models, context):
+ """ Works an an appropriate ordering for the models.
+ This isn't essential, but makes the script look nicer because
+ more instances can be defined on their first try.
+ """
+
+ # Max number of cycles allowed before we call it an infinite loop.
+ MAX_CYCLES = 5
+
+ model_queue = []
+ number_remaining_models = len(models)
+ allowed_cycles = MAX_CYCLES
+
+ while number_remaining_models > 0:
+ previous_number_remaining_models = number_remaining_models
+
+ model = models.pop(0)
+
+ # If the model is ready to be processed, add it to the list
+ if check_dependencies(model, model_queue, context["__avaliable_models"]):
+ model_class = ModelCode(model=model, context=context, stdout=self.stdout, stderr=self.stderr)
+ model_queue.append(model_class)
+
+ # Otherwise put the model back at the end of the list
+ else:
+ models.append(model)
+
+ # Check for infinite loops.
+ # This means there is a cyclic foreign key structure
+ # That cannot be resolved by re-ordering
+ number_remaining_models = len(models)
+ if number_remaining_models == previous_number_remaining_models:
+ allowed_cycles -= 1
+ if allowed_cycles <= 0:
+ # Add the remaining models, but do not remove them from the model list
+ missing_models = [ModelCode(model=m, context=context, stdout=self.stdout, stderr=self.stderr) for m in models]
+ model_queue += missing_models
+ # Replace the models with the model class objects
+ # (sure, this is a little bit of hackery)
+ models[:] = missing_models
+ break
+ else:
+ allowed_cycles = MAX_CYCLES
+
+ return model_queue
+
+
+
def get_lines(self):
""" Returns a list of lists or strings, representing the code body.
Each list is a block, each string is a statement.
@@ -421,9 +478,9 @@ def get_lines(self):
code = [self.FILE_HEADER.strip()]
# Queue and process the required models
- for model_class in queue_models(self.models, context=self.context):
+ for model_class in self._queue_models(self.models, context=self.context):
msg = 'Processing model: %s\n' % model_class.model.__name__
- sys.stderr.write(msg)
+ self.stderr.write(msg)
code.append(" #"+msg)
code.append(model_class.import_lines)
code.append("")
@@ -432,7 +489,7 @@ def get_lines(self):
# Process left over foreign keys from cyclic models
for model in self.models:
msg = 'Re-processing model: %s\n' % model.model.__name__
- sys.stderr.write(msg)
+ self.stderr.write(msg)
code.append(" #"+msg)
for instance in model.instances:
if instance.waiting_list or instance.many_to_many_waiting_list:
@@ -592,51 +649,6 @@ def make_clean_dict(the_dict):
return the_dict
-def queue_models(models, context):
- """ Works an an appropriate ordering for the models.
- This isn't essential, but makes the script look nicer because
- more instances can be defined on their first try.
- """
-
- # Max number of cycles allowed before we call it an infinite loop.
- MAX_CYCLES = 5
-
- model_queue = []
- number_remaining_models = len(models)
- allowed_cycles = MAX_CYCLES
-
- while number_remaining_models > 0:
- previous_number_remaining_models = number_remaining_models
-
- model = models.pop(0)
-
- # If the model is ready to be processed, add it to the list
- if check_dependencies(model, model_queue, context["__avaliable_models"]):
- model_class = ModelCode(model=model, context=context)
- model_queue.append(model_class)
-
- # Otherwise put the model back at the end of the list
- else:
- models.append(model)
-
- # Check for infinite loops.
- # This means there is a cyclic foreign key structure
- # That cannot be resolved by re-ordering
- number_remaining_models = len(models)
- if number_remaining_models == previous_number_remaining_models:
- allowed_cycles -= 1
- if allowed_cycles <= 0:
- # Add the remaining models, but do not remove them from the model list
- missing_models = [ModelCode(model=m, context=context) for m in models]
- model_queue += missing_models
- # Replace the models with the model class objects
- # (sure, this is a little bit of hackery)
- models[:] = missing_models
- break
- else:
- allowed_cycles = MAX_CYCLES
-
- return model_queue
def check_dependencies(model, model_queue, avaliable_models):
View
54 django_extensions/management/commands/mail_debug.py
@@ -1,10 +1,40 @@
-from django.core.management.base import BaseCommand
+from django_extensions.management.utils import setup_logger
+from django.core.management.base import BaseCommand, CommandError
+from optparse import make_option
+from smtpd import SMTPServer
import sys
-import smtpd
import asyncore
+from logging import getLogger
+
+
+logger = getLogger(__name__)
+
+
+class ExtensionDebuggingServer(SMTPServer):
+ """Duplication of smtpd.DebuggingServer, but using logging instead of print."""
+ # Do something with the gathered message
+ def process_message(self, peer, mailfrom, rcpttos, data):
+ """Output will be sent to the module logger at INFO level."""
+ inheaders = 1
+ lines = data.split('\n')
+ logger.info('---------- MESSAGE FOLLOWS ----------')
+ for line in lines:
+ # headers first
+ if inheaders and not line:
+ logger.info('X-Peer: %s' % peer[0])
+ inheaders = 0
+ logger.info(line)
+ logger.info('------------ END MESSAGE ------------')
class Command(BaseCommand):
+ option_list = BaseCommand.option_list + (
+ make_option('--output', dest='output_file', default=None,
+ help='Specifies an output file to send a copy of all messages (not flushed immediately).'),
+ make_option('--use-settings', dest='use_settings',
+ action='store_true', default=False,
+ help='Uses EMAIL_HOST and HOST_PORT from Django settings.'),
+ )
help = "Starts a test mail server for development."
args = '[optional port number or ippaddr:port]'
@@ -12,10 +42,15 @@ class Command(BaseCommand):
def handle(self, addrport='', *args, **options):
if args:
- raise CommandError('Usage is runserver %s' % self.args)
+ raise CommandError('Usage is mail_debug %s' % self.args)
if not addrport:
- addr = ''
- port = '1025'
+ if options.get('use_settings', False):
+ from django.conf import settings
+ addr = getattr(settings, 'EMAIL_HOST', '')
+ port = str(getattr(settings, 'EMAIL_PORT', '1025'))
+ else:
+ addr = ''
+ port = '1025'
else:
try:
addr, port = addrport.split(':')
@@ -29,11 +64,14 @@ def handle(self, addrport='', *args, **options):
else:
port = int(port)
- quit_command = (sys.platform == 'win32') and 'CTRL-BREAK' or 'CONTROL-C'
+ # Add console handler
+ setup_logger(logger, stream=self.stdout, filename=options.get('output_file', None))
def inner_run():
- print "Now accepting mail at %s:%s" % (addr, port)
- server = smtpd.DebuggingServer((addr, port), None)
+ quit_command = (sys.platform == 'win32') and 'CTRL-BREAK' or 'CONTROL-C'
+ print "Now accepting mail at %s:%s -- use %s to quit" % (addr, port, quit_command)
+
+ ExtensionDebuggingServer((addr, port), None)
asyncore.loop()
try:
View
11 django_extensions/management/commands/notes.py
@@ -4,10 +4,11 @@
import os
import re
-ANNOTATION_RE = re.compile("#[\s]*?(TODO|FIXME|HACK|XXX)[\s:]?(.+)")
+ANNOTATION_RE = re.compile("\{?#[\s]*?(TODO|FIXME|HACK|XXX)[\s:]?(.+)")
+ANNOTATION_END_RE = re.compile("(.*)#\}(.*)")
class Command(BaseCommand):
- help = 'Show all annotations like TODO, FIXME, HACK or XXX in your py files.'
+ help = 'Show all annotations like TODO, FIXME, HACK or XXX in your py and HTML files.'
args = 'tag'
label = 'annotation tag (TODO, FIXME, HACK, XXX)'
@@ -18,7 +19,7 @@ def handle(self, *args, **options):
for app_dir in apps:
for top, dirs, files in os.walk(app_dir):
for f in files:
- if os.path.splitext(f)[1] in ['.py']:
+ if os.path.splitext(f)[1] in ['.py','.html']:
fpath = os.path.join(top, f)
annotation_lines = []
with open(fpath, 'r') as f:
@@ -31,6 +32,10 @@ def handle(self, *args, **options):
search_for_tag = args[0].upper()
if not search_for_tag == tag:
break
+
+ if ANNOTATION_END_RE.search(msg.strip()):
+ msg = ANNOTATION_END_RE.findall(msg.strip())[0][0]
+
annotation_lines.append("[%3s] %-5s %s" % (i, tag, msg.strip()))
if annotation_lines:
print fpath+":"
View
58 django_extensions/management/commands/runserver_plus.py
@@ -1,8 +1,10 @@
from django.conf import settings
from django.core.management.base import BaseCommand, CommandError
+from django_extensions.management.utils import setup_logger, RedirectHandler
from optparse import make_option
import os
import sys
+import time
try:
from django.contrib.staticfiles.handlers import StaticFilesHandler
@@ -10,6 +12,10 @@
except ImportError, e:
USE_STATICFILES = False
+import logging
+logger = logging.getLogger(__name__)
+
+
def null_technical_500_response(request, exc_type, exc_value, tb):
raise exc_type, exc_value, tb
@@ -17,20 +23,24 @@ def null_technical_500_response(request, exc_type, exc_value, tb):
class Command(BaseCommand):
option_list = BaseCommand.option_list + (
make_option('--noreload', action='store_false', dest='use_reloader', default=True,
- help='Tells Django to NOT use the auto-reloader.'),
+ help='Tells Django to NOT use the auto-reloader.'),
make_option('--browser', action='store_true', dest='open_browser',
- help='Tells Django to open a browser.'),
+ help='Tells Django to open a browser.'),
make_option('--adminmedia', dest='admin_media_path', default='',
- help='Specifies the directory from which to serve admin media.'),
+ help='Specifies the directory from which to serve admin media.'),
make_option('--threaded', action='store_true', dest='threaded',
- help='Run in multithreaded mode.'),
+ help='Run in multithreaded mode.'),
+ make_option('--output', dest='output_file', default=None,
+ help='Specifies an output file to send a copy of all messages (not flushed immediately).'),
+ make_option('--print-sql', action='store_true', default=False,
+ help="Print SQL queries as they're executed"),
)
if USE_STATICFILES:
option_list += (
make_option('--nostatic', action="store_false", dest='use_static_handler', default=True,
- help='Tells Django to NOT automatically serve static files at STATIC_URL.'),
+ help='Tells Django to NOT automatically serve static files at STATIC_URL.'),
make_option('--insecure', action="store_true", dest='insecure_serving', default=False,
- help='Allows serving static files even if DEBUG is False.'),
+ help='Allows serving static files even if DEBUG is False.'),
)
help = "Starts a lightweight Web server for development."
args = '[optional port number, or ipaddr:port]'
@@ -40,7 +50,39 @@ class Command(BaseCommand):
def handle(self, addrport='', *args, **options):
import django
- from django.core.servers.basehttp import run, WSGIServerException
+
+ setup_logger(logger, self.stderr, filename=options.get('output_file', None)) # , fmt="[%(name)s] %(message)s")
+ logredirect = RedirectHandler(__name__)
+
+ # Redirect werkzeug log items
+ werklogger = logging.getLogger('werkzeug')
+ werklogger.setLevel(logging.INFO)
+ werklogger.addHandler(logredirect)
+ werklogger.propagate = False
+
+ if options.get("print_sql", False):
+ from django.db.backends import util
+ try:
+ import sqlparse
+ except ImportError:
+ sqlparse = None # noqa
+
+ class PrintQueryWrapper(util.CursorDebugWrapper):
+ def execute(self, sql, params=()):
+ starttime = time.time()
+ try:
+ return self.cursor.execute(sql, params)
+ finally:
+ raw_sql = self.db.ops.last_executed_query(self.cursor, sql, params)
+ execution_time = time.time() - starttime
+ therest = ' -- [Execution time: %.6fs] [Database: %s]' % (execution_time, self.db.alias)
+ if sqlparse:
+ logger.info(sqlparse.format(raw_sql, reindent=True) + therest)
+ else:
+ logger.info(raw_sql + therest)
+
+ util.CursorDebugWrapper = PrintQueryWrapper
+
try:
from django.core.servers.basehttp import AdminMediaHandler
USE_ADMINMEDIAHANDLER = True
@@ -50,7 +92,7 @@ def handle(self, addrport='', *args, **options):
try:
from django.core.servers.basehttp import get_internal_wsgi_application as WSGIHandler
except ImportError:
- from django.core.handlers.wsgi import WSGIHandler
+ from django.core.handlers.wsgi import WSGIHandler # noqa
try:
from werkzeug import run_simple, DebuggedApplication
except ImportError:
View
79 django_extensions/management/commands/shell_plus.py
@@ -1,5 +1,6 @@
import os
from django.core.management.base import NoArgsCommand
+from django_extensions.management.shells import import_objects
from optparse import make_option
import time
@@ -8,6 +9,8 @@ class Command(NoArgsCommand):
option_list = NoArgsCommand.option_list + (
make_option('--ipython', action='store_true', dest='ipython',
help='Tells Django to use IPython, not BPython.'),
+ make_option('--notebook', action='store_true', dest='notebook',
+ help='Tells Django to use IPython Notebook.'),
make_option('--plain', action='store_true', dest='plain',
help='Tells Django to use plain Python, not BPython nor IPython.'),
make_option('--no-pythonrc', action='store_true', dest='no_pythonrc',
@@ -24,13 +27,8 @@ class Command(NoArgsCommand):
requires_model_validation = True
def handle_noargs(self, **options):
- # XXX: (Temporary) workaround for ticket #1796: force early loading of all
- # models from installed apps. (this is fixed by now, but leaving it here
- # for people using 0.96 or older trunk (pre [5919]) versions.
- from django.db.models.loading import get_models, get_apps
- loaded_models = get_models()
-
- use_ipython = options.get('ipython', False)
+ use_notebook = options.get('notebook', False)
+ use_ipython = options.get('ipython', use_notebook)
use_plain = options.get('plain', False)
use_pythonrc = not options.get('no_pythonrc', True)
@@ -55,7 +53,7 @@ def execute(self, sql, params=()):
else:
print raw_sql
print
- print 'Execution time: %.6fs' % execution_time
+ print 'Execution time: %.6fs [Database: %s]' % (execution_time, self.db.alias)
print
util.CursorDebugWrapper = PrintQueryWrapper
@@ -63,50 +61,6 @@ def execute(self, sql, params=()):
# Set up a dictionary to serve as the environment for the shell, so
# that tab completion works on objects that are imported at runtime.
# See ticket 5082.
- from django.conf import settings
- imported_objects = {'settings': settings}
-
- dont_load_cli = options.get('dont_load') # optparse will set this to [] if it doensnt exists
- dont_load_conf = getattr(settings, 'SHELL_PLUS_DONT_LOAD', [])
- dont_load = dont_load_cli + dont_load_conf
- quiet_load = options.get('quiet_load')
-
- model_aliases = getattr(settings, 'SHELL_PLUS_MODEL_ALIASES', {})
-
- for app_mod in get_apps():
- app_models = get_models(app_mod)
- if not app_models:
- continue
-
- app_name = app_mod.__name__.split('.')[-2]
- if app_name in dont_load:
- continue
-
- app_aliases = model_aliases.get(app_name, {})
- model_labels = []
-
- for model in app_models:
- try:
- imported_object = getattr(__import__(app_mod.__name__, {}, {}, model.__name__), model.__name__)
- model_name = model.__name__
-
- if "%s.%s" % (app_name, model_name) in dont_load:
- continue
-
- alias = app_aliases.get(model_name, model_name)
- imported_objects[alias] = imported_object
- if model_name == alias:
- model_labels.append(model_name)
- else:
- model_labels.append("%s (as %s)" % (model_name, alias))
-
- except AttributeError, e:
- if not quiet_load:
- print self.style.ERROR("Failed to import '%s' from '%s' reason: %s" % (model.__name__, app_name, str(e)))
- continue
- if not quiet_load:
- print self.style.SQL_COLTYPE("From '%s' autoload: %s" % (app_mod.__name__.split('.')[-2], ", ".join(model_labels)))
-
try:
if use_plain:
# Don't bother loading B/IPython, because the user wants plain Python.
@@ -116,17 +70,33 @@ def execute(self, sql, params=()):
# User wants IPython
raise ImportError
from bpython import embed
+ imported_objects = import_objects(options, self.style)
embed(imported_objects)
except ImportError:
try:
- from IPython import embed
- embed(user_ns=imported_objects)
+ if use_notebook:
+ from django.conf import settings
+ from IPython.frontend.html.notebook import notebookapp
+ app = notebookapp.NotebookApp.instance()
+ ipython_arguments = getattr(
+ settings,
+ 'IPYTHON_ARGUMENTS',
+ ['--ext',
+ 'django_extensions.management.notebook_extension'])
+ app.initialize(ipython_arguments)
+ app.start()
+ else:
+ from IPython import embed
+ imported_objects = import_objects(options, self.style)
+ embed(user_ns=imported_objects)
except ImportError:
# IPython < 0.11
# Explicitly pass an empty list as arguments, because otherwise
# IPython would use sys.argv from this script.
+ # Notebook not supported for IPython < 0.11.
try:
from IPython.Shell import IPShell
+ imported_objects = import_objects(options, self.style)
shell = IPShell(argv=[], user_ns=imported_objects)
shell.mainloop()
except ImportError:
@@ -135,6 +105,7 @@ def execute(self, sql, params=()):
except ImportError:
# Using normal Python shell
import code
+ imported_objects = import_objects(options, self.style)
try:
# Try activating rlcompleter, because it's handy.
import readline
View
80 django_extensions/management/commands/sync_media_s3.py
@@ -18,6 +18,12 @@
AWS_SECRET_ACCESS_KEY = ''
AWS_BUCKET_NAME = ''
+When you call this command with the `--renamegzip` param, it will add
+the '.gz' extension to the file name. But Safari just doesn't recognize
+'.gz' files and your site won't work on it! To fix this problem, you can
+set any other extension (like .jgz) in the `SYNC_S3_RENAME_GZIP_EXT`
+variable.
+
Command options are:
-p PREFIX, --prefix=PREFIX
The prefix to prepend to the path on S3.
@@ -27,10 +33,15 @@
files.
--filter-list Override default directory and file exclusion
filters. (enter as comma seperated line)
- --renamegzip Enables renaming of gzipped files by appending '.gz.
- to the original file name. This way your original assets
- will not be replaced by the gzipped ones if you don't want
- them to be.
+ --renamegzip Enables renaming of gzipped files by appending '.gz'.
+ to the original file name. This way your original
+ assets will not be replaced by the gzipped ones.
+ You can change the extension setting the
+ `SYNC_S3_RENAME_GZIP_EXT` var in your settings.py
+ file.
+ --invalidate Invalidates the objects in CloudFront after uploaading
+ stuff to s3.
+
TODO:
* Use fnmatch (or regex) to allow more complex FILTER_LIST rules.
@@ -60,6 +71,9 @@ class Command(BaseCommand):
AWS_ACCESS_KEY_ID = ''
AWS_SECRET_ACCESS_KEY = ''
AWS_BUCKET_NAME = ''
+ AWS_CLOUDFRONT_DISTRIBUTION = ''
+ SYNC_S3_RENAME_GZIP_EXT = ''
+
DIRECTORY = ''
FILTER_LIST = ['.DS_Store', '.svn', '.hg', '.git', 'Thumbs.db']
GZIP_CONTENT_TYPES = (
@@ -69,6 +83,7 @@ class Command(BaseCommand):
'text/javascript'
)
+ uploaded_files = []
upload_count = 0
skip_count = 0
@@ -95,6 +110,9 @@ class Command(BaseCommand):
optparse.make_option('--filter-list', dest='filter_list',
action='store', default='',
help="Override default directory and file exclusion filters. (enter as comma seperated line)"),
+ optparse.make_option('--invalidate', dest='invalidate', default=False,
+ action='store_true',
+ help='Invalidates the associated objects in CloudFront')
)
help = 'Syncs the complete MEDIA_ROOT structure and files to S3 into the given bucket name.'
@@ -127,12 +145,19 @@ def handle(self, *args, **options):
if not settings.MEDIA_ROOT:
raise CommandError('MEDIA_ROOT must be set in your settings.')
+ self.AWS_CLOUDFRONT_DISTRIBUTION = \
+ getattr(settings, 'AWS_CLOUDFRONT_DISTRIBUTION', '')
+
+ self.SYNC_S3_RENAME_GZIP_EXT = \
+ getattr(settings, 'SYNC_S3_RENAME_GZIP_EXT', '.gz')
+
self.verbosity = int(options.get('verbosity'))
self.prefix = options.get('prefix')
self.do_gzip = options.get('gzip')
self.rename_gzip = options.get('renamegzip')
self.do_expires = options.get('expires')
self.do_force = options.get('force')
+ self.invalidate = options.get('invalidate')
self.DIRECTORY = options.get('dir')
self.FILTER_LIST = getattr(settings, 'FILTER_LIST', self.FILTER_LIST)
filter_list = options.get('filter_list')
@@ -145,10 +170,47 @@ def handle(self, *args, **options):
# upload all files found.
self.sync_s3()
+ # Sending the invalidation request to CloudFront if the user
+ # requested this action
+ if self.invalidate:
+ self.invalidate_objects_cf()
+
print
print "%d files uploaded." % (self.upload_count)
print "%d files skipped." % (self.skip_count)
+ def open_cf(self):
+ """
+ Returns an open connection to CloudFront
+ """
+ return boto.connect_cloudfront(
+ self.AWS_ACCESS_KEY_ID, self.AWS_SECRET_ACCESS_KEY)
+
+ def invalidate_objects_cf(self):
+ """
+ Split the invalidation request in groups of 1000 objects
+ """
+ if not self.AWS_CLOUDFRONT_DISTRIBUTION:
+ raise CommandError(
+ 'An object invalidation was requested but the variable '
+ 'AWS_CLOUDFRONT_DISTRIBUTION is not present in your settings.')
+
+ # We can't send more than 1000 objects in the same invalidation
+ # request.
+ chunk = 1000
+
+ # Connecting to CloudFront
+ conn = self.open_cf()
+
+ # Splitting the object list
+ objs = self.uploaded_files
+ chunks = [objs[i:i + chunk] for i in range(0, len(objs), chunk)]
+
+ # Invalidation requests
+ for paths in chunks:
+ conn.create_invalidation_request(
+ self.AWS_CLOUDFRONT_DISTRIBUTION, paths)
+
def sync_s3(self):
"""
Walks the media directory and syncs files to S3
@@ -241,9 +303,12 @@ def upload_s3(self, arg, dirname, names):
# and only if file is a common text type (not a binary file)
if file_size > 1024 and content_type in self.GZIP_CONTENT_TYPES:
filedata = self.compress_string(filedata)
- if self.rename_gzip:
- #If rename_gzip is True, then rename the file by appending '.gz' to original filename
- file_key = '%s.gz' % (file_key)
+ if self.rename_gzip:
+ # If rename_gzip is True, then rename the file
+ # by appending an extension (like '.gz)' to
+ # original filename.
+ file_key = '%s.%s' % (
+ file_key, self.SYNC_S3_RENAME_GZIP_EXT)
headers['Content-Encoding'] = 'gzip'
if self.verbosity > 1:
print "\tgzipped: %dk to %dk" % \
@@ -270,6 +335,7 @@ def upload_s3(self, arg, dirname, names):
raise
else:
self.upload_count += 1
+ self.uploaded_files.append(file_key)
file_obj.close()
View
32 django_extensions/management/commands/validate_templates.py
@@ -4,6 +4,9 @@
from django.core.management.base import BaseCommand, CommandError
from django.core.management.color import color_style
from django.template import Template
+from django.template.base import add_to_builtins
+from django.template.loaders.filesystem import Loader
+from django_extensions.utils import validatingtemplatetags
#
# TODO: Render the template with fake request object ?
@@ -15,6 +18,10 @@ class Command(BaseCommand):
option_list = BaseCommand.option_list + (
make_option('--break', '-b', action='store_true', dest='break',
default=False, help="Break on first error."),
+ make_option('--check-urls', '-u', action='store_true', dest='check_urls',
+ default=False, help="Check url tag view names are quoted appropriately"),
+ make_option('--force-new-urls', '-n', action='store_true', dest='force_new_urls',
+ default=False, help="Error on usage of old style url tags (without {% load urls from future %}"),
make_option('--include', '-i', action='append', dest='includes',
default=[], help="Append these paths to TEMPLATE_DIRS")
)
@@ -26,8 +33,16 @@ def handle(self, *args, **options):
template_dirs |= set(options.get('includes', []))
template_dirs |= set(getattr(settings, 'VALIDATE_TEMPLATES_EXTRA_TEMPLATE_DIRS', []))
settings.TEMPLATE_DIRS = list(template_dirs)
+ settings.TEMPLATE_DEBUG = True
verbosity = int(options.get('verbosity', 1))
errors = 0
+
+ template_loader = Loader()
+
+ # Replace built in template tags with our own validating versions
+ if options.get('check_urls', False):
+ add_to_builtins('django_extensions.utils.validatingtemplatetags')
+
for template_dir in template_dirs:
for root, dirs, filenames in os.walk(template_dir):
for filename in filenames:
@@ -38,16 +53,19 @@ def handle(self, *args, **options):
filepath = os.path.join(root, filename)
if verbosity>1:
print filepath
+ validatingtemplatetags.before_new_template(options.get('force_new_urls', False))
try:
- tmpl = Template(open(filepath).read())
+ template_loader.load_template(filename, [root])
except Exception, e:
errors += 1
- if options.get('break', False):
- raise CommandError("%s: %s %s" % (filepath, e.__class__.__name__, str(e)))
- else:
- print "%s:" % filepath
- print style.ERROR("%s %s" % (e.__class__.__name__, str(e)))
- print
+ print "%s: %s" % (filepath, style.ERROR("%s %s" % (e.__class__.__name__, str(e))))
+ template_errors = validatingtemplatetags.get_template_errors()
+ for origin, line, message in template_errors:
+ errors += 1
+ print "%s(%s): %s" % (origin, line, style.ERROR(message))
+ if errors and options.get('break', False):
+ raise CommandError("Errors found")
+
if errors:
raise CommandError("%s errors found" % errors)
print "%s errors found" % errors
View
6 django_extensions/management/notebook_extension.py
@@ -0,0 +1,6 @@
+def load_ipython_extension(ipython):
+ from django.core.management.color import no_style
+ from django_extensions.management.shells import import_objects
+ imported_objects = import_objects(options={'dont_load': []},
+ style=no_style())
+ ipython.push(imported_objects)
View
56 django_extensions/management/shells.py
@@ -0,0 +1,56 @@
+class ObjectImportError(Exception):
+ pass
+
+
+def import_objects(options, style):
+ # XXX: (Temporary) workaround for ticket #1796: force early loading of all
+ # models from installed apps. (this is fixed by now, but leaving it here
+ # for people using 0.96 or older trunk (pre [5919]) versions.
+ from django.db.models.loading import get_models, get_apps
+ loaded_models = get_models()
+
+ from django.conf import settings
+ imported_objects = {'settings': settings}
+
+ dont_load_cli = options.get('dont_load') # optparse will set this to [] if it doensnt exists
+ dont_load_conf = getattr(settings, 'SHELL_PLUS_DONT_LOAD', [])
+ dont_load = dont_load_cli + dont_load_conf
+ quiet_load = options.get('quiet_load')
+
+ model_aliases = getattr(settings, 'SHELL_PLUS_MODEL_ALIASES', {})
+
+ for app_mod in get_apps():
+ app_models = get_models(app_mod)
+ if not app_models:
+ continue
+
+ app_name = app_mod.__name__.split('.')[-2]
+ if app_name in dont_load:
+ continue
+
+ app_aliases = model_aliases.get(app_name, {})
+ model_labels = []
+
+ for model in app_models:
+ try:
+ imported_object = getattr(__import__(app_mod.__name__, {}, {}, model.__name__), model.__name__)
+ model_name = model.__name__
+
+ if "%s.%s" % (app_name, model_name) in dont_load:
+ continue
+
+ alias = app_aliases.get(model_name, model_name)
+ imported_objects[alias] = imported_object
+ if model_name == alias:
+ model_labels.append(model_name)
+ else:
+ model_labels.append("%s (as %s)" % (model_name, alias))
+
+ except AttributeError, e:
+ if not quiet_load:
+ print style.ERROR("Failed to import '%s' from '%s' reason: %s" % (model.__name__, app_name, str(e)))
+ continue
+ if not quiet_load:
+ print style.SQL_COLTYPE("From '%s' autoload: %s" % (app_mod.__name__.split('.')[-2], ", ".join(model_labels)))
+
+ return imported_objects
View
30 django_extensions/management/utils.py
@@ -1,6 +1,7 @@
from django.conf import settings
import os
import sys
+import logging
def get_project_root():
@@ -8,6 +9,7 @@ def get_project_root():
settings_mod = __import__(settings.SETTINGS_MODULE, {}, {}, [''])
return os.path.dirname(os.path.abspath(settings_mod.__file__))
+
def _make_writeable(filename):
"""
Make sure that the file is writeable. Useful if our source is
@@ -23,3 +25,31 @@ def _make_writeable(filename):
new_permissions = stat.S_IMODE(st.st_mode) | stat.S_IWUSR
os.chmod(filename, new_permissions)
+
+def setup_logger(logger, stream, filename=None, fmt=None):
+ """Sets up a logger (if no handlers exist) for console output,
+ and file 'tee' output if desired."""
+ if len(logger.handlers) < 1:
+ console = logging.StreamHandler(stream)
+ console.setLevel(logging.DEBUG)
+ console.setFormatter(logging.Formatter(fmt))
+ logger.addHandler(console)
+ logger.setLevel(logging.DEBUG)
+
+ if filename:
+ outfile = logging.FileHandler(filename)
+ outfile.setLevel(logging.INFO)
+ outfile.setFormatter(logging.Formatter("%(asctime)s " + (fmt if fmt else '%(message)s')))
+ logger.addHandler(outfile)
+
+
+class RedirectHandler(logging.Handler):
+ """Redirect logging sent to one logger (name) to another."""
+ def __init__(self, name, level=logging.DEBUG):
+ # Contemplate feasibility of copying a destination (allow original handler) and redirecting.
+ logging.Handler.__init__(self, level)
+ self.name = name
+ self.logger = logging.getLogger(name)
+
+ def emit(self, record):
+ self.logger.handle(record)
View
0  ...go_extensions/css/jquery.autocomplete.css → ...go_extensions/css/jquery.autocomplete.css
File renamed without changes
View
0  ...media/django_extensions/img/indicator.gif → ...tatic/django_extensions/img/indicator.gif
File renamed without changes
View
45 .../django_extensions/js/jquery.ajaxQueue.js → .../django_extensions/js/jquery.ajaxQueue.js
@@ -1,6 +1,6 @@
/**
* Ajax Queue Plugin
- *
+ *
* Homepage: http://jquery.com/plugins/project/ajaxqueue
* Documentation: http://docs.jquery.com/AjaxQueue
*/
@@ -32,7 +32,7 @@ $(function(){
*/
/*
* Queued Ajax requests.
- * A new Ajax request won't be started until the previous queued
+ * A new Ajax request won't be started until the previous queued
* request has finished.
*/
@@ -44,67 +44,67 @@ $(function(){
*/
-(function($) {
-
- var ajax = $.ajax;
-
+(function(jQuery) {
+
+ var ajax = jQuery.ajax;
+
var pendingRequests = {};
-
+
var synced = [];
var syncedData = [];
-
- $.ajax = function(settings) {
+
+ jQuery.ajax = function(settings) {
// create settings for compatibility with ajaxSetup
settings = jQuery.extend(settings, jQuery.extend({}, jQuery.ajaxSettings, settings));
-
+
var port = settings.port;
-
+
switch(settings.mode) {
- case "abort":
+ case "abort":
if ( pendingRequests[port] ) {
pendingRequests[port].abort();
}
return pendingRequests[port] = ajax.apply(this, arguments);
- case "queue":
+ case "queue":
var _old = settings.complete;
settings.complete = function(){
if ( _old )
_old.apply( this, arguments );
jQuery([ajax]).dequeue("ajax" + port );;
};
-
+
jQuery([ ajax ]).queue("ajax" + port, function(){
ajax( settings );
});
return;
case "sync":
var pos = synced.length;
-
+
synced[ pos ] = {
error: settings.error,
success: settings.success,
complete: settings.complete,
done: false
};
-
+
syncedData[ pos ] = {
error: [],
success: [],
complete: []
};
-
+
settings.error = function(){ syncedData[ pos ].error = arguments; };
settings.success = function(){ syncedData[ pos ].success = arguments; };
settings.complete = function(){
syncedData[ pos ].complete = arguments;
synced[ pos ].done = true;
-
+
if ( pos == 0 || !synced[ pos-1 ] )
for ( var i = pos; i < synced.length && synced[i].done; i++ ) {
if ( synced[i].error ) synced[i].error.apply( jQuery, syncedData[i].error );
if ( synced[i].success ) synced[i].success.apply( jQuery, syncedData[i].success );
if ( synced[i].complete ) synced[i].complete.apply( jQuery, syncedData[i].complete );
-
+
synced[i] = null;
syncedData[i] = null;
}
@@ -112,5 +112,8 @@ $(function(){
}
return ajax.apply(this, arguments);
};
-
-})(jQuery);
+
+})((typeof window.jQuery == 'undefined' && typeof window.django != 'undefined')
+ ? django.jQuery
+ : jQuery
+);
View
127 ...ango_extensions/js/jquery.autocomplete.js → ...ango_extensions/js/jquery.autocomplete.js
@@ -12,7 +12,7 @@
*/
;(function($) {
-
+
$.fn.extend({
autocomplete: function(urlOrData, options) {
var isUrl = typeof urlOrData == "string";
@@ -22,13 +22,13 @@ $.fn.extend({
delay: isUrl ? $.Autocompleter.defaults.delay : 10,
max: options && !options.scroll ? 10 : 150
}, options);
-
+
// if highlight is set to false, replace it with a do-nothing function
options.highlight = options.highlight || function(value) { return value; };
-
+
// if the formatMatch option is not specified, then use formatItem for backwards compatibility
options.formatMatch = options.formatMatch || options.formatItem;
-
+
return this.each(function() {
new $.Autocompleter(this, options);
});
@@ -77,9 +77,9 @@ $.Autocompleter = function(input, options) {
mouseDownOnSelect: false
};
var select = $.Autocompleter.Select(options, input, selectCurrent, config);
-
+
var blockSubmit;
-
+
// prevent form submit in opera when selecting with return key
$.browser.opera && $(input.form).bind("submit.autocomplete", function() {
if (blockSubmit) {
@@ -87,13 +87,13 @@ $.Autocompleter = function(input, options) {
return false;
}
});
-
+
// only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
$input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) {
// track last key pressed
lastKeyPressCode = event.keyCode;
switch(event.keyCode) {
-
+
case KEY.UP:
event.preventDefault();
if ( select.visible() ) {
@@ -102,7 +102,7 @@ $.Autocompleter = function(input, options) {
onChange(0, true);
}
break;
-
+
case KEY.DOWN:
event.preventDefault();
if ( select.visible() ) {
@@ -111,7 +111,7 @@ $.Autocompleter = function(input, options) {
onChange(0, true);
}
break;
-
+
case KEY.PAGEUP:
event.preventDefault();
if ( select.visible() ) {
@@ -120,7 +120,7 @@ $.Autocompleter = function(input, options) {
onChange(0, true);
}
break;
-
+
case KEY.PAGEDOWN:
event.preventDefault();
if ( select.visible() ) {
@@ -129,7 +129,7 @@ $.Autocompleter = function(input, options) {
onChange(0, true);
}
break;
-
+
// matches also semicolon
case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
case KEY.TAB:
@@ -141,11 +141,11 @@ $.Autocompleter = function(input, options) {
return false;
}
break;
-
+
case KEY.ESC:
select.hide();
break;
-
+
default:
clearTimeout(timeout);
timeout = setTimeout(onChange, options.delay);
@@ -196,16 +196,16 @@ $.Autocompleter = function(input, options) {
$input.unbind();
$(input.form).unbind(".autocomplete");
});
-
-
+
+
function selectCurrent() {
var selected = select.selected();
if( !selected )
return false;
-
+
var v = selected.result;
previousValue = v;
-
+
if ( options.multiple ) {
var words = trimWords($input.val());
if ( words.length > 1 ) {
@@ -213,26 +213,26 @@ $.Autocompleter = function(input, options) {
}
v += options.multipleSeparator;
}
-
+
$input.val(v);
hideResultsNow();
$input.trigger("result", [selected.data, selected.value]);
return true;
}
-
+
function onChange(crap, skipPrevCheck) {
if( lastKeyPressCode == KEY.DEL ) {
select.hide();
return;
}
-
+
var currentValue = $input.val();
-
+
if ( !skipPrevCheck && currentValue == previousValue )
return;
-
+
previousValue = currentValue;
-
+
currentValue = lastWord(currentValue);
if ( currentValue.length >= options.minChars) {
$input.addClass(options.loadingClass);
@@ -244,7 +244,7 @@ $.Autocompleter = function(input, options) {
select.hide();
}
};
-
+
function trimWords(value) {
if ( !value ) {
return [""];
@@ -257,14 +257,14 @@ $.Autocompleter = function(input, options) {
});
return result;
}
-
+
function lastWord(value) {
if ( !options.multiple )
return value;
var words = trimWords(value);
return words[words.length - 1];
}
-
+
// fills in the input box w/the first match (assumed to be the best match)
// q: the term entered
// sValue: the first matching result
@@ -330,14 +330,14 @@ $.Autocompleter = function(input, options) {
success(term, data);
// if an AJAX url has been supplied, try loading the data now
} else if( (typeof options.url == "string") && (options.url.length > 0) ){
-
+
var extraParams = {
timestamp: +new Date()
};
$.each(options.extraParams, function(key, param) {
extraParams[key] = typeof param == "function" ? param() : param;
});
-
+
$.ajax({
// try to leverage ajaxQueue plugin to abort previous requests
mode: "abort",
@@ -361,7 +361,7 @@ $.Autocompleter = function(input, options) {
failure(term);
}
};
-
+
function parse(data) {
var parsed = [];
var rows = data.split("\n");
@@ -416,25 +416,25 @@ $.Autocompleter.Cache = function(options) {
var data = {};
var length = 0;
-
+
function matchSubset(s, sub) {
- if (!options.matchCase)
+ if (!options.matchCase)
s = s.toLowerCase();
var i = s.indexOf(sub);
if (i == -1) return false;
return i == 0 || options.matchContains;
};
-
+
function add(q, value) {
if (length > options.cacheLength){
flush();
}
- if (!data[q]){
+ if (!data[q]){
length++;
}
data[q] = value;
}
-
+
function populate(){
if( !options.data ) return false;
// track the matches
@@ -443,23 +443,23 @@ $.Autocompleter.Cache = function(options) {
// no url was specified, we need to adjust the cache length to make sure it fits the local data store
if( !options.url ) options.cacheLength = 1;
-
+
// track all options for minChars = 0
stMatchSets[""] = [];
-
+
// loop through the array and create a lookup structure
for ( var i = 0, ol = options.data.length; i < ol; i++ ) {
var rawValue = options.data[i];
// if rawValue is a string, make an array otherwise just reference the array
rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue;
-
+
var value = options.formatMatch(rawValue, i+1, options.data.length);
if ( value === false )
continue;
-
+
var firstChar = value.charAt(0).toLowerCase();
// if no lookup array for this character exists, look it up now
- if( !stMatchSets[firstChar] )
+ if( !stMatchSets[firstChar] )
stMatchSets[firstChar] = [];
// if the match is a string
@@ -468,7 +468,7 @@ $.Autocompleter.Cache = function(options) {
data: rawValue,
result: options.formatResult && options.formatResult(rawValue) || value
};
-
+
// push the current match into the set list
stMatchSets[firstChar].push(row);
@@ -486,15 +486,15 @@ $.Autocompleter.Cache = function(options) {
add(i, value);
});
}
-
+
// populate any existing data
setTimeout(populate, 25);
-
+
function flush(){
data = {};
length = 0;
}
-
+
return {
flush: flush,
add: add,
@@ -502,7 +502,7 @@ $.Autocompleter.Cache = function(options) {
load: function(q) {
if (!options.cacheLength || !length)
return null;
- /*
+ /*
* if dealing w/local data and matchContains than we must make sure
* to loop through all the data collections looking for matches
*/
@@ -522,9 +522,9 @@ $.Autocompleter.Cache = function(options) {
}
});
}
- }
+ }
return csub;
- } else
+ } else
// if the exact item exists, use it
if (data[q]){
return data[q];
@@ -552,7 +552,7 @@ $.Autocompleter.Select = function (options, input, select, config) {
var CLASSES = {
ACTIVE: "ac_over"
};
-
+
var listItems,
active = -1,
data,
@@ -560,7 +560,7 @@ $.Autocompleter.Select = function (options, input, select, config) {
needsInit = true,
element,
list;
-
+
// Create results
function init() {
if (!needsInit)
@@ -570,11 +570,11 @@ $.Autocompleter.Select = function (options, input, select, config) {
.addClass(options.resultsClass)
.css("position", "absolute")
.appendTo(document.body);
-
+
list = $("<ul/>").appendTo(element).mouseover( function(event) {
if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
- $(target(event)).addClass(CLASSES.ACTIVE);
+ $(target(event)).addClass(CLASSES.ACTIVE);
}
}).click(function(event) {
$(target(event)).addClass(CLASSES.ACTIVE);
@@ -587,13 +587,13 @@ $.Autocompleter.Select = function (options, input, select, config) {
}).mouseup(function() {
config.mouseDownOnSelect = false;
});
-
+
if( options.width > 0 )
element.css("width", options.width);
-
+
needsInit = false;
- }
-
+ }
+
function target(event) {
var element = event.target;
while(element && element.tagName != "LI")
@@ -620,7 +620,7 @@ $.Autocompleter.Select = function (options, input, select, config) {
}
}
};
-
+
function movePosition(step) {
active += step;
if (active < 0) {
@@ -629,13 +629,13 @@ $.Autocompleter.Select = function (options, input, select, config) {
active = 0;
}
}
-
+
function limitNumberOfItems(available) {
return options.max && options.max < available
? options.max
: available;
}
-
+
function fillList() {
list.empty();
var max = limitNumberOfItems(data.length);
@@ -657,7 +657,7 @@ $.Autocompleter.Select = function (options, input, select, config) {
if ( $.fn.bgiframe )
list.bgiframe();
}
-
+
return {
display: function(d, q) {
init();
@@ -709,7 +709,7 @@ $.Autocompleter.Select = function (options, input, select, config) {
maxHeight: options.scrollHeight,
overflow: 'auto'
});
-
+
if($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
var listHeight = 0;
listItems.each(function() {
@@ -722,7 +722,7 @@ $.Autocompleter.Select = function (options, input, select, config) {
listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) );
}
}
-
+
}
},
selected: function() {
@@ -756,4 +756,7 @@ $.Autocompleter.Selection = function(field, start, end) {
field.focus();
};
-})(jQuery);
+})((typeof window.jQuery == 'undefined' && typeof window.django != 'undefined')
+ ? django.jQuery
+ : jQuery
+);
View
4 ...ango_extensions/js/jquery.bgiframe.min.js → ...ango_extensions/js/jquery.bgiframe.min.js
@@ -1,5 +1,5 @@
/* Copyright (c) 2006 Brandon Aaron (http://brandonaaron.net)
- * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
+ * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
* and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
*
* $LastChangedDate: 2007-07-22 01:45:56 +0200 (Son, 22 Jul 2007) $
@@ -7,4 +7,4 @@
*
* Version 2.1.1
*/
-(function($){$.fn.bgIframe=$.fn.bgiframe=function(s){if($.browser.msie&&/6.0/.test(navigator.userAgent)){s=$.extend({top:'auto',left:'auto',width:'auto',height:'auto',opacity:true,src:'javascript:false;'},s||{});var prop=function(n){return n&&n.constructor==Number?n+'px':n;},html='<iframe class="bgiframe"frameborder="0"tabindex="-1"src="'+s.src+'"'+'style="display:block;position:absolute;z-index:-1;'+(s.opacity!==false?'filter:Alpha(Opacity=\'0\');':'')+'top:'+(s.top=='auto'?'expression(((parseInt(this.parentNode.currentStyle.borderTopWidth)||0)*-1)+\'px\')':prop(s.top))+';'+'left:'+(s.left=='auto'?'expression(((parseInt(this.parentNode.currentStyle.borderLeftWidth)||0)*-1)+\'px\')':prop(s.left))+';'+'width:'+(s.width=='auto'?'expression(this.parentNode.offsetWidth+\'px\')':prop(s.width))+';'+'height:'+(s.height=='auto'?'expression(this.parentNode.offsetHeight+\'px\')':prop(s.height))+';'+'"/>';return this.each(function(){if($('> iframe.bgiframe',this).length==0)this.insertBefore(document.createElement(html),this.firstChild);});}return this;};})(jQuery);
+(function($){$.fn.bgIframe=$.fn.bgiframe=function(s){if($.browser.msie&&/6.0/.test(navigator.userAgent)){s=$.extend({top:'auto',left:'auto',width:'auto',height:'auto',opacity:true,src:'javascript:false;'},s||{});var prop=function(n){return n&&n.constructor==Number?n+'px':n;},html='<iframe class="bgiframe"frameborder="0"tabindex="-1"src="'+s.src+'"'+'style="display:block;position:absolute;z-index:-1;'+(s.opacity!==false?'filter:Alpha(Opacity=\'0\');':'')+'top:'+(s.top=='auto'?'expression(((parseInt(this.parentNode.currentStyle.borderTopWidth)||0)*-1)+\'px\')':prop(s.top))+';'+'left:'+(s.left=='auto'?'expression(((parseInt(this.parentNode.currentStyle.borderLeftWidth)||0)*-1)+\'px\')':prop(s.left))+';'+'width:'+(s.width=='auto'?'expression(this.parentNode.offsetWidth+\'px\')':prop(s.width))+';'+'height:'+(s.height=='auto'?'expression(this.parentNode.offsetHeight+\'px\')':prop(s.height))+';'+'"/>';return this.each(function(){if($('> iframe.bgiframe',this).length==0)this.insertBefore(document.createElement(html),this.firstChild);});}return this;};})((typeof window.jQuery=='undefined' && typeof window.django!='undefined')? django.jQuery : jQuery);
View
0  ...ions/media/django_extensions/js/jquery.js → ...ons/static/django_extensions/js/jquery.js
File renamed without changes
View
6 django_extensions/templates/django_extensions/widgets/foreignkey_searchinput.html
@@ -4,7 +4,7 @@
<img src="{{ admin_media_prefix }}img/{% if pre_django_14 %}admin/{% endif %}selector-search.gif" width="16" height="16" alt="{% trans "Lookup" %}" />
</a>
<script type="text/javascript">
-$(document).ready(function() {
+(function($){
// Show lookup input
$("#lookup_{{ name }}").show();
function reset() {
@@ -56,5 +56,5 @@
}
}
timeout = window.setInterval(check, 300);
-});
-</script>
+})((typeof window.jQuery == 'undefined' && typeof window.django != 'undefined')? django.jQuery : jQuery);
+</script>
View
12 django_extensions/templatetags/truncate_letters.py
@@ -1,3 +1,4 @@
+import django
from django import template
from django.template.defaultfilters import stringfilter
@@ -17,6 +18,11 @@ def truncateletters(value, arg):
return value # Fail silently
return truncate_letters(value, length)
-truncateletters.is_safe = True
-truncateletters = stringfilter(truncateletters)
-register.filter(truncateletters)
+if django.get_version() >= "1.4":
+ truncateletters = stringfilter(truncateletters)
+ register.filter(truncateletters, is_safe=True)
+else:
+ truncateletters.is_safe = True
+ truncateletters = stringfilter(truncateletters)
+ register.filter(truncateletters)
+
View
2  django_extensions/tests/__init__.py
@@ -1,10 +1,10 @@
from django.db import models
from django_extensions.tests.test_dumpscript import DumpScriptTests
-from django_extensions.tests.test_print_settings import PrintSettingsTests
from django_extensions.tests.utils import UTILS_TRUNCATE_LETTERS_TESTS
from django_extensions.tests.utils import UTILS_UUID_TESTS
from django_extensions.tests.json_field import JsonFieldTest
from django_extensions.tests.uuid_field import UUIDFieldTest
+from django_extensions.tests.fields import AutoSlugFieldTest
try:
from django_extensions.tests.encrypted_fields import EncryptedFieldsTestCase
from django_extensions.tests.models import Secret
View
89 django_extensions/tests/fields.py
@@ -0,0 +1,89 @@
+import unittest
+
+from django.conf import settings
+from django.core.management import call_command
+from django.db.models import loading
+from django.db import models
+from django_extensions.db.fields import AutoSlugField
+
+
+class SluggedTestModel(models.Model):
+ title = models.CharField(max_length=42)
+ slug = AutoSlugField(populate_from='title')
+
+
+class AutoSlugFieldTest(unittest.TestCase):
+ def setUp(self):
+ self.old_installed_apps = settings.INSTALLED_APPS
+ settings.INSTALLED_APPS = list(settings.INSTALLED_APPS)
+ settings.INSTALLED_APPS.append('django_extensions.tests')
+ loading.cache.loaded = False
+ call_command('syncdb', verbosity=0)
+
+ def tearDown(self):
+ SluggedTestModel.objects.all().delete()
+ settings.INSTALLED_APPS = self.old_installed_apps
+
+ def testAutoCreateSlug(self):
+ m = SluggedTestModel(title='foo')
+ m.save()
+ self.assertEqual(m.slug, 'foo')
+
+ def testAutoCreateNextSlug(self):
+ m = SluggedTestModel(title='foo')
+ m.save()
+
+ m = SluggedTestModel(title='foo')
+ m.save()
+ self.assertEqual(m.slug, 'foo-2')
+
+ def testAutoCreateSlugWithNumber(self):
+ m = SluggedTestModel(title='foo 2012')
+ m.save()
+ self.assertEqual(m.slug, 'foo-2012')
+
+ def testAutoUpdateSlugWithNumber(self):
+ m = SluggedTestModel(title='foo 2012')
+ m.save()
+ m.save()
+ self.assertEqual(m.slug, 'foo-2012')
+
+ def testUpdateSlug(self):
+ m = SluggedTestModel(title='foo')
+ m.save()
+
+ # update m instance without using `save'
+ SluggedTestModel.objects.filter(pk=m.pk).update(slug='foo-2012')
+ # update m instance with new data from the db
+ m = SluggedTestModel.objects.get(pk=m.pk)
+
+ self.assertEqual(m.slug, 'foo-2012')
+
+ m.save()
+ self.assertEqual(m.slug, 'foo-2012')
+
+ def testSimpleSlugSource(self):
+ m = SluggedTestModel(title='-foo')
+ m.save()
+ self.assertEqual(m.slug, 'foo')
+
+ n = SluggedTestModel(title='-foo')
+ n.save()
+ self.assertEqual(n.slug, 'foo-2')
+
+ n.save()
+ self.assertEqual(n.slug, 'foo-2')
+
+ def testEmptySlugSource(self):
+ # regression test
+
+ m = SluggedTestModel(title='')
+ m.save()
+ self.assertEqual(m.slug, '-2')
+
+ n = SluggedTestModel(title='')
+ n.save()
+ self.assertEqual(n.slug, '-3')
+
+ n.save()
+ self.assertEqual(n.slug, '-3')
View
27 django_extensions/tests/test_dumpscript.py
@@ -10,7 +10,9 @@
class DumpScriptTests(TestCase):
def setUp(self):
self.real_stdout = sys.stdout
+ self.real_stderr = sys.stderr
sys.stdout = StringIO()
+ sys.stderr = StringIO()
self.original_installed_apps = settings.INSTALLED_APPS
settings.INSTALLED_APPS = list(settings.INSTALLED_APPS)
@@ -20,6 +22,7 @@ def setUp(self):
def tearDown(self):
sys.stdout = self.real_stdout
+ sys.stderr = self.real_stderr
settings.INSTALLED_APPS.remove('django_extensions.tests')
settings.INSTALLED_APPS = self.original_installed_apps
loading.cache.loaded = False
@@ -31,3 +34,27 @@ def test_runs(self):
call_command('dumpscript', 'tests')
self.assertTrue('Gabriel' in sys.stdout.getvalue())
+ #----------------------------------------------------------------------
+ def test_replaced_stdout(self):
+ # check if stdout can be replaced
+ sys.stdout = StringIO()
+ n = Name(name='Mike')
+ n.save()
+ tmp_out = StringIO()
+ call_command('dumpscript', 'tests', stdout=tmp_out)
+ self.assertTrue('Mike' in tmp_out.getvalue()) # script should go to tmp_out
+ self.assertFalse(sys.stdout.len) # there should not be any output to sys.stdout
+ tmp_out.close()
+
+ #----------------------------------------------------------------------
+ def test_replaced_stderr(self):
+ # check if stderr can be replaced, without changing stdout
+ n = Name(name='Fred')
+ n.save()
+ tmp_err = StringIO()
+ sys.stderr = StringIO()
+ call_command('dumpscript', 'tests', stderr=tmp_err)
+ self.assertTrue('Fred' in sys.stdout.getvalue()) # script should still go to stdout
+ self.assertTrue('Name' in tmp_err.getvalue()) # error output should go to tmp_err
+ self.assertFalse(sys.stderr.len) # there should not be any output to sys.stderr
+ tmp_err.close()
View
57 django_extensions/tests/test_print_settings.py
@@ -1,57 +0,0 @@
-try:
- import simplejson as json
-except ImportError:
- import json
-
-import subprocess
-import sys
-from django.utils import unittest
-
-
-class PrintSettingsTests(unittest.TestCase):
-
- def _exec_manage_py(self, argv):
- python = sys.executable
- argv = [python, 'example_project/manage.py'] + argv
- return subprocess.Popen(argv, stdout=subprocess.PIPE).communicate()[0]
-
- def assertIn(self, needle, haystack):
- self.assertTrue(needle in haystack)
-
- def test_manage_py_print_settings_help(self):
- output = self._exec_manage_py(['print_settings', '--help'])
- # output = subprocess.check_output(['python', 'example_project/manage.py', 'print_settings', '--help'])
- self.assertIn('print_settings [options]', output)
- self.assertIn('--format=FORMAT', output)
- self.assertIn('--indent=INDENT', output)
-
- def test_manage_py_print_settings_no_args(self):
- output = self._exec_manage_py(['print_settings'])
- self.assertIn('America/Los_Angeles', output)
-
- def test_manage_py_print_settings_json(self):
- output = self._exec_manage_py(['print_settings', '--format=json'])
- self.assertIn('America/Los_Angeles', output)
- settings_dict = json.loads(output)
- self.assertEquals(len(settings_dict['AUTHENTICATION_BACKENDS']), 1)
- self.assertEquals(
- settings_dict['AUTHENTICATION_BACKENDS'][0],
- 'django.contrib.auth.backends.ModelBackend'
- )
-
- def test_manage_py_print_settings_yaml(self):
- try:
- import yaml
- except ImportError:
- self.skipTest("`yaml` module not available; install `PyYAML` to test --format=yaml")
-
- output = self._exec_manage_py(['print_settings', '--format=yaml'])
- self.assertIn('America/Los_Angeles', output)
- settings_dict = yaml.load(output)
- self.assertEquals(settings_dict['TIME_ZONE'], 'America/Los_Angeles')
- self.assertEquals(len(settings_dict['AUTHENTICATION_BACKENDS']), 1)
- self.assertEquals(
- settings_dict['AUTHENTICATION_BACKENDS'][0],
- 'django.contrib.auth.backends.ModelBackend'
- )
-
View
82 django_extensions/utils/validatingtemplatetags.py
@@ -0,0 +1,82 @@
+from django.template.base import Library, Node
+from django.template import defaulttags
+from django.templatetags import future
+register = Library()
+
+error_on_old_style_url_tag = False
+new_style_url_tag = False
+errors = []
+
+def before_new_template(force_new_urls):
+ """Reset state ready for new template"""
+ global new_style_url_tag, error_on_old_style_url_tag, errors
+ new_style_url_tag = False
+ error_on_old_style_url_tag = force_new_urls
+ errors = []
+
+