[Python-modules-commits] [python-django-extensions] 03/08: Import python-django-extensions_1.5.0.orig.tar.gz

Brian May bam at moszumanska.debian.org
Sat Oct 24 03:44:50 UTC 2015


This is an automated email from the git hooks/post-receive script.

bam pushed a commit to branch master
in repository python-django-extensions.

commit 71fba97c97ae2d4188f72fdee179d65c5e0619ef
Author: Brian May <brian at linuxpenguins.xyz>
Date:   Sat Oct 24 14:33:29 2015 +1100

    Import python-django-extensions_1.5.0.orig.tar.gz
---
 .travis.yml                                        |  17 +-
 CHANGELOG => CHANGELOG.md                          | 125 ++++++++
 README.rst                                         |  64 ++++
 django_extensions/__init__.py                      |   2 +-
 django_extensions/admin/__init__.py                |   5 +-
 django_extensions/db/fields/__init__.py            |  31 +-
 django_extensions/db/fields/encrypted.py           |  14 +-
 django_extensions/db/fields/json.py                |   6 +-
 django_extensions/jobs/daily/cache_cleanup.py      |  32 +-
 django_extensions/jobs/daily/daily_cleanup.py      |   7 +-
 .../management/commands/admin_generator.py         | 332 +++++++++++++++++++++
 django_extensions/management/commands/clean_pyc.py |  27 +-
 .../management/commands/clear_cache.py             |  16 +
 .../management/commands/compile_pyc.py             |  26 +-
 .../management/commands/create_app.py              |   3 +-
 .../management/commands/create_command.py          |   3 +-
 .../management/commands/create_jobs.py             |   3 +-
 .../management/commands/create_template_tags.py    |   3 +-
 .../management/commands/describe_form.py           |  11 +-
 .../{reset_db.py => drop_test_database.py}         |  89 +++---
 .../management/commands/dumpscript.py              |  48 ++-
 .../management/commands/export_emails.py           |   4 +-
 .../management/commands/find_template.py           |   3 +
 .../management/commands/generate_secret_key.py     |   3 +
 .../management/commands/graph_models.py            |   6 +-
 .../management/commands/mail_debug.py              |   3 +-
 django_extensions/management/commands/notes.py     |   2 +
 django_extensions/management/commands/passwd.py    |   2 +
 .../management/commands/pipchecker.py              |   2 +
 .../management/commands/print_settings.py          |   2 +
 .../management/commands/print_user_for_session.py  |   3 +-
 django_extensions/management/commands/reset_db.py  |  28 +-
 django_extensions/management/commands/runjob.py    |   4 +-
 django_extensions/management/commands/runjobs.py   |   8 +-
 .../management/commands/runprofileserver.py        |  14 +-
 django_extensions/management/commands/runscript.py |  54 +++-
 .../management/commands/runserver_plus.py          |  12 +-
 .../management/commands/set_default_site.py        |   4 +-
 .../management/commands/set_fake_emails.py         |   3 +
 .../management/commands/set_fake_passwords.py      |   3 +
 .../management/commands/shell_plus.py              | 207 +++++++++----
 .../management/commands/show_templatetags.py       |   7 +-
 django_extensions/management/commands/show_urls.py |  34 ++-
 django_extensions/management/commands/sqlcreate.py |   4 +-
 django_extensions/management/commands/sqldiff.py   | 236 +++++++++++----
 django_extensions/management/commands/sync_s3.py   |   3 +
 django_extensions/management/commands/syncdata.py  |  43 ++-
 .../management/commands/unreferenced_files.py      |   3 +
 .../management/commands/update_permissions.py      |  38 ++-
 .../management/commands/validate_templates.py      |   2 +
 .../management/email_notifications.py              | 136 +++++++++
 django_extensions/management/jobs.py               |   3 +-
 django_extensions/management/shells.py             |   6 +-
 django_extensions/management/signals.py            |   3 +
 django_extensions/management/utils.py              |  11 +
 django_extensions/mongodb/fields/__init__.py       |  12 +-
 django_extensions/tests/__init__.py                |  10 +-
 django_extensions/tests/management_command.py      |  47 +++
 django_extensions/tests/test_clean_pyc.py          |   9 -
 django_extensions/tests/test_compile_pyc.py        |  10 -
 django_extensions/utils/dia2django.py              |   2 +-
 django_extensions/validators.py                    |  65 ++++
 docs/AUTHORS                                       |   2 +
 docs/command_extensions.rst                        |   9 +
 docs/command_signals.rst                           |  91 ++++++
 docs/conf.py                                       |   6 +-
 docs/field_extensions.rst                          |  10 +
 docs/graph_models.rst                              |   2 +-
 docs/index.rst                                     |   3 +-
 docs/runserver_plus.rst                            |  15 +
 docs/sqldiff.rst                                   |   5 +
 setup.py                                           |   1 +
 tox.ini                                            |  22 +-
 73 files changed, 1702 insertions(+), 379 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 9dfa1bb..b36720f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,12 +5,15 @@ python:
   - 2.7
   - 3.3
   - 3.4
