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

Brian May bam at moszumanska.debian.org
Sat Oct 24 06:50:19 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 ab5069fe8d44daa6b3fc004252476a00ed6c3f63
Author: Brian May <brian at linuxpenguins.xyz>
Date:   Sat Oct 24 14:45:21 2015 +1100

    Import python-django-extensions_1.5.7.orig.tar.gz
---
 .editorconfig                                      |  18 ++
 .gitignore                                         |   5 +-
 .travis.yml                                        |  87 +++----
 CHANGELOG.md                                       | 107 +++++++++
 MANIFEST.in                                        |   1 -
 README.rst                                         |  11 +-
 conftest.py                                        |  72 ++++++
 django_extensions/__init__.py                      |   2 +-
 django_extensions/admin/__init__.py                |  18 +-
 django_extensions/admin/filter.py                  |  59 +++++
 django_extensions/admin/widgets.py                 |   7 +-
 django_extensions/compat.py                        |  95 ++++++++
 .../app_template/migrations/__init__.py.tmpl}      |   0
 django_extensions/db/fields/__init__.py            | 266 ++++++++++++++++-----
 django_extensions/db/fields/encrypted.py           |   7 +-
 django_extensions/db/fields/json.py                |   6 +-
 django_extensions/db/models.py                     |  21 +-
 django_extensions/future_1_5.py                    |  16 --
 django_extensions/jobs/daily/cache_cleanup.py      |   4 +-
 .../management/commands => logging}/__init__.py    |   0
 django_extensions/logging/filters.py               |  35 +++
 django_extensions/management/base.py               |   1 -
 django_extensions/management/color.py              |   8 +
 .../management/commands/admin_generator.py         |  32 ++-
 django_extensions/management/commands/clean_pyc.py |   9 +-
 .../management/commands/compile_pyc.py             |   9 +-
 .../management/commands/create_app.py              |  18 +-
 .../management/commands/create_command.py          |   6 +-
 .../management/commands/create_jobs.py             |   4 +-
 .../management/commands/create_template_tags.py    |   8 +-
 .../management/commands/describe_form.py           |   3 +-
 .../management/commands/drop_test_database.py      |  11 +-
 .../management/commands/dumpscript.py              |  17 +-
 .../management/commands/export_emails.py           |  12 +-
 .../management/commands/find_template.py           |   6 +-
 .../management/commands/generate_secret_key.py     |   3 +-
 .../management/commands/graph_models.py            |   9 +-
 .../management/commands/mail_debug.py              |  13 +-
 django_extensions/management/commands/notes.py     |   9 +-
 django_extensions/management/commands/passwd.py    |  11 +-
 .../management/commands/pipchecker.py              |  50 ++--
 .../management/commands/print_settings.py          |   6 +-
 .../management/commands/print_user_for_session.py  |  10 +-
 django_extensions/management/commands/reset_db.py  |   9 +-
 django_extensions/management/commands/runjob.py    |   4 +-
 django_extensions/management/commands/runjobs.py   |   8 +-
 .../management/commands/runprofileserver.py        |  10 +-
 django_extensions/management/commands/runscript.py |  13 +-
 .../management/commands/runserver_plus.py          | 104 +++++++-
 .../management/commands/set_default_site.py        |   2 +-
 .../management/commands/set_fake_emails.py         |  13 +-
 .../management/commands/set_fake_passwords.py      |   7 +-
 .../management/commands/shell_plus.py              | 165 ++++++++++---
 .../management/commands/show_templatetags.py       |   5 +-
 django_extensions/management/commands/show_urls.py | 122 ++++++----
 django_extensions/management/commands/sqlcreate.py |   7 +-
 django_extensions/management/commands/sqldiff.py   |  34 ++-
 django_extensions/management/commands/sqldsn.py    | 143 +++++++++++
 django_extensions/management/commands/sync_s3.py   |  14 +-
 django_extensions/management/commands/syncdata.py  |  12 +-
 .../management/commands/unreferenced_files.py      |   3 +-
 .../management/commands/update_permissions.py      |   3 +-
 .../management/commands/validate_templates.py      |  19 +-
 .../management/email_notifications.py              |   4 +-
 django_extensions/management/modelviz.py           |  48 ++--
 django_extensions/management/shells.py             |  47 ++--
 django_extensions/management/technical_response.py |   1 -
 django_extensions/management/utils.py              |  33 +--
 django_extensions/mongodb/fields/encrypted.py      |   4 +-
 django_extensions/mongodb/fields/json.py           |  18 +-
 django_extensions/mongodb/models.py                |  10 +-
 django_extensions/settings.py                      |   1 +
 django_extensions/templatetags/highlighting.py     |  18 +-
 django_extensions/templatetags/syntax_color.py     |  17 +-
 django_extensions/templatetags/truncate_letters.py |   1 -
 django_extensions/templatetags/widont.py           |   2 +
 django_extensions/tests/__init__.py                |  26 --
 django_extensions/tests/shortuuid_field.py         |  39 ---
 django_extensions/tests/utils.py                   |  76 ------
 django_extensions/utils/dia2django.py              |  11 +-
 django_extensions/utils/text.py                    |   1 -
 django_extensions/utils/validatingtemplatetags.py  |   3 +-
 docs/AUTHORS                                       |   1 +
 docs/admin_extensions.rst                          |  19 ++
 docs/command_extensions.rst                        |   5 +
 docs/conf.py                                       |   2 +-
 docs/field_extensions.rst                          |  18 ++
 docs/index.rst                                     |   3 +-
 docs/model_extensions.rst                          |   7 +-
 docs/runscript.rst                                 |   2 +-
 docs/runserver_plus.rst                            |  38 ++-
 docs/shell_plus.rst                                |  25 ++
 docs/sqldsn.rst                                    |  52 ++++
 run_tests.py                                       | 141 -----------
 setup.cfg                                          |  14 ++
 setup.py                                           |  37 ++-
 .../tests/testapp => tests}/__init__.py            |   0
 .../tests => tests}/management/__init__.py         |   0
 .../management/commands}/__init__.py               |   0
 .../management/commands/error_raising_command.py   |   2 -
 .../fields.py => tests/test_autoslug_fields.py     |  73 +++---
 .../tests => tests}/test_clean_pyc.py              |  21 +-
 .../tests => tests}/test_compile_pyc.py            |  35 ++-
 .../tests => tests}/test_dumpscript.py             |  30 +--
 .../test_encrypted_fields.py                       |  88 +++----
 .../json_field.py => tests/test_json_field.py      |  13 +-
 .../test_management_command.py                     |  34 ++-
 {django_extensions/tests => tests}/test_models.py  |   3 +-
 tests/test_randomchar_field.py                     |  92 +++++++
 tests/test_runscript.py                            |  33 +++
 tests/test_shortuuid_field.py                      |  29 +++
 .../tests => tests}/test_templatetags.py           |   1 -
 tests/test_utils.py                                |  26 ++
 .../uuid_field.py => tests/test_uuid_field.py      |  20 +-
 .../tests/management => tests/testapp}/__init__.py |   0
 tests/testapp/apps.py                              |   8 +
 .../tests => tests}/testapp/models.py              |  81 ++++++-
 .../testapp/scripts}/__init__.py                   |   0
 tests/testapp/scripts/sample_script.py             |   2 +
 {django_extensions/tests => tests}/testapp/urls.py |   0
 tox.ini                                            | 103 +++-----
 121 files changed, 2160 insertions(+), 1047 deletions(-)

diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..5c99f65
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,18 @@
+# http://editorconfig.org
+# Source: pydanny cookiecutter-django repo
+
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.{py,rst,ini}]
+indent_style = space
+indent_size = 4
+
+[*.yml]
+indent_style = space
+indent_size = 2
diff --git a/.gitignore b/.gitignore
index ca4ca99..8d268cd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,8 +6,11 @@ build
 dist
 docs/_build
 docs/_static
-venv
+venv*
 *.egg-info
 .tox
 *.bak
 .DS_Store
+.eggs/
+.coverage
+
diff --git a/.travis.yml b/.travis.yml
index b36720f..8a3dc3b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,52 +1,53 @@
 language: python
 
-python:
-  - 2.6
-  - 2.7
-  - 3.3
-  - 3.4
-  - pypy
-  - pypy3
+sudo: false
 
 env:
-  - DJANGO=Django==1.4
-  - DJANGO=Django==1.5
-  - DJANGO=Django==1.6
-  - DJANGO=Django==1.7
-  - DJANGO=Django==1.8a1
-  - DJANGO="git+git://github.com/django/django.git@master#egg=django"
+    - TOX_ENV=py27-flake8
+    - TOX_ENV=py34-flake8
+    - TOX_ENV=py27-dj14
+    - TOX_ENV=py27-dj15
+    - TOX_ENV=py27-dj16
+    - TOX_ENV=py32-dj15
+    - TOX_ENV=py32-dj16
+    - TOX_ENV=py33-dj15
+    - TOX_ENV=py33-dj16
+    - TOX_ENV=py34-dj15
+    - TOX_ENV=py34-dj16
+    - TOX_ENV=py27-dj17
+    - TOX_ENV=py27-dj18
+    - TOX_ENV=py27-djmaster
+    - TOX_ENV=py32-dj17
+    - TOX_ENV=py32-dj18
+    - TOX_ENV=py32-djmaster
+    - TOX_ENV=py33-dj17
+    - TOX_ENV=py33-dj18
+    - TOX_ENV=py33-djmaster
+    - TOX_ENV=py34-dj17
+    - TOX_ENV=py34-dj18
+    - TOX_ENV=py34-djmaster
+    - TOX_ENV=pypy-dj17
+    - TOX_ENV=pypy-dj18
+    - TOX_ENV=pypy-djmaster
+    - TOX_ENV=pypy3-dj17
+    - TOX_ENV=pypy3-dj18
+    - TOX_ENV=pypy3-djmaster
+
+matrix:
+  fast_finish: true
+  allow_failures:
+    - env: TOX_ENV=py34-djmaster
+    - env: TOX_ENV=py33-djmaster
+    - env: TOX_ENV=py32-djmaster
+    - env: TOX_ENV=py27-djmaster
+    - env: TOX_ENV=pypy-djmaster
+    - env: TOX_ENV=pypy3-djmaster
 
 install:
