[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