+  - pypy
+  - pypy3
 
 env:
   - DJANGO=Django==1.4
   - DJANGO=Django==1.5
   - DJANGO=Django==1.6
-  - DJANGO="https://www.djangoproject.com/download/1.7b3/tarball/"
+  - DJANGO=Django==1.7
+  - DJANGO=Django==1.8a1
   - DJANGO="git+git://github.com/django/django.git@master#egg=django"
 
 install:
@@ -28,12 +31,22 @@ script:
 matrix:
   exclude:
     - python: 2.6
-      env: DJANGO="https://www.djangoproject.com/download/1.7b3/tarball/"
+      env: DJANGO=Django==1.7
+    - python: 2.6
+      env: DJANGO=Django==1.8a1
     - python: 2.6
       env: DJANGO="git+git://github.com/django/django.git@master#egg=django"
     - python: 3.3
       env: DJANGO=Django==1.4
     - python: 3.4
       env: DJANGO=Django==1.4
+    - python: pypy
+      env: DJANGO=Django==1.4
+    - python: pypy
+      env: DJANGO=Django==1.5
+    - python: pypy3
+      env: DJANGO=Django==1.4
+    - python: pypy3
+      env: DJANGO=Django==1.5
   allow_failures:
       - env: DJANGO="git+git://github.com/django/django.git@master#egg=django"
diff --git a/CHANGELOG b/CHANGELOG.md
similarity index 53%
rename from CHANGELOG
rename to CHANGELOG.md
index 5555942..cd2122e 100644
--- a/CHANGELOG
+++ b/CHANGELOG.md
@@ -1,6 +1,131 @@
 Changelog
 =========
 
+1.5.0
+-----
+
+Changes:
+ - Fix: various fixes for Django 1.8
+ - Improvement: shell_plus, autodetect vi mode by looking at $EDITOR shell env setting
+ - Improvement: shell_plus, print which shell is being used at verbosity > 1
+ - Improvement: shell_plus, added --no-browser option for IPython notebooks
+ - Improvement: tox.ini, updated to latest Django versions
+ - Docs: add reference to JSONField in documentation
+ - Docs: fixed various typo's and links in docs and changelog
+ - Docs: added some basic use cases to README
+ - Docs: added information for companies or people wanting to donate towards the project
+ - Fix: graphmodels, fix for python3
+ - Fix: dumpscript, fix check for missing import_helper module in Python 3
+ - Fix: runprofileserver, explicitly close file to avoid error on windows
+ - Fix: json field, migration issues when adding new JSONField to existing model
+ - Fix: runjobs, fix python3 issues
+
+
+1.4.9
+-----
+
+Changes:
+ - New: drop_test_database, drops the test database
+ - New: command_signals, git commit -a -m 'bumped version number' (see docs)
+ - Bugfix: runserver_plus, removed empty lines when logging on Python 3
+
+
+1.4.8
+-----
+
+Changes:
+ - Bugfix: validators, fix NoWhitespaceValidator __eq__ check
+
+
+1.4.7
+-----
+
+Changes:
+ - New: validators.py, NoControlCharactersValidator and NoWhitespaceValidator
+ - New: EmailNotificationCommand class, email exceptions from Django Commands
+ - Improvement: runserver_plus, enable threading by default and added --nothreading
+ - Improvement: runscript, better detection when import error occured in script 
+ - Improvement: runscript, use EmailNotificationCommand class
+ - Deprecation: deprecated UUIDField since Django 1.8 will have a native version.
+ - Removed: completely remove support for automatically finding project root.
+
+
+1.4.6
+-----
+
+Changes:
+ - Improvement: sqldiff, fix for dbcolumn not used in few places when generating the sqldiff
+ - Fix: sqldiff, backwards compatiblity fix for Django 1.4
+ - Fix: ForeignKey Field, handling of __str__ instead of __unicode__ in python3
+
+
+1.4.5
+-----
+
+Changes:
+ - New: clear_cache, Clear django cache, useful when testing or deploying
+ - Improvement: AutoSlugField, add the possibility to define a custom slugify function
+ - Improvement: shell_plus --notebook, add a big warning when the notebook extension is not going to be loaded
+ - Improvement: setup.py, add pypy classifier
+ - Improvement: readme, add pypy badges
+ - Fix: admin_generator, Fixed Python 3 __unicode__/__str__ compatibility
+
+
+1.4.4
+-----
+
+Changes:
+ - Fix: admin_generator, fix ImproperlyConfigured exception on Django 1.7
+ - Improvement: Remove "requires_model_validation" and "requires_system_checks" in commands which set the default value
+
+
+1.4.1
+-----
+
+Changes:
+ - New: shell_plus, Added python-prompt-toolkit integration for shell_plus
+ - New: shell_plus, Added --ptipython (PYPython + IPython)
+ - Improvement: reset_db, output traceback to easy debugging in case of error
+ - Improvement: dumpscript, add --autofield to dumpscript to include autofields in export
+ - Improvement: show_urls, Include namespace in URL name
+ - Improvement: show_urls, Allow multiple decorators on the show_urls command
+ - Improvement: runscript, show script errors with verbosity > 1
+ - Fix: jobs, daily_cleanup job use clearsessions for Django 1.5 and later
+ - Fix: shell_plus, refactored importing and selecting shells to avoid polluted exception
+ - Fix: shell_plus, Fix model loading for sentry
+
+
+1.4.0
+-----
+
+Changes:
+ - New admin_generator, can generate a admin.py file from models
+ - Improvement: sqldiff, use the same exit codes as diff uses
+ - Improvement: sqldiff, add support for unsigned numeric fields
+ - Improvement: sqldiff, add NOT NULL support for MySQL
+ - Improvement: sqldiff, add proper AUTO_INCREMENT support for MySQL
+ - Improvement: sqldiff, detect tables for which no model exists
+ - Improvement: travis.yml, add pypy to tests
+ - Fix: sqldiff, fix for mysql misreported field lengths
+ - Fix: sqldiff, in PG custom int primary keys would be mistaking for serial
+ - Fix: sqldiff, use Django 1.7 db_parameters() for detect check constraints
+ - Fix: update_permissions, Django 1.7 support
+ - Fix: encrypted fields, fix for Django 1.7 migrations
+
+
+1.3.11
+------
+
+Changes:
+ - Improvement: sqldiff, show differences for not managed tables
+ - Improvement: show_urls -f aligned, 3 spaces between columns
+ - Improvement: reset_db, support mysql options files in reset_db
+ - Fix: sqldiff, Fixed bug with --output_text option and notnull-differ text
+ - Fix: reset_db, Fix for PostgreSQL databases with dashes, dots, etc in the name
+ - Fix: dumpscript, AttributeError for datefields that are None
+ - Docs: Adding RUNSERVERPLUS_SERVER_ADDRESS_PORT to docs
+
+
 1.3.10
 ------
 