-  - pip install -q $DJANGO
-  - pip install flake8
-  - pip install python-dateutil
-  - if [[ $TRAVIS_PYTHON_VERSION < '3.0' ]]; then pip install --pre -q python-keyczar; fi
-  - pip install -q PyYAML shortuuid
-  - pip install -e .
+  - pip install tox coveralls
 
 script:
-  - flake8 --ignore=E265,E501,W391 .
-  - python setup.py test
+  - tox -e $TOX_ENV
 
-matrix:
-  exclude:
-    - python: 2.6
-      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"
+after_success:
+  - coveralls
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cd2122e..5303e1c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,113 @@
 Changelog
 =========
 
+1.5.7
+-----
+
+Changes:
+ - Fix: CreationDateTimeField, migration error
+ - Fix: ModificationDateTimeField, migration error
+ - Fix: shell_plus, options is not always in db config dictionary
+ - Fix: admin filters, contrib.admin.util fallback code
+ - Fix: graph_models, currectly support parsing lists for cli options
+ - Improvement: sqldsn, support postfix
+ - Improvement: utils, remove get_project_root function
+
+
+1.5.6
+-----
+
+Changes:
+ - New: RandomCharField, prepopulates random character string
+ - New: (Not)NullFieldListFilter, filters for admin
+ - New: runserver_plus, integrate with django-pdb
+ - New: runserver_plus, add check_migrations from Django
+ - Improvement: show_urls, nested namespace support
+ - Improvement: show_urls, allow to specify alternative urlconf
+ - Improvement: show_urls, support i18n_patterns
+ - Improvement: show_urls, use --language to filter on a particular language
+ - Improvement: admin_generator, added docstrings to module
+ - Improvement: shell_plus, allow cli arguments to be passed to ipython
+ - Improvement: shell_plus, fixed PYTHONPATH bug when using django-admin shell_plus --notebook
+ - Improvement: shell_plus, set application_name on PostgreSQL databases
+ - Improvement: shell_plus, load user pypython config file
+ - Improvement: CreationDateTimeField, use auto_now_add instead of default ModificationDateTimeField
+ - Improvement: ModificationDateTimeField, use auto_now instead of pre_save method
+ - Improvement: ForeignKeyAutocompleteAdmin, added ability to filter autocomplete query
+ - Fix: shell_plus, support for pypython>=0.27
+ - Fix: shell_plus, load apps and models directly through the apps interface when available
+ - Fix: shell_plus, use ipython start_ipython instead of embed
+ - Fix: shell_plus, fix swalling ImportErrors with IPython 3 and higher
+ - Fix: dumpscript, fix missing imports in dumped script
+ - Fix: admin_generator, fix issues with Django 1.9
+ - Fix: template tags, move exception for import failure to inside of the template tags
+ - Fix: reset_db, fix for Django 1.9
+ - Fix: runserver_plus, fix for Django 1.9
+
+
+1.5.5
+-----
+
+Changes:
+ - Fix: sqldiff, previous Django 1.8 fix was slightly broken
+
+
+1.5.4
+-----
+
+Changes:
+ - Improvement: syncdata, add skip-remove option
+ - Improvement: logging, report how often mail was ratelimited
+ - Fix: admin, Django 1.8 compatibility module_name is now called model_name
+ - Fix: notes, Python 3.x fix force output of filter into list
+ - Fix: sqldiff, fix for Django 1.8
+
+
+1.5.3
+-----
+
+Changes:
+ - New: ratelimiter, a simple ratelimiter filter for Python logging
+ - Fix: various improvements for Django 1.8
+ - Fix: sync_s3, use os.walk instead of os.path.walk (py3 fix)
+ - Improvement: pipchecker, use name instead of url_name to fix casing mismatches
+ - Improvement: pipchecker, use https
+ - Improvement: pipchecker, fix issues with new(er) pip versions
+ - Docs: fixed a few typos
+ - Docs: added documentation about NOTEBOOK_ARGUMENTS settings
+
+
+1.5.2
+-----
+
+Changes:
+ - New: sqldsn, prints Data Source Name for defined database(s)
+ - Fix: graph_models, Django 1.8 support
+ - Fix: highlighting tag, fix usage of is_safe
+ - Fix: runscript, fix for runscript with AppConfig apps
+ - Fix: sqldiff, KeyError when index is missing in database
+ - Fix: sqldiff, multi column indexes was also counted as a single colomn index
+ - Improvements: JSONField, Added try/catch for importing json/simplejson for Django 1.7
+
+
+1.5.1
+-----
+
+Changes:
+ - New: runserver_plus, add support for --extra-files parameter
+ - Fix: Django 1.7 defined MIDDLEWARE_CLASSES for tests
+ - Fix: shell_plus, problem when auto-loading modules with empty '__module__' property
+ - Improvement: shell_plus, IPython 3.x support for notebooks
+ - Improvement: tests, move to py.test and lots of other improvements
+ - Improvement: create_app, add migrations folder
+ - Improvement: tox.ini, refactored to be more DRY
+ - Improvement: runserver_plus, also reload on changes to translation files
+ - Improvement: runserver_plus, add reloader_interval support
+ - Improvement: create_template_tags, removed unusued command line option
+ - Docs: print_user_for_session, add note about SESSION_ENGINE
+ - Docs: runserver_plus, added section about IO calls and CPU usage
+
+
 1.5.0
 -----
 