diff --git a/README.rst b/README.rst
index a293d3f..06d839d 100644
--- a/README.rst
+++ b/README.rst
@@ -2,10 +2,22 @@
  Django Extensions
 ===================
 
+.. image:: https://img.shields.io/badge/license-MIT-blue.svg
+   :target: https://raw.githubusercontent.com/django-extensions/django-extensions/master/LICENSE
+
 .. image:: https://secure.travis-ci.org/django-extensions/django-extensions.png?branch=master
     :alt: Build Status
     :target: http://travis-ci.org/django-extensions/django-extensions
 
+.. image:: https://pypip.in/v/django-extensions/badge.png
+    :target: https://pypi.python.org/pypi/django-extensions/
+    :alt: Latest PyPI version
+
+.. image:: https://pypip.in/d/django-extensions/badge.png
+    :target: https://pypi.python.org/pypi/django-extensions/
+    :alt: Number of PyPI downloads
+
+
 Django Extensions is a collection of custom extensions for the Django Framework.
 
 
@@ -64,6 +76,18 @@ Produce a tab-separated list of `(url_pattern, view_function, name)` tuples for
 
     $ python manage.py show_urls
 
+Check templates for rendering errors::
+
+    $ python manage.py validate_templates
+
+Run the enchanced django shell::
+
+    $ python manage.py shell_plus
+
+Run the enchanced django runserver, (requires Werkzeug install)::
+
+    $ python manage.py runserver_plus
+
 
 Getting Involved
 ================
@@ -86,6 +110,46 @@ You can view documentation online at:
 Or you can look at the docs/ directory in the repository.
 
 
+Donations
+=========
+
+Django Extensions is free and always will be. From time to time people and company's have expressed the willingness
+to donation to the project to help foster it's development. We prefer people to become active in the project and support
+us by sending pull requests but will humbly accept donations as well.
+
+Donations will be used to make Django Extensions better by allowing developers to spend more time on it, paying to go
+to conferences, paying for infrastructure, etc. If for some reason we would receive more donations then needed they will
+go towards the Django and Python foundations.
+
+We have setup a couple of ways you can donate towards Django Extensions using the buttons below:
+
+ - Bountysource
+ - Bitcoins at address: 13V3AaapCRz2i2HnJiFoqVECe1t5H26yCg
+ - Gratipay (formerly Gittip)
+ - Flattr
+ - PayPal
+ - Patreon `here <https://patreon.com/trbs>`_
+
+.. image:: https://www.bountysource.com/badge/team?team_id=7470&style=bounties_posted
+    :target: https://www.bountysource.com/teams/django-extensions/bounties?utm_source=django-extensions&utm_medium=shield&utm_campaign=bounties_posted
+    :alt: BountySource
+
+.. image:: https://img.shields.io/bitcoin/donate.png
+    :target: bitcoin:13V3AaapCRz2i2HnJiFoqVECe1t5H26yCg?label=DjangoExtensions
+
+.. image:: https://img.shields.io/flattr/donate.png
+    :target: https://flattr.com/submit/auto?user_id=Trbs&url=https%3A%2F%2Fgithub.com%2Fdjango-extensions%2Fdjango-extensions
+    :alt: Flattr this
+
+.. image:: https://img.shields.io/paypal/donate.png
+    :target: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=P57EJJ9QYL232
+    :alt: PayPal Donations
+
+.. image:: https://img.shields.io/gratipay/trbs.png
+    :target: https://gratipay.com/trbs/
+    :alt: Gifts received
+
+
 __ http://ericholscher.com/blog/2008/sep/12/screencast-django-command-extensions/
 __ http://vimeo.com/1720508
 __ https://godjango.com/39-be-more-productive-with-django_extensions/
diff --git a/django_extensions/__init__.py b/django_extensions/__init__.py
index e1558aa..65ff1ac 100644
--- a/django_extensions/__init__.py
+++ b/django_extensions/__init__.py
@@ -1,5 +1,5 @@
 
-VERSION = (1, 3, 10)
+VERSION = (1, 5, 0)
 
 # Dynamically calculate the version based on VERSION tuple
 if len(VERSION) > 2 and VERSION[2] is not None:
diff --git a/django_extensions/admin/__init__.py b/django_extensions/admin/__init__.py
index 59aae32..46e10f4 100644
--- a/django_extensions/admin/__init__.py
+++ b/django_extensions/admin/__init__.py
@@ -80,7 +80,10 @@ class ForeignKeyAutocompleteAdmin(ModelAdmin):
         try:
             to_string_function = self.related_string_functions[model_name]
         except KeyError:
-            to_string_function = lambda x: x.__unicode__()
+            if six.PY3:
+                to_string_function = lambda x: x.__str__()
+            else:
+                to_string_function = lambda x: x.__unicode__()
 
         if search_fields and app_label and model_name and (query or object_pk):
             def construct_search(field_name):
diff --git a/django_extensions/db/fields/__init__.py b/django_extensions/db/fields/__init__.py
index 44a00ce..d68c04e 100644
--- a/django_extensions/db/fields/__init__.py
+++ b/django_extensions/db/fields/__init__.py
@@ -3,6 +3,8 @@ Django Extensions additional model fields
 """
 import re
 import six
+import warnings
+
 try:
     import uuid
     HAS_UUID = True
@@ -62,6 +64,8 @@ class AutoSlugField(SlugField):
             raise ValueError("missing 'populate_from' argument")
         else:
             self._populate_from = populate_from
+
+        self.slugify_function = kwargs.pop('slugify_function', slugify)
         self.separator = kwargs.pop('separator', six.u('-'))
         self.overwrite = kwargs.pop('overwrite', False)
         if not isinstance(self.overwrite, bool):
@@ -91,7 +95,7 @@ class AutoSlugField(SlugField):
 
     def slugify_func(self, content):
         if content:
-            return slugify(content)
+            return self.slugify_function(content)
         return ''
 
     def create_slug(self, model_instance, add):
@@ -259,7 +263,9 @@ class UUIDField(CharField):
     """
     DEFAULT_MAX_LENGTH = 36
 
-    def __init__(self, verbose_name=None, name=None, auto=True, version=4, node=None, clock_seq=None, namespace=None, **kwargs):
+    def __init__(self, verbose_name=None, name=None, auto=True, version=4, node=None, clock_seq=None, namespace=None, uuid_name=None, *args, **kwargs):
+        warnings.warn("Django 1.8 features a native UUIDField, this UUIDField will be removed after Django 1.7 becomes unsupported.", DeprecationWarning)
+
         if not HAS_UUID:
             raise ImproperlyConfigured("'uuid' module is required for UUIDField. (Do you have Python 2.5 or higher installed ?)")
         kwargs.setdefault('max_length', self.DEFAULT_MAX_LENGTH)
@@ -272,11 +278,8 @@ class UUIDField(CharField):
         self.node = node
         self.clock_seq = clock_seq
         self.namespace = namespace
-        self.name = name
-        CharField.__init__(self, verbose_name, name, **kwargs)
-
-    def get_internal_type(self):
-        return CharField.__name__
+        self.uuid_name = uuid_name or name
+        super(UUIDField, self).__init__(verbose_name=verbose_name, *args, **kwargs)
 
     def create_uuid(self):
         if not self.version or self.version == 4:
@@ -286,9 +289,9 @@ class UUIDField(CharField):
         elif self.version == 2:
             raise UUIDVersionError("UUID version 2 is not supported.")
         elif self.version == 3:
-            return uuid.uuid3(self.namespace, self.name)
+            return uuid.uuid3(self.namespace, self.uuid_name)
         elif self.version == 5:
-            return uuid.uuid5(self.namespace, self.name)
+            return uuid.uuid5(self.namespace, self.uuid_name)
         else:
             raise UUIDVersionError("UUID version %s is not valid." % self.version)
 
@@ -320,7 +323,7 @@ class UUIDField(CharField):
 
     def deconstruct(self):
         name, path, args, kwargs = super(UUIDField, self).deconstruct()
-        if self.max_length == self.DEFAULT_MAX_LENGTH:
+        if kwargs.get('max_length', None) == self.DEFAULT_MAX_LENGTH:
             del kwargs['max_length']
         if self.auto is not True:
             kwargs['auto'] = self.auto