diff --git a/MANIFEST.in b/MANIFEST.in
index f9b717a..c9207cb 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -4,5 +4,4 @@ recursive-include django_extensions/static *
 recursive-include django_extensions/locale *
 recursive-include docs *
 include LICENSE
-include run_tests.py
 include tox.ini
diff --git a/README.rst b/README.rst
index 06d839d..c92ae51 100644
--- a/README.rst
+++ b/README.rst
@@ -17,6 +17,9 @@
     :target: https://pypi.python.org/pypi/django-extensions/
     :alt: Number of PyPI downloads
 
+.. image:: https://coveralls.io/repos/django-extensions/django-extensions/badge.png?branch=master
+   :target: https://coveralls.io/r/django-extensions/django-extensions?branch=master
+   :alt: Coverage
 
 Django Extensions is a collection of custom extensions for the Django Framework.
 
@@ -80,11 +83,11 @@ Check templates for rendering errors::
 
     $ python manage.py validate_templates
 
-Run the enchanced django shell::
+Run the enhanced django shell::
 
     $ python manage.py shell_plus
 
-Run the enchanced django runserver, (requires Werkzeug install)::
+Run the enhanced django runserver, (requires Werkzeug install)::
 
     $ python manage.py runserver_plus
 
@@ -124,7 +127,6 @@ 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
@@ -134,9 +136,6 @@ We have setup a couple of ways you can donate towards Django Extensions using th
     :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