@@ -332,12 +335,16 @@ class UUIDField(CharField):
             kwargs['clock_seq'] = self.clock_seq
         if self.namespace is not None:
             kwargs['namespace'] = self.namespace
-        if self.name is not None:
-            kwargs['name'] = self.name
+        if self.uuid_name is not None:
+            kwargs['uuid_name'] = self.name
         return name, path, args, kwargs
 
 
 class PostgreSQLUUIDField(UUIDField):
+    def __init__(self, *args, **kwargs):
+        warnings.warn("Django 1.8 features a native UUIDField, this UUIDField will be removed after Django 1.7 becomes unsupported.", DeprecationWarning)
+        super(PostgreSQLUUIDField, self).__init__(*args, **kwargs)
+
     def db_type(self, connection=None):
         return "UUID"
 
diff --git a/django_extensions/db/fields/encrypted.py b/django_extensions/db/fields/encrypted.py
index 35225f6..8eddba1 100644
--- a/django_extensions/db/fields/encrypted.py
+++ b/django_extensions/db/fields/encrypted.py
@@ -30,13 +30,17 @@ class BaseEncryptedField(models.Field):
         # Encrypted size is larger than unencrypted
         self.unencrypted_length = max_length = kwargs.get('max_length', None)
         if max_length:
-            max_length = len(self.prefix) + len(self.crypt.Encrypt('x' * max_length))
-            # TODO: Re-examine if this logic will actually make a large-enough
-            # max-length for unicode strings that have non-ascii characters in them.
-            kwargs['max_length'] = max_length
+            kwargs['max_length'] = self.calculate_crypt_max_length(max_length)
 
         super(BaseEncryptedField, self).__init__(*args, **kwargs)
 
+    def calculate_crypt_max_length(self, unencrypted_length):
+        # TODO: Re-examine if this logic will actually make a large-enough
+        # max-length for unicode strings that have non-ascii characters in them.
+        # For PostGreSQL we might as well always use textfield since there is little
+        # difference (except for length checking) between varchar and text in PG.
+        return len(self.prefix) + len(self.crypt.Encrypt('x' * unencrypted_length))
+
     def get_crypt_class(self):
         """
         Get the Keyczar class to use.
@@ -99,7 +103,7 @@ class BaseEncryptedField(models.Field):
 
     def deconstruct(self):
         name, path, args, kwargs = super(BaseEncryptedField, self).deconstruct()
-        kwargs['max_length'] = self.max_length
+        kwargs['max_length'] = self.unencrypted_length
         return name, path, args, kwargs
 
 
diff --git a/django_extensions/db/fields/json.py b/django_extensions/db/fields/json.py
index 7080913..0d69a6e 100644
--- a/django_extensions/db/fields/json.py
+++ b/django_extensions/db/fields/json.py
@@ -94,7 +94,11 @@ class JSONField(six.with_metaclass(models.SubfieldBase, models.TextField)):
         """Convert our JSON object to a string before we save"""
         if value is None and self.null:
             return None
-        return super(JSONField, self).get_db_prep_save(dumps(value), connection=connection)
+        # default values come in as strings; only non-strings should be
+        # run through `dumps`
+        if not isinstance(value, six.string_types):
+            value = dumps(value)
+        return super(JSONField, self).get_db_prep_save(value, connection=connection)
 
     def south_field_triple(self):
         """Returns a suitable description of this field for South."""
diff --git a/django_extensions/jobs/daily/cache_cleanup.py b/django_extensions/jobs/daily/cache_cleanup.py
index b598e0d..bbc75b5 100644
--- a/django_extensions/jobs/daily/cache_cleanup.py
+++ b/django_extensions/jobs/daily/cache_cleanup.py
@@ -6,6 +6,7 @@ sessions at the moment).
 """
 
 import six
+from contextlib import contextmanager
 from django_extensions.management.jobs import DailyJob
 
 
@@ -22,6 +23,14 @@ class Job(DailyJob):
         except ImportError:
             timezone = None
 
+        if hasattr(transaction, 'atomic'):
+            atomic = transaction.atomic
+        else:
+            @contextmanager
+            def atomic(using=None):
+                yield
+                transaction.commit_unless_managed(using=using)
+
         if hasattr(settings, 'CACHES') and timezone:
             from django.core.cache import get_cache
             from django.db import router, connections
@@ -30,10 +39,10 @@ class Job(DailyJob):
                 if cache_options['BACKEND'].endswith("DatabaseCache"):
                     cache = get_cache(cache_name)
                     db = router.db_for_write(cache.cache_model_class)
-                    cursor = connections[db].cursor()
-                    now = timezone.now()
-                    cache._cull(db, cursor, now)
-                    transaction.commit_unless_managed(using=db)
+                    with atomic(using=db):
+                        cursor = connections[db].cursor()
+                        now = timezone.now()
+                        cache._cull(db, cursor, now)
             return
 
         if hasattr(settings, 'CACHE_BACKEND'):
@@ -41,11 +50,12 @@ class Job(DailyJob):
                 from django.db import connection
                 os.environ['TZ'] = settings.TIME_ZONE
                 table_name = settings.CACHE_BACKEND[5:]
-                cursor = connection.cursor()
-                cursor.execute(
-                    "DELETE FROM %s WHERE %s < current_timestamp;" % (
-                        connection.ops.quote_name(table_name),
-                        connection.ops.quote_name('expires')
+
+                with atomic():
+                    cursor = connection.cursor()
+                    cursor.execute(
+                        "DELETE FROM %s WHERE %s < current_timestamp;" % (
+                            connection.ops.quote_name(table_name),
+                            connection.ops.quote_name('expires')
+                        )
                     )
-                )
-                transaction.commit_unless_managed()
diff --git a/django_extensions/jobs/daily/daily_cleanup.py b/django_extensions/jobs/daily/daily_cleanup.py
index 8e7172c..614e9ec 100644
--- a/django_extensions/jobs/daily/daily_cleanup.py
+++ b/django_extensions/jobs/daily/daily_cleanup.py
@@ -13,4 +13,9 @@ class Job(DailyJob):
 
     def execute(self):
         from django.core import management
-        management.call_command("cleanup")
+        from django import VERSION
+
+        if VERSION[:2] < (1, 5):
+            management.call_command("cleanup")
+        else:
+            management.call_command("clearsessions")
diff --git a/django_extensions/management/commands/admin_generator.py b/django_extensions/management/commands/admin_generator.py
new file mode 100644
index 0000000..006d0bd
--- /dev/null
+++ b/django_extensions/management/commands/admin_generator.py
@@ -0,0 +1,332 @@
+# -*- coding: utf-8 -*-
+
+import re
+import sys
+import optparse
+
+from django.db.models.loading import get_models
+from django.db import models
+from django.core.management.base import BaseCommand
+from django.conf import settings
+
+from django_extensions.management.color import color_style
+from django_extensions.management.utils import signalcommand
+
+# Configurable constants
+MAX_LINE_WIDTH = getattr(settings, 'MAX_LINE_WIDTH', 78)
+INDENT_WIDTH = getattr(settings, 'INDENT_WIDTH', 4)
+LIST_FILTER_THRESHOLD = getattr(settings, 'LIST_FILTER_THRESHOLD', 25)
+RAW_ID_THRESHOLD = getattr(settings, 'RAW_ID_THRESHOLD', 100)
+
+LIST_FILTER = getattr(settings, 'LIST_FILTER', (
+    models.DateField,
+    models.DateTimeField,
+    models.ForeignKey,
+    models.BooleanField,
+))
+
+SEARCH_FIELD_NAMES = getattr(settings, 'SEARCH_FIELD_NAMES', (
+    'name',
+    'slug',
+))
+
+DATE_HIERARCHY_NAMES = getattr(settings, 'DATE_HIERARCHY_NAMES', (
+    'joined_at',
+    'updated_at',
+    'created_at',
+))
+
+PREPOPULATED_FIELD_NAMES = getattr(settings, 'PREPOPULATED_FIELD_NAMES', (
+    'slug=name',
+))
+
+PRINT_IMPORTS = getattr(settings, 'PRINT_IMPORTS', '''# -*- coding: utf-8 -*-
+from django.contrib import admin
+
+from .models import %(models)s
+''')
+
+PRINT_ADMIN_CLASS = getattr(settings, 'PRINT_ADMIN_CLASS', '''
+
+class %(name)sAdmin(admin.ModelAdmin):%(class_)s
+admin.site.register(%(name)s, %(name)sAdmin)
+''')
+
+PRINT_ADMIN_PROPERTY = getattr(settings, 'PRINT_ADMIN_PROPERTY', '''
+    %(key)s = %(value)s''')
+
+
+class UnicodeMixin(object):
+    """Mixin class to handle defining the proper __str__/__unicode__
+    methods in Python 2 or 3."""
+
+    if sys.version_info[0] >= 3:  # Python 3
+        def __str__(self):
+            return self.__unicode__()
+    else:  # Python 2
+        def __str__(self):
+            return self.__unicode__().encode('utf8')
+
+
+class AdminApp(UnicodeMixin):
+    def __init__(self, app, model_res, **options):
+        self.app = app
+        self.model_res = model_res
+        self.options = options
+
+    def __iter__(self):
+        for model in get_models(self.app):
+            admin_model = AdminModel(model, **self.options)
+
+            for model_re in self.model_res:
+                if model_re.search(admin_model.name):
+                    break
+            else:
+                if self.model_res:
+                    continue
+
+            yield admin_model
+
+    def __unicode__(self):
+        return ''.join(self._unicode_generator())
+
+    def _unicode_generator(self):
+        models_list = [admin_model.name for admin_model in self]
+        yield PRINT_IMPORTS % dict(models=', '.join(models_list))
+
+        admin_model_names = []
+        for admin_model in self:
+            yield PRINT_ADMIN_CLASS % dict(
+                name=admin_model.name,
+                class_=admin_model,
+            )
+            admin_model_names.append(admin_model.name)
+
+    def __repr__(self):
+        return '<%s[%s]>' % (
+            self.__class__.__name__,
+            self.app,
+        )
+
+
+class AdminModel(UnicodeMixin):
+    PRINTABLE_PROPERTIES = (
+        'list_display',
+        'list_filter',
+        'raw_id_fields',
+        'search_fields',
+        'prepopulated_fields',
+        'date_hierarchy',
+    )
+
+    def __init__(self, model, raw_id_threshold=RAW_ID_THRESHOLD,
+                 list_filter_threshold=LIST_FILTER_THRESHOLD,
+                 search_field_names=SEARCH_FIELD_NAMES,
+                 date_hierarchy_names=DATE_HIERARCHY_NAMES,
+                 prepopulated_field_names=PREPOPULATED_FIELD_NAMES, **options):
+        self.model = model
+        self.list_display = []
+        self.list_filter = []
+        self.raw_id_fields = []
+        self.search_fields = []
+        self.prepopulated_fields = {}
+        self.date_hierarchy = None
+        self.search_field_names = search_field_names
+        self.raw_id_threshold = raw_id_threshold
+        self.list_filter_threshold = list_filter_threshold
+        self.date_hierarchy_names = date_hierarchy_names
+        self.prepopulated_field_names = prepopulated_field_names
+
+    def __repr__(self):
+        return '<%s[%s]>' % (
+            self.__class__.__name__,
+            self.name,
+        )
+
+    @property
+    def name(self):
+        return self.model.__name__
+
+    def _process_many_to_many(self, meta):
+        raw_id_threshold = self.raw_id_threshold
+        for field in meta.local_many_to_many:
+            related_objects = field.related.parent_model.objects.all()
+            if(related_objects[:raw_id_threshold].count() < raw_id_threshold):
+                yield field.name
+
+    def _process_fields(self, meta):
+        parent_fields = meta.parents.values()
+        for field in meta.fields:
+            name = self._process_field(field, parent_fields)
+            if name:
+                yield name
+
+    def _process_foreign_key(self, field):
+        raw_id_threshold = self.raw_id_threshold
+        list_filter_threshold = self.list_filter_threshold
+        max_count = max(list_filter_threshold, raw_id_threshold)
+        related_count = field.related.parent_model.objects.all()
+        related_count = related_count[:max_count].count()
+
+        if related_count >= raw_id_threshold:
+            self.raw_id_fields.append(field.name)
+
+        elif related_count < list_filter_threshold:
+            self.list_filter.append(field.name)
+
+        else:  # pragma: no cover
+            pass  # Do nothing :)
+
+    def _process_field(self, field, parent_fields):
+        if field in parent_fields:
+            return
+
+        self.list_display.append(field.name)
+        if isinstance(field, LIST_FILTER):
+            if isinstance(field, models.ForeignKey):
+                self._process_foreign_key(field)
+            else:
+                self.list_filter.append(field.name)
+
+        if field.name in self.search_field_names:
+            self.search_fields.append(field.name)
+
+        return field.name
+
+    def __unicode__(self):
+        return ''.join(self._unicode_generator())
+
+    def _yield_value(self, key, value):
+        if isinstance(value, (list, set, tuple)):
+            return self._yield_tuple(key, tuple(value))
+        elif isinstance(value, dict):
+            return self._yield_dict(key, value)
+        elif isinstance(value, str):
+            return self._yield_string(key, value)
+        else:  # pragma: no cover
+            raise TypeError('%s is not supported in %r' % (type(value), value))
+
+    def _yield_string(self, key, value, converter=repr):
+        return PRINT_ADMIN_PROPERTY % dict(
+            key=key,
+            value=converter(value),
+        )
+
+    def _yield_dict(self, key, value):
+        row_parts = []
+        row = self._yield_string(key, value)
+        if len(row) > MAX_LINE_WIDTH:
+            row_parts.append(self._yield_string(key, '{', str))
+            for k, v in value.items():
+                row_parts.append('%s%r: %r' % (2 * INDENT_WIDTH * ' ', k, v))
+
+            row_parts.append(INDENT_WIDTH * ' ' + '}')
+            row = '\n'.join(row_parts)
+
+        return row
+
+    def _yield_tuple(self, key, value):
+        row_parts = []
+        row = self._yield_string(key, value)
+        if len(row) > MAX_LINE_WIDTH:
+            row_parts.append(self._yield_string(key, '(', str))
+            for v in value:
+                row_parts.append(2 * INDENT_WIDTH * ' ' + repr(v) + ',')
+
+            row_parts.append(INDENT_WIDTH * ' ' + ')')
+            row = '\n'.join(row_parts)
+
+        return row
+
+    def _unicode_generator(self):
+        self._process()
+        for key in self.PRINTABLE_PROPERTIES:
+            value = getattr(self, key)
+            if value:
+                yield self._yield_value(key, value)
+
+    def _process(self):
+        meta = self.model._meta
+
+        self.raw_id_fields += list(self._process_many_to_many(meta))
+        field_names = list(self._process_fields(meta))
+
+        for field_name in self.date_hierarchy_names[::-1]:
+            if field_name in field_names and not self.date_hierarchy:
+                self.date_hierarchy = field_name
+                break
+
+        for k in sorted(self.prepopulated_field_names):
+            k, vs = k.split('=', 1)
+            vs = vs.split(',')
+            if k in field_names:
+                incomplete = False
+                for v in vs:
+                    if v not in field_names:
+                        incomplete = True
+                        break
+
+                if not incomplete:
+                    self.prepopulated_fields[k] = vs
+
+        self.processed = True
+
+
+class Command(BaseCommand):
+    help = '''Generate a `admin.py` file for the given app (models)'''
+    option_list = BaseCommand.option_list + (
+        optparse.make_option(
+            '-s', '--search-field', action='append',
+            default=SEARCH_FIELD_NAMES,
+            help='Fields named like this will be added to `search_fields`'
+            ' [default: %default]'),
+        optparse.make_option(
+            '-d', '--date-hierarchy', action='append',
+            default=DATE_HIERARCHY_NAMES,
+            help='A field named like this will be set as `date_hierarchy`'
+            ' [default: %default]'),
+        optparse.make_option(
+            '-p', '--prepopulated-fields', action='append',
+            default=PREPOPULATED_FIELD_NAMES,
+            help='These fields will be prepopulated by the other field.'
+            'The field names can be specified like `spam=eggA,eggB,eggC`'
+            ' [default: %default]'),
+        optparse.make_option(
+            '-l', '--list-filter-threshold', type='int',
+            default=LIST_FILTER_THRESHOLD, metavar='LIST_FILTER_THRESHOLD',
+            help='If a foreign key has less than LIST_FILTER_THRESHOLD items '
+            'it will be added to `list_filter` [default: %default]'),
+        optparse.make_option(
+            '-r', '--raw-id-threshold', type='int',
+            default=RAW_ID_THRESHOLD, metavar='RAW_ID_THRESHOLD',
+            help='If a foreign key has more than RAW_ID_THRESHOLD items '
+            'it will be added to `list_filter` [default: %default]'),
+    )
+    can_import_settings = True
+
+    @signalcommand
+    def handle(self, *args, **kwargs):
+        self.style = color_style()
+
+        installed_apps = dict((a.__name__.rsplit('.', 1)[0], a) for a in models.get_apps())
+
+        # Make sure we always have args
+        if not args:
+            args = [False]
+
+        app = installed_apps.get(args[0])
+        if not app:
+            print(self.style.WARN('This command requires an existing app name as argument'))
+            print(self.style.WARN('Available apps:'))
+            for app in sorted(installed_apps):
+                print(self.style.WARN('    %s' % app))
+            sys.exit(1)
+
+        model_res = []
+        for arg in args[1:]:
+            model_res.append(re.compile(arg, re.IGNORECASE))
+
+        self.handle_app(app, model_res, **kwargs)
+
+    def handle_app(self, app, model_res, **options):
+        print(AdminApp(app, model_res, **options))
diff --git a/django_extensions/management/commands/clean_pyc.py b/django_extensions/management/commands/clean_pyc.py
index bcae0ab..4f626e7 100644
--- a/django_extensions/management/commands/clean_pyc.py
+++ b/django_extensions/management/commands/clean_pyc.py
@@ -1,13 +1,12 @@
 import os
-import time
 import fnmatch
-import warnings
-from django.core.management.base import NoArgsCommand
+from django.core.management.base import NoArgsCommand, CommandError
 from django.conf import settings
-from django_extensions.management.utils import get_project_root
 from optparse import make_option
 from os.path import join as _j
 
+from django_extensions.management.utils import signalcommand
+
 
 class Command(NoArgsCommand):
     option_list = NoArgsCommand.option_list + (
@@ -21,6 +20,7 @@ class Command(NoArgsCommand):
 
     requires_model_validation = False
 
+    @signalcommand
     def handle_noargs(self, **options):
         project_root = options.get("path", getattr(settings, 'BASE_DIR', None))
         if not project_root:
@@ -28,23 +28,8 @@ class Command(NoArgsCommand):
 
         verbosity = int(options.get("verbosity"))
         if not project_root:
-            warnings.warn("settings.BASE_DIR or specifying --path will become mandatory in 1.4.0", DeprecationWarning)
-            project_root = get_project_root()
-            if verbosity > 0:
-                self.stdout.write("""No path specified and settings.py does not contain BASE_DIR.
-Assuming '%s' is the project root.
-
-Please add BASE_DIR to your settings.py future versions 1.4.0 and higher of Django-Extensions
-will require either BASE_DIR or specifying the --path option.
-
-Waiting for 30 seconds. Press ctrl-c to abort.
-""" % project_root)
-                if getattr(settings, 'CLEAN_PYC_DEPRECATION_WAIT', True):
-                    try:
-                        time.sleep(30)
-                    except KeyboardInterrupt:
-                        self.stdout.write("Aborted\n")
-                        return
+            raise CommandError("No --path specified and settings.py does not contain BASE_DIR")
+
... 3211 lines suppressed ...

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-django-extensions.git



More information about the Python-modules-commits mailing list