diff --git a/conftest.py b/conftest.py
new file mode 100644
index 0000000..3091bdd
--- /dev/null
+++ b/conftest.py
@@ -0,0 +1,72 @@
+from django.conf import settings
+
+
+def pytest_configure():
+    import sys
+
+    try:
+        import django  # NOQA
+    except ImportError:
+        print("Error: missing test dependency:")
+        print("  django library is needed to run test suite")
+        print("  you can install it with 'pip install django'")
+        print("  or use tox to automatically handle test dependencies")
+        sys.exit(1)
+
+    try:
+        import shortuuid  # NOQA
+    except ImportError:
+        print("Error: missing test dependency:")
+        print("  shortuuid library is needed to run test suite")
+        print("  you can install it with 'pip install shortuuid'")
+        print("  or use tox to automatically handle test dependencies")
+        sys.exit(1)
+
+    try:
+        import dateutil  # NOQA
+    except ImportError:
+        print("Error: missing test dependency:")
+        print("  dateutil library is needed to run test suite")
+        print("  you can install it with 'pip install python-dateutil'")
+        print("  or use tox to automatically handle test dependencies")
+        sys.exit(1)
+
+    try:
+        import six  # NOQA
+    except ImportError:
+        print("Error: missing test dependency:")
+        print("  six library is needed to run test suite")
+        print("  you can install it with 'pip install six'")
+        print("  or use tox to automatically handle test dependencies")
+        sys.exit(1)
+
+    # Dynamically configure the Django settings with the minimum necessary to
+    # get Django running tests.
+    settings.configure(
+        INSTALLED_APPS=[
+            'django.contrib.auth',
+            'django.contrib.contenttypes',
+            'django.contrib.admin',
+            'django.contrib.sessions',
+            'tests.testapp',
+            'django_extensions',
+        ],
+        MIDDLEWARE_CLASSES=(
+            'django.contrib.sessions.middleware.SessionMiddleware',
+            'django.contrib.auth.middleware.AuthenticationMiddleware',
+            'django.contrib.messages.middleware.MessageMiddleware',
+        ),
+        # Django replaces this, but it still wants it. *shrugs*
+        DATABASE_ENGINE='django.db.backends.sqlite3',
+        DATABASES={
+            'default': {
+                'ENGINE': 'django.db.backends.sqlite3',
+                'NAME': ':memory:',
+            }
+        },
+        MEDIA_ROOT='/tmp/django_extensions_test_media/',
+        MEDIA_PATH='/media/',
+        ROOT_URLCONF='tests.urls',
+        DEBUG=True,
+        TEMPLATE_DEBUG=True,
+    )
diff --git a/django_extensions/__init__.py b/django_extensions/__init__.py
index 65ff1ac..03c8095 100644
--- a/django_extensions/__init__.py
+++ b/django_extensions/__init__.py
@@ -1,5 +1,5 @@
 
-VERSION = (1, 5, 0)
+VERSION = (1, 5, 7)
 
 # 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 46e10f4..019c745 100644
--- a/django_extensions/admin/__init__.py
+++ b/django_extensions/admin/__init__.py
@@ -5,6 +5,7 @@ import six
 import operator
 from six.moves import reduce
 
+import django
 from django.http import HttpResponse, HttpResponseNotFound
 from django.conf import settings
 from django.db import models
@@ -60,7 +61,12 @@ class ForeignKeyAutocompleteAdmin(ModelAdmin):
                 return self.admin_site.admin_view(view)(*args, **kwargs)
             return update_wrapper(wrapper, view)
 
-        info = self.model._meta.app_label, self.model._meta.module_name
+        # model._meta.module_name is deprecated in django version 1.7 and removed in django version 1.8.
+        # It is replaced by model._meta.model_name
+        if django.VERSION < (1, 7):
+            info = self.model._meta.app_label, self.model._meta.module_name
+        else:
+            info = self.model._meta.app_label, self.model._meta.model_name
 
         urlpatterns = patterns('', url(r'foreignkey_autocomplete/$', wrap(self.foreignkey_autocomplete), name='%s_%s_autocomplete' % info))
         urlpatterns += super(ForeignKeyAutocompleteAdmin, self).get_urls()
@@ -107,6 +113,10 @@ class ForeignKeyAutocompleteAdmin(ModelAdmin):
                     other_qs = other_qs.filter(reduce(operator.or_, or_queries))
                     queryset = queryset & other_qs
 
+                additional_filter = self.get_related_filter(model, request)
+                if additional_filter:
+                    queryset = queryset.filter(additional_filter)
+
                 if self.autocomplete_limit:
                     queryset = queryset[:self.autocomplete_limit]
 
@@ -121,6 +131,12 @@ class ForeignKeyAutocompleteAdmin(ModelAdmin):
             return HttpResponse(data)
         return HttpResponseNotFound()
 
+    def get_related_filter(self, model, request):
+        """Given a model class and current request return an optional Q object
+        that should be applied as an additional filter for autocomplete query.
+        If no additional filtering is needed, this method should return
+        None."""
+
     def get_help_text(self, field_name, model_name):
         searchable_fields = self.related_search_fields.get(field_name, None)
         if searchable_fields:
diff --git a/django_extensions/admin/filter.py b/django_extensions/admin/filter.py
new file mode 100644
index 0000000..571e235
--- /dev/null
+++ b/django_extensions/admin/filter.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.contrib.admin import FieldListFilter
+try:
+    from django.contrib.admin.utils import prepare_lookup_value
+except ImportError:
+    # django < 1.7
+    from django.contrib.admin.util import prepare_lookup_value
+from django.utils.translation import ugettext_lazy as _
+
+
+class NullFieldListFilter(FieldListFilter):
+    def __init__(self, field, request, params, model, model_admin, field_path):
+        self.lookup_kwarg = '{0}__isnull'.format(field_path)
+        super(NullFieldListFilter, self).__init__(field, request, params, model, model_admin, field_path)
+        lookup_choices = self.lookups(request, model_admin)
+        self.lookup_choices = () if lookup_choices is None else list(lookup_choices)
+
+    def expected_parameters(self):
+        return [self.lookup_kwarg]
+
+    def value(self):
+        return self.used_parameters.get(self.lookup_kwarg, None)
+
+    def lookups(self, request, model_admin):
+        return (
+            ('1', _('Yes')),
+            ('0', _('No')),
+        )
+
+    def choices(self, cl):
+        yield {
+            'selected': self.value() is None,
+            'query_string': cl.get_query_string({}, [self.lookup_kwarg]),
+            'display': _('All'),
+        }
+        for lookup, title in self.lookup_choices:
+            yield {
+                'selected': self.value() == prepare_lookup_value(self.lookup_kwarg, lookup),
+                'query_string': cl.get_query_string({
+                    self.lookup_kwarg: lookup,
+                }, []),
+                'display': title,
+            }
+
+    def queryset(self, request, queryset):
+        if self.value() is not None:
+            kwargs = {self.lookup_kwarg: self.value()}
+            return queryset.filter(**kwargs)
+        return queryset
+
+
+class NotNullFieldListFilter(NullFieldListFilter):
+    def lookups(self, request, model_admin):
+        return (
+            ('0', _('Yes')),
+            ('1', _('No')),
+        )
diff --git a/django_extensions/admin/widgets.py b/django_extensions/admin/widgets.py
index e48e090..01cec62 100644
--- a/django_extensions/admin/widgets.py
+++ b/django_extensions/admin/widgets.py
@@ -1,12 +1,11 @@
-import six
 import django
-
+import six
 from django import forms
 from django.contrib.admin.sites import site
+from django.contrib.admin.widgets import ForeignKeyRawIdWidget
+from django.template.loader import render_to_string
 from django.utils.safestring import mark_safe
 from django.utils.text import Truncator
-from django.template.loader import render_to_string
-from django.contrib.admin.widgets import ForeignKeyRawIdWidget
 
 
 class ForeignKeySearchInput(ForeignKeyRawIdWidget):
diff --git a/django_extensions/compat.py b/django_extensions/compat.py
new file mode 100644
index 0000000..01017ce
--- /dev/null
+++ b/django_extensions/compat.py
@@ -0,0 +1,95 @@
+from __future__ import unicode_literals
+
+import sys
+
+import django
+from django.conf import settings
+
+# flake8: noqa
+
+#
+# Python compatibility
+#
+PY3 = sys.version_info[0] == 3
+OLD_PY2 = sys.version_info[:2] < (2, 7)
+
+if PY3:  # pragma: no cover
+    from io import StringIO
+    import importlib
+
+elif OLD_PY2:  # pragma: no cover
+    from cStringIO import StringIO
+    from django.utils import importlib
+
+else:  # pragma: no cover
+    from cStringIO import StringIO
+    import importlib
+
+#
+# Django compatibility
+#
+try:  # Django 1.5
+    from django.contrib.auth import get_user_model
+except ImportError:  # pragma: no cover
+    assert django.VERSION < (1, 5)
+    from django.contrib.auth.models import User
+    User.USERNAME_FIELD = "username"
+    User.get_username = lambda self: self.username
+
+    def get_user_model():
+        return User
+
+
+def list_apps():
+    try:
+        # django >= 1.7, to support AppConfig
+        from django.apps import apps
+        return [app.name for app in apps.get_app_configs()]
+    except ImportError:
+        # old way
+        return settings.INSTALLED_APPS
+
+
+def get_apps():
+    try:
+        # django >= 1.7, to support AppConfig
+        from django.apps import apps
+        return [app.models_module for app in apps.get_app_configs() if app.models_module]
+    except ImportError:
+        from django.db import models
+        return models.get_apps()
+
+
+def get_app_models(app_labels=None):
+    if app_labels is None:
+        try:
+            # django >= 1.7, to support AppConfig
+            from django.apps import apps
+            return apps.get_models(include_auto_created=True)
+        except ImportError:
+            from django.db import models
+            return models.get_models(include_auto_created=True)
+
+    if not isinstance(app_labels, (list, tuple, set)):
+        app_labels = [app_labels]
+
+    app_models = []
+    try:
+        # django >= 1.7, to support AppConfig
+        from django.apps import apps
+
+        for app_label in app_labels:
+            app_config = apps.get_app_config(app_label)
+            app_models.extend(app_config.get_models(include_auto_created=True))
+    except ImportError:
+        from django.db import models
+
+        try:
+            app_list = [models.get_app(app_label) for app_label in app_labels]
+        except (models.ImproperlyConfigured, ImportError) as e:
+            raise CommandError("%s. Are you sure your INSTALLED_APPS setting is correct?" % e)
+
+        for app in app_list:
+            app_models.extend(models.get_models(app, include_auto_created=True))
+
+    return app_models
diff --git a/django_extensions/tests/management/__init__.py b/django_extensions/conf/app_template/migrations/__init__.py.tmpl
similarity index 100%
copy from django_extensions/tests/management/__init__.py
copy to django_extensions/conf/app_template/migrations/__init__.py.tmpl
diff --git a/django_extensions/db/fields/__init__.py b/django_extensions/db/fields/__init__.py
index d68c04e..d48774d 100644
--- a/django_extensions/db/fields/__init__.py
+++ b/django_extensions/db/fields/__init__.py
@@ -3,6 +3,7 @@ Django Extensions additional model fields
 """
 import re
 import six
+import string
 import warnings
 
 try:
@@ -18,8 +19,9 @@ except ImportError:
     HAS_SHORT_UUID = False
 
 from django.core.exceptions import ImproperlyConfigured
-from django.template.defaultfilters import slugify
 from django.db.models import DateTimeField, CharField, SlugField
+from django.utils.crypto import get_random_string
+from django.template.defaultfilters import slugify
 
 try:
     from django.utils.timezone import now as datetime_now
@@ -34,7 +36,45 @@ except ImportError:
     from django.utils.encoding import force_text as force_unicode  # NOQA
 
 
-class AutoSlugField(SlugField):
+MAX_UNIQUE_QUERY_ATTEMPTS = 100
+
+
+class UniqueFieldMixin(object):
+
+    def check_is_bool(self, attrname):
+        if not isinstance(getattr(self, attrname), bool):
+            raise ValueError("'{}' argument must be True or False".format(attrname))
+
+    def get_queryset(self, model_cls, slug_field):
+        for field, model in model_cls._meta.get_fields_with_model():
+            if model and field == slug_field:
+                return model._default_manager.all()
+        return model_cls._default_manager.all()
+
+    def find_unique(self, model_instance, field, iterator, *args):
+        # exclude the current model instance from the queryset used in finding
+        # next valid hash
+        queryset = self.get_queryset(model_instance.__class__, field)
+        if model_instance.pk:
+            queryset = queryset.exclude(pk=model_instance.pk)
+
+        # form a kwarg dict used to impliment any unique_together contraints
+        kwargs = {}
+        for params in model_instance._meta.unique_together:
+            if self.attname in params:
+                for param in params:
+                    kwargs[param] = getattr(model_instance, param, None)
+
+        new = six.next(iterator)
+        kwargs[self.attname] = new
+        while not new or queryset.filter(**kwargs):
+            new = six.next(iterator)
+            kwargs[self.attname] = new
+        setattr(model_instance, self.attname, new)
+        return new
+
+
+class AutoSlugField(UniqueFieldMixin, SlugField):
     """ AutoSlugField
 
     By default, sets editable=False, blank=True.
@@ -68,11 +108,9 @@ class AutoSlugField(SlugField):
         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):
-            raise ValueError("'overwrite' argument must be True or False")
+        self.check_is_bool('overwrite')
         self.allow_duplicates = kwargs.pop('allow_duplicates', False)
-        if not isinstance(self.allow_duplicates, bool):
-            raise ValueError("'allow_duplicates' argument must be True or False")
+        self.check_is_bool('allow_duplicates')
         super(AutoSlugField, self).__init__(*args, **kwargs)
 
     def _slug_strip(self, value):
@@ -87,17 +125,25 @@ class AutoSlugField(SlugField):
         value = re.sub('%s+' % re_sep, self.separator, value)
         return re.sub(r'^%s+|%s+$' % (re_sep, re_sep), '', value)
 
-    def get_queryset(self, model_cls, slug_field):
-        for field, model in model_cls._meta.get_fields_with_model():
-            if model and field == slug_field:
-                return model._default_manager.all()
-        return model_cls._default_manager.all()
-
     def slugify_func(self, content):
         if content:
             return self.slugify_function(content)
         return ''
 
+    def slug_generator(self, original_slug, start):
+        yield original_slug
+        for i in range(start, MAX_UNIQUE_QUERY_ATTEMPTS):
+            slug = original_slug
+            end = '%s%s' % (self.separator, i)
+            end_len = len(end)
+            if self.slug_len and len(slug) + end_len > self.slug_len:
+                slug = slug[:self.slug_len - end_len]
+                slug = self._slug_strip(slug)
+            slug = '%s%s' % (slug, end)
+            yield slug
+        raise RuntimeError('max slug attempts for %s exceeded (%s)' %
+            (original_slug, MAX_UNIQUE_QUERY_ATTEMPTS))
+
     def create_slug(self, model_instance, add):
         # get fields to populate from and slug field to set
         if not isinstance(self._populate_from, (list, tuple)):
@@ -108,7 +154,7 @@ class AutoSlugField(SlugField):
             # slugify the original field content and set next step to 2
             slug_for_field = lambda field: self.slugify_func(getattr(model_instance, field))
             slug = self.separator.join(map(slug_for_field, self._populate_from))
-            next = 2
+            start = 2
         else:
             # get slug from the current model instance
             slug = getattr(model_instance, self.attname)
@@ -118,46 +164,20 @@ class AutoSlugField(SlugField):
 
         # strip slug depending on max_length attribute of the slug field
         # and clean-up
-        slug_len = slug_field.max_length
-        if slug_len:
-            slug = slug[:slug_len]
+        self.slug_len = slug_field.max_length
+        if self.slug_len:
+            slug = slug[:self.slug_len]
         slug = self._slug_strip(slug)
         original_slug = slug
 
         if self.allow_duplicates:
             return slug
 
-        # exclude the current model instance from the queryset used in finding
-        # the next valid slug
-        queryset = self.get_queryset(model_instance.__class__, slug_field)
-        if model_instance.pk:
-            queryset = queryset.exclude(pk=model_instance.pk)
-
-        # form a kwarg dict used to impliment any unique_together contraints
-        kwargs = {}
-        for params in model_instance._meta.unique_together:
-            if self.attname in params:
-                for param in params:
-                    kwargs[param] = getattr(model_instance, param, None)
-        kwargs[self.attname] = slug
-
-        # increases the number while searching for the next valid slug
-        # depending on the given slug, clean-up
-        while not slug or queryset.filter(**kwargs):
-            slug = original_slug
-            end = '%s%s' % (self.separator, next)
-            end_len = len(end)
-            if slug_len and len(slug) + end_len > slug_len:
-                slug = slug[:slug_len - end_len]
-                slug = self._slug_strip(slug)
-            slug = '%s%s' % (slug, end)
-            kwargs[self.attname] = slug
-            next += 1
-        return slug
+        return super(AutoSlugField, self).find_unique(
+            model_instance, slug_field, self.slug_generator(original_slug, start))
 
     def pre_save(self, model_instance, add):
         value = force_unicode(self.create_slug(model_instance, add))
-        setattr(model_instance, self.attname, value)
         return value
 
     def get_internal_type(self):
@@ -190,16 +210,149 @@ class AutoSlugField(SlugField):
         return name, path, args, kwargs
 
 
+class RandomCharField(UniqueFieldMixin, CharField):
+    """ RandomCharField
+
+    By default, sets editable=False, blank=True, unique=False.
+
+    Required arguments:
+
+    length
+        Specifies the length of the field
+
+    Optional arguments:
+
+    unique
+        If set to True, duplicate entries are not allowed (default: False)
+
+    lowercase
+        If set to True, lowercase the alpha characters (default: False)
+
+    uppercase
+        If set to True, uppercase the alpha characters (default: False)
+
+    include_alpha
+        If set to True, include alpha characters (default: True)
+
+    include_digits
+        If set to True, include digit characters (default: True)
+
+    include_punctuation
+        If set to True, include punctuation characters (default: False)
+    """
+    def __init__(self, *args, **kwargs):
+        kwargs.setdefault('blank', True)
+        kwargs.setdefault('editable', False)
+
+        self.length = kwargs.pop('length', None)
+        if self.length is None:
+            raise ValueError("missing 'length' argument")
+        kwargs['max_length'] = self.length
+
+        self.lowercase = kwargs.pop('lowercase', False)
+        self.check_is_bool('lowercase')
+        self.uppercase = kwargs.pop('uppercase', False)
+        self.check_is_bool('uppercase')
+        if self.uppercase and self.lowercase:
+            raise ValueError("the 'lowercase' and 'uppercase' arguments are mutually exclusive")
... 4722